消息队列的作用
消息队列是分布式系统中被广泛使用的中间件技术,它主要是用来提升分布式系统的性能和可靠性,解决系统间通信的复杂性。
主要有以下作用:
- 服务解耦
- 削峰填谷-流量控制
- 异步化处理
- 最终一致性
- 延迟队列
服务解耦
一个订单系统,当一个订单创建时。
1.支付系统需要发起支付流程
2.风控系统需要校验订单的合法性
3.客服系统需要发送消息给客户
4.经营分析系统需要更新统计数据
5…
这些下游的订单系统都需要实时获取最新的订单数据,随着业务的不断扩展,下游系统的不断增加,不断变化,负责订单系统的开发团队就不得不花费很大的精力来应对这种不断增加的变化,不断的修改和调试与下游系统交互的接口,任何一个下游系统的变更,都需要订单模块重新进行的上线。
引入消息队列来解决类似的系统耦合过于紧密的问题,当订单发生变更时,订单系统将变更的订单消息发送到消息队列中topic,所有的下游系统都订阅topic,这样每个子系统都可以获取到最新的订单信息。
无论增加、减少下游系统需要如何变化时,订单服务都不需要重新上线,实现了订单系统和下游服务之间的解耦。
削峰填谷
当实现一个秒杀系统时,我们会面临一个问题,如何避免大量的请求压垮我们的秒杀系统。
一个健壮的程序具有自我保护能力,也就是说,它可以在海量的请求下,能在服务自身能力范围内处理尽可能多的请求,拒绝处理不了的请求并保障服务自身运行正常。
基于消息队列,将网关服务和后端服务隔离起来,来实现流量控制和保护后端服务的目的。
加入消息队列后,请求流程变为
- 网关在收到请求时,将请求丢到消息队列中;
- 后端服务从消息队列中获取APP请求,完成后续处理过程,然后返回结果。
秒杀系统开始后,当短时间内有大量的请求达到网关时,不会直接冲击后端服务,而是先堆积在消息队列中,后端服务按照自己的最大处理能力,从消息队列中消费请求并返回响应。
对于超时的请求可以直接丢弃,APP将超时无响应的请求处理为秒杀失败,开发团队还可以随时增加秒杀服务的实例数量来动态的扩容,而不用对系统的其他部分做任何修改。
这种设计的优点是,能根据下游的实际处理能力自动调节流量,达到削峰填谷的目的,但是这样同样会带来其他的代价:
- 增加了系统的调用链环节,导致总体请求的响应时间延长。
- 上下游系统都要将同步调用改成异步消息,增加系统复杂度。
其他更简单一点的流量控制方案:如果我们可以预估出系统的处理能力,就可以用消息队列实现一个令牌桶,进行流量控制。
原理是:单位时间内只生产固定数量的令牌放到桶里面,后端服务在处理请求之前先从令牌桶里面获取一个令牌,如果获取不到则拒绝请求,这样就能保证在单位时间内,能处理的请求不会超过生成令牌的数量,起到流量控制的作用。
异步化处理
当我们设计一个秒杀系统时,肯定要考虑的是如何利用当前有限的资源去尽可能的处理更多的请求,比如一个秒杀系统分为以下几步。
- 风控控制
- 库存锁定
- 订单生成
- 消息通知
- 更新经营数据
如果不进行任何优化,正常的流程是先进行风控控制,在锁定库存,依次调用上述五个流程,然后将结果返回。
但是对于以上五个步骤,能否决定是否秒杀成功,取决风控控制和库存锁定二个步骤,只要用户的秒杀请求通过了风控控制和库存锁定,就可以给用户返回秒杀成功了,对于后续的步骤,可以等秒杀结束后再处理,把大量的系统资源用于处理秒杀请求。
在这个场景中,我们引入消息队列实现了服务的异步处理,这样的好处有 - 更快的返回处理结果
- 减少等待,实现了步骤之间的并发,提升系统的性能。
- 控制有限的资源 处理核心的流程。
最终一致性
跨服务的事务难以通过本地事务保证事务的一致性。
通过消息队列的可靠投递(如事务消息,重试机制),实现消息的最终一致性。
延迟队列
延迟队列的定义:用来存放需要在指定时间之后被处理的元素的队列,队列中的每个元素都指定了一个延迟时间,该时间表示元素应该在何时被取出处理,在延迟时间到达之前,元素会保持在队列中,不会被消费者立即消费。
当我们处理一个订单系统时,希望在订单生成成功后多长时间范围内,需要用户完成付款。我们可以通过消息队列的消息超时时间 + 死信队列实现延迟处理的功能,比如基于 rabbitmq实现延迟队列。