消息队列解决什么问题
- 异步处理
- 流量控制
- 服务解耦
1.异步处理
处理一个秒杀请求包含了很多步骤,例如:
- 风险控制;
- 库存锁定;
- 生成订单;
- 短信通知;
- 更新统计数据
能否决定秒杀成功,实际上只有风险控制和库存锁定这 2 个步骤确定后,就可以给用户返回秒杀结果了,然后把请求的数据放入消息队列中,由消息队列异步地进行后续的操作。
处理一个秒杀请求,从 5 个步骤减少为 2 个步骤,不仅响应速度更快,并且在秒杀期间,可以把大量的服务器资源用来处理秒杀请求。
秒杀结束后再把资源用于处理后面的步骤,充分利用有限的服务器资源处理更多的秒杀请求。
消息队列被用于实现服务的异步处理。这样做的好处是:
- 可以更快地返回结果;
- 减少等待,自然实现了步骤之间的并发,提升系统总体的性能。
2.流量控制
如何避免过多的请求压垮我们的秒杀系统?
设计健壮的程序有自我保护的能力。在海量的请求下,能在自身能力范围内尽可能多地处理请求,拒绝处理不了的请求并且保证自身运行正常。现实中很多程序并没有那么“健壮”,而直接拒绝请求返回错误对于用户来说是不怎么好的体验。
因此,我们需要设计一套足够健壮的架构来将后端的服务保护起来。
我们的设计思路是:使用消息队列隔离网关和后端服务,以达到流量控制和保护后端服务的目的。
加入消息队列后,整个秒杀流程变为:
- 网关在收到请求后,将请求放入请求消息队列;
- 后端服务从请求消息队列中获取 APP 请求,完成后续秒杀处理过程,然后返回结果。
秒杀开始后,短时间内大量的秒杀请求到达网关时,不会直接冲击到后端的秒杀服务,而是先堆积在消息队列中,后端服务按照自己的最大处理能力,从消息队列中消费请求进行处理。
超时的请求可直接丢弃,APP 将超时无响应的请求处理为秒杀失败即可。还可以随时增加秒杀服务的实例数量进行水平扩容,而不用对系统的其他部分做任何更改。
优点:能根据下游的处理能力自动调节流量,达到“削峰填谷”的作用
同时有代价:
- 增加了系统调用链环节,导致总体的响应时延变长
- 上下游系统都要将同步调用改为异步消息,增加了系统的复杂度
更简单一点儿的流量控制方法:用消息队列实现一个令牌桶(前提能预估出秒杀服务的处理能力)
令牌桶控制流量的原理:
单位时间内只发放固定数量的令牌到令牌桶中,规定服务在处理请求之前必须先从令牌桶中拿出一个令牌,如果令牌桶中没有令牌,则拒绝请求。这样就保证单位时间内,能处理的请求不超过发放令牌的数量,起到了流量控制的作用。
实现的方式也很简单,不需要破坏原有的调用链,只要网关在处理 APP 请求时增加一个获取令牌的逻辑
令牌桶可以简单地用一个有固定容量的消息队列加一个“令牌发生器”来实现:令牌发生器按照预估的处理能力,匀速生产令牌并放入令牌队列(如果队列满了则丢弃令牌),网关在收到请求时去令牌队列消费一个令牌,获取到令牌则继续调用后端秒杀服务,如果获取不到令牌则直接返回秒杀失败。
3.服务解耦
随着业务不断发展,订单下游系统不断的增加,不断变化,并且每个系统可能只需要订单数据的一个子集,订单服务需要应对不断增加变化的下游系统,不停地修改调试订单系统与这些下游系统的接口。任何一个下游系统接口变更,都需要订单模块重新进行一次上线,对于一个电商的核心服务来说,这几乎是不可接受的。
所有的电商都选择用消息队列来解决类似的系统耦合过于紧密的问题。引入消息队列后,订单服务在订单变化时发送一条消息到消息队列的一个主题 Order 中,所有下游系统都订阅主题 Order,这样每个下游系统都可以获得一份实时完整的订单数据。
无论增加、减少下游系统或是下游系统需求如何变化,订单服务都无需做任何更改,实现了订单服务与下游服务的解耦。
小结
以上就是消息队列最常被使用的三种场景:异步处理、流量控制和服务解耦。当然,消息队列的适用范围不仅仅局限于这些场景,还有包括:
- 作为发布 / 订阅系统实现一个微服务级系统间的观察者模式;
- 连接流计算任务和数据;
- 用于将消息广播给大量接收者。
简单的说,我们在单体应用里面需要用队列解决的问题,在分布式系统中大多都可以用消息队列来解决。
同时我们也要认识到,消息队列也有它自身的一些问题和局限性,包括:
- 引入消息队列带来的延迟问题;
- 增加了系统的复杂度;
- 可能产生数据不一致的问题。
所以我们说没有最好的架构,只有最适合的架构,根据目标业务的特点和自身条件选择合适的架构,才是体现一个架构师功力的地方。