长期以来公司架构是基于 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 找平衡的 |