消息队列(Message Queue),简称为MQ,是一种跨进程的通信机制,用于上下游传递消息。常见消息队列中间件如:Kafka、ActiveMQ、RabbitMQ、RocketMQ等。
消息队列引入对系统的优势
1. 解耦
在未使用消息队列的系统中,系统间耦合性太强。如下图所示的业务场景,系统A在代码中直接调用系统B和系统C的代码,如果将来D系统接入或者B系统取消,系统A还需要修改代码,造成系统风险。
在这个场景中,A系统与其它的系统严重耦合,A系统要考虑各个下游系统如果挂掉的话的失败重试或兜底策略。
在使用消息队列后,将下游需要的消息push到消息列队中,需要消息的系统自己从消息队列中订阅;如果某个系统不需要这条数据了,就取消对 MQ 消息的订阅即可,从而系统A不需要做任何修改,也不需要考虑下游消费失败的情况。
通过引入消息队列的Pub/Sub发布订阅消息,A系统就与其它系统彻底解耦。这样也解决了大系统中多部门或者多人协作的职责分离问题,减少事故的发生。
2. 异步
在未使用消息队列的系统中,一些非必要的业务逻辑以同步的方式运行,耗费大量时间。
如下图所示的业务场景,A 系统接收一个请求,自身运算话费30ms,还需要在BCD进行运算(均需要100ms)。最终请求总延时是 330ms,如果A系统是网关,接收到请求是toc的用户请求,更高的延时将导致更差的用户体验。
一般互联网类的企业,对于用户直接的操作,一般要求是每个请求都必须在 200 ms 以内完成,对用户几乎是无感知的。
在使用消息队列后,将系统A的消息写入消息队列,非必要的业务逻辑BCD以异步的方式运行,这样总耗时就降低到30ms,提升了10倍的响应速度。
使用消息队列进行异步优化的时候要熟悉业务场景,并不是所有业务场景都可以用消息队列进行异步优化。
3. 削峰
在未使用消息队列的系统中,系统面对突发大流量会导致系统崩溃。如下图所示的业务场景,当突发并发大流量的时候,所有的请求直接怼到数据库,造成数据库连接异常。
在未使用消息队列且同时没有弹性伸缩的系统中,如果突发大流量,即使下游不是数据库,也会因为消费能力不足导致请求大量超时,系统宕机最终导致分布式系统的雪崩效应。
在使用消息队列后,系统A按照数据库(或下游系统)能处理的并发量,从消息队列中慢慢拉取消息。在大多数生产系统的业务场景中,短暂的高峰期的消息积压是允许的。这样就可以用有限的机器资源承载高并发请求。
如果是下游系统处理能力有限,能增加弹性扩容的基础设施能力,那当然是最好的,但是弹性扩容的响应速度有限,如果不能应对突发的流量高峰的话,还是推荐使用消息队列进行削峰操作(或者可以的话,使用降级熔断)。
消息队列引入对系统的劣势
虽然消息队列有上面三种优势,但是并不是盲目使用的。
系统可用性降低
系统每增加一个组件,必然导致可用性降低。毕竟没有一个组件可以保证100%可用性,因此还需要在消息队列高可用方面花费投入。
系统复杂性增加
在使用消息队列后,会增加很多方面的问题,比如如何保证消息不被重复消费、如何保证消息可靠传输、如何保证数据一致性问题和如何解决海量消息的积压故障。因此,需要考虑的东西更多,系统复杂性增大。
但上面出现的问题,都是有比较成熟的解决方案的,之后博客会逐个讲解。
什么时候不能使用消息队列
最后再讲下,什么时候不能使用消息队列。
上游请求到来之后,系统A调用系统B并需要知道B的执行结果。这种业务场景下通常不能使用消息队列,而使用RPC调用。
这种业务场景下使用消息队列,会导致系统A与系统B的信息割裂。
技术选型
原本想列出具体消息队列的优劣势对比,但由于版本不断更迭,很快这块内容会失去意义,所以只讲讲选型的原则。首先是要基于团队成员的技术栈以及部门公司的技术栈来进行选择,其次是根据业务场景:由于Kafka在大数据业务中有着无可争议的优势,所以如果在线业务只是需要数据流转,Kafka完全可以同时兼顾在线业务和大数据业务,保证技术栈单一,便于维护;如果需要复杂的消息队列功能,可以根据版本对应的功能,从RabbitMQ和RocketMQ做选型。
注:
- ActiveMQ的社区活跃已经大不如前,而开源项目RabbitMQ的社区依旧活跃。所以新项目不需要考虑前者。
- RocketMQ使用Java开发,RabbitMQ使用Erlang开发。前者对于广大Java后端更为友好些,毕竟方便深入源码。同时作为阿里的开源项目,一段时间内的技术可靠性还是有保证的,如果阿里放弃维护,也会有很多java社区贡献者来继续完善;后者的社区活跃与技术成熟度都要比前者高,且有大量互联网公司的成功案例,所以两个各有利弊,选型的时候需要仔细思考。
总结
作为后端开发,一定要熟悉消息队列的优缺点,并针对引入消息队列可能引发的系统问题作出相应的方案设计,保证系统高可用、高可靠的运行,以及保证数据的一致性。