长期以来公司架构是基于 Spring 的单体应用,经过这么多年的发展,在企业应用开发层面沉淀颇多,对于新需求也能快速响应。但是由于目前公司业务多元化,单体应用的瓶颈就上来了,比如某个业务需要独立迭代,SAAS 改造的提出等,所以计划将单体升级改造为微服务架构。
目前经过调研,整体升级代价不大,因为虽然是单体应用,但是依然使用多模块开发,各应用天然解耦。但是目前遇到几个比较麻烦的问题,看看有做过相同事情的 hxd 有没有解决方案。
为了降低模块之间的耦合度,系统中大量利用了 Spring 的一些特性,比如 getBeansOfType(),这种模式下,只需要在 A 模块定义好接口,在 B 和 C 模块定义好实现,即可在不同的业务场景下调用不同的实现。这种方式在同一个 Spring 上下中可以轻易获取实现类,但是微服务后就是多个上下文环境,就无法获取到这些实现类了。 伪代码如下:
模块 A:
public interface MessageSender {
/**
* 发送者唯一标识
*/
String key();
/**
* 发送消息
*/
void send(List<Message> messages);
}
@Controller
public class MessageController {
private ApplicationContext ac;
public void sendMessage(String key){
List<MessageSender> messageSenders = ac.getBeansOfType(MessageSender.class);
for(MessageSender sender: messageSenders){
// 获取对应的实例
if(Objects.equals(sender.getKey(), key)){
// 推送消息
sender.send(...);
break;
}
}
}
}
模块 B
@Component
public class EmailMessageSender implements MessageSender {
/**
* 发送者唯一标识
*/
String key(){
return "email":
}
/**
* 发送消息
*/
void send(List<Message> messages){
// 发送邮件
}
}
模块 C
@Component
public class SMSMessageSender implements MessageSender {
/**
* 发送者唯一标识
*/
String key(){
return "sms":
}
/**
* 发送消息
*/
void send(List<Message> messages){
// 发送短信
}
}
利用 Spring 的ApplicationEventPublisher
实现事件推送,目前了解到可以通过对接Spring Cloud Bus
实现跨服务事件推送。现在在我们应用中有同步事件和异步事件,异步事件使用@Async
实现,目前没有了解到Spring Cloud Bus
是否支持同步事件?
模块 A
public class MessageService{
ApplicationEventPublisher publisher;
public void sendMessage(){
publisher.publishEvent(event);
}
}
模块 B
// 模块 B 监听程序
public class EventListener{
@EventListener
public void onEvent(){
// 同步监听
}
@Async
@EventListener
public void onEvent(){
// 异步监听
}
}
1
love2075904 OP 目前针对第一个问题,有一个解决方案是模仿 xxl-job 这种,让模块 B 和模块 C 向模块 A 进行注册,然后通过 http 调用执行
|
2
sky857412 2022-02-16 17:55:51 +08:00
第一个问题,应该将 MessageSender 的实现类都放到一个微服务中,controller 调用这个微服务暴露的接口,具体是调用哪个实现类,在这个暴露的接口中处理
第二个,应该无法没办法实现同步事件 |
3
wolfie 2022-02-16 18:11:05 +08:00
@FeignClient
public interface EmailMessageSender extends MessageSender {} |
4
psydonki 2022-02-17 01:10:56 +08:00
我理解使用 MQ 作为中间件可以很好的解决这个问题。
Sender 只负责向 MQ 发送消息,同时在 Header 里面注明消息的类型: ```json { "type": "email" } ``` 然后根据 header 中的类型,将消息分流到不同的 queue 。 模块 B/C 分别是不同的微服务,监听不同的 queue 就好。 |
5
love2075904 OP @sky857412 之前有过这种想法,但是整体看来是不可能的,MessageSender 只是伪代码,实际业务中是不同的业务模块处理代码。
|
6
love2075904 OP @wolfie 这种就写死了,但是实际上,模块 A 根本不需要知道有哪些实现,这个时候完全可以出现一个模块 D 来实现一个新的 MessageSender
|
7
love2075904 OP @psydonki MQ 确实是一个思路,感谢!
|
8
wolfie 2022-02-17 09:44:09 +08:00
|
9
kowgarnett 2022-02-17 10:41:21 +08:00
我司的做法是抽出来了一个单独的微服务负责所有的 send message ,需要同步的用 REST call ,不需要的用 Kafka event 处理
|
10
freeup 2022-02-17 16:55:40 +08:00
对于拆分单体项目成微服务的我说几个我经历过得坑
1.方法间调用问题,原来都是直接调用,拆分出去后就是远程调用,所以相关代码都得改造 2.查询。。查询是最麻烦的,如果都在同一个库都还好,如果拆分时拆分了库。。那么以前的查询要么直接跨库联查,要么单独查 3.公共部分需要单独打包,也就是一些公共的依赖需要单独新建项目进行开发然后整合,各个服务进行依赖 4.事务问题,我们用 seata 解决分布式事务问题 其他的 都是一些业务上的问题了 我遇到过的就这几个问题比较麻烦 |
11
love2075904 OP @wolfie 我大概了解您的意思了,感谢。
|
12
love2075904 OP @kowgarnett 这也是一个方案,不过目前这样改动影响比较大,这样一来这个单独的微服务可能就不够纯粹,会去调用其他的业务模块代码了。
|
13
love2075904 OP @freeup 确实,这几个坑我们肯定也要踩。
|
14
kowgarnett 2022-02-22 02:50:26 +08:00
@love2075904 那就看你们对于微服务的业务范围的定义了,这个都是要讨论看 trade-off 找平衡的
|