单体应用改造微服务疑惑求解

2022-02-16 15:01:44 +08:00
 love2075904

长期以来公司架构是基于 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(){
    	// 异步监听
    }
}


2699 次点击
所在节点    Java
14 条回复
love2075904
2022-02-16 15:28:46 +08:00
目前针对第一个问题,有一个解决方案是模仿 xxl-job 这种,让模块 B 和模块 C 向模块 A 进行注册,然后通过 http 调用执行
sky857412
2022-02-16 17:55:51 +08:00
第一个问题,应该将 MessageSender 的实现类都放到一个微服务中,controller 调用这个微服务暴露的接口,具体是调用哪个实现类,在这个暴露的接口中处理
第二个,应该无法没办法实现同步事件
wolfie
2022-02-16 18:11:05 +08:00
@FeignClient
public interface EmailMessageSender extends MessageSender {}
psydonki
2022-02-17 01:10:56 +08:00
我理解使用 MQ 作为中间件可以很好的解决这个问题。
Sender 只负责向 MQ 发送消息,同时在 Header 里面注明消息的类型:

```json
{
"type": "email"
}
```

然后根据 header 中的类型,将消息分流到不同的 queue 。
模块 B/C 分别是不同的微服务,监听不同的 queue 就好。
love2075904
2022-02-17 09:19:06 +08:00
@sky857412 之前有过这种想法,但是整体看来是不可能的,MessageSender 只是伪代码,实际业务中是不同的业务模块处理代码。
love2075904
2022-02-17 09:20:45 +08:00
@wolfie 这种就写死了,但是实际上,模块 A 根本不需要知道有哪些实现,这个时候完全可以出现一个模块 D 来实现一个新的 MessageSender
love2075904
2022-02-17 09:21:43 +08:00
@psydonki MQ 确实是一个思路,感谢!
wolfie
2022-02-17 09:44:09 +08:00
@love2075904 #6
这个就是放在公共依赖里的 FeignClient 声明。
你还是可以根据 MessageSender 类型获取所有的 bean 。
kowgarnett
2022-02-17 10:41:21 +08:00
我司的做法是抽出来了一个单独的微服务负责所有的 send message ,需要同步的用 REST call ,不需要的用 Kafka event 处理
freeup
2022-02-17 16:55:40 +08:00
对于拆分单体项目成微服务的我说几个我经历过得坑
1.方法间调用问题,原来都是直接调用,拆分出去后就是远程调用,所以相关代码都得改造
2.查询。。查询是最麻烦的,如果都在同一个库都还好,如果拆分时拆分了库。。那么以前的查询要么直接跨库联查,要么单独查
3.公共部分需要单独打包,也就是一些公共的依赖需要单独新建项目进行开发然后整合,各个服务进行依赖
4.事务问题,我们用 seata 解决分布式事务问题
其他的 都是一些业务上的问题了 我遇到过的就这几个问题比较麻烦
love2075904
2022-02-18 13:40:57 +08:00
@wolfie 我大概了解您的意思了,感谢。
love2075904
2022-02-18 13:42:12 +08:00
@kowgarnett 这也是一个方案,不过目前这样改动影响比较大,这样一来这个单独的微服务可能就不够纯粹,会去调用其他的业务模块代码了。
love2075904
2022-02-18 13:42:47 +08:00
@freeup 确实,这几个坑我们肯定也要踩。
kowgarnett
2022-02-22 02:50:26 +08:00
@love2075904 那就看你们对于微服务的业务范围的定义了,这个都是要讨论看 trade-off 找平衡的

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/834248

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX