优点主要围绕3大核心来讲述:解耦,异步,削峰填谷。
个人理解单体系统引入消息队列是徒增复杂性,所以单体下,没有使用消息队列的必要,这里假设一个电商系统已经不再是单体,而是实现了服务拆分以及微服务化,各个服务之间独立部署,独立维护,一个购物下单请求业务流程如下:
简单描述下:用户下单后,要远程调用库存系统减商品库存,要远程调用物流系统新增物流信息,要调用第三方服务整合系统提供的短信服务来给用户发送购物成功的信息这里可以类比12306购票,提高用户购物体验,以上思路就是很标准的微服务体系下的各个服务间互相调用的业务逻辑,远程调用可以使用Spring Cloud的Fegin或者Dubbo。
问题一:此时如果需要引入一个积分系统,在用户下单购物成功后给用户增加相应积分(这里排除退货情况),那么下单业务流程图如下:
此时开发要做的就是在下单的代码上加上远程调用积分系统的逻辑。这里不妨设想下,随着业务的发展,产品再来点奇思妙想,购物下单操作后,又多了几个需要远程调用的系统,那么又得在原来的下单代码上加上相应的代码,这就不符合对扩展开放,对修改关闭的原则了,那么这时候要做的就是服务间的解耦,业务代码间不会强依赖,此时选择引入消息队列中间件,下单流程图变成如下结构:
如上图所示,引入了消息队列,每当购物下单成功后,订单系统都会将相应的订单数据作为消息发送到MQ中的队列里,然后积分,物流等系统再去取出MQ中的消息做相应的业务消费。这就间接的实现了发布/订阅模型,生产者发布消息,消费者订阅消息类型,然后消费消息。哪怕之后再多的系统需要消费购物下单成功后的数据,作为生产者的订单系统也不需要再改动代码,新增的系统自己订阅相应的消息队列即可,这就使用消息队列完成了服务间的解耦。
异步
然后继续参考以上的几个流程图,可以发现在引入积分系统后,如果没有引入MQ,在下单成功后,一次要调用积分,库存,物流,短信等服务,不算上网络等开销,整个请求至少也要花费1s的时间,而一般来说,一个请求响应维持在200ms对用户的体验来说是比较好的。然后再回顾下引入MQ后的业务流程图,由于服务间没有了强依赖,各个服务是通过MQ来做数据交流的,所以在下单成功后,系统其实就可以作出响应了,而不再需要等待积分,库存,物流,短信等服务远程调用逻辑处理完成后才返回。当然了,由于调用的服务业务逻辑之间是没有先后顺序要求的,那么也可以使用多线程来优化代码,但是要知道,哪怕是使用了多线程,也是要花至少400ms的(下订单的200ms+其他系统异步调用的200ms),而使用MQ,则只需花费200ms左右。
削峰填谷
假设这个电商系统只能支撑每秒2000的并发量,但是由于搞促销,现在每秒进来了5000的请求,如果让这5000的并发量一次性打到数据库中,无疑会导致数据库崩溃宕机,这时候通过MQ来做削峰,先让数据进入MQ,然后服务每秒从MQ中取出2000条消息处理,保证服务的稳定,然后等大促过后,并发量降下来了,假设此时每秒只有100的并发,但是由于之前累计的消息还没有被消费完,服务依旧还是每秒取出2000条消息消费,直到累积的消息被消费完,所以此时就做了个类似填谷的操作,将服务对请求的处理速度维持成了一个平缓的曲线。
缺点
个人见解,引入的任何中间件,都会增加系统的复杂程度,同时也会增加对开发人员的技术要求,增大开发成本,以下列出引入MQ的几个缺点:
- 系统都依赖MQ做数据交互,那么要是MQ宕机了怎么办,是不是要做高可用?
- 消息丢失了怎么办,怎么保证消息不丢失?
- 消息重复发送了怎么办?怎么保证消费的幂等性?
- 消息的消费如果要求是要有顺序的,怎么保证消息传递的顺序性?
- 每个系统都是取出MQ中的消息来做自己的业务消费,要是某个系统消费消息失败了,整个业务是不是要回滚,怎么保证消息消费的一致性?
- 既然要引入消息队列,那么市场上那么多的消息队列怎么做技术选型?哪种适合自己的系统?