这篇文章主要围绕两个话题来叙述:消息队列是什么?主要解决什么问题。
消息队列是最古老的中间件之一,从系统之间有通信需求开始,就自然产生了消息队列。但是给消息队列下一个准确的定义却不太容易。我们知道,消息队列的主要功能就是收发消息,但是它的作用不仅仅只是解决应用之间的通信问题这么简单。
生活中也存在很多例子可以用来解释消息队列的概念。一个常见的例子是快餐餐厅的点餐系统。让我们来看看这个例子:
在一个繁忙的快餐餐厅,通常有多个工作人员负责不同的任务,包括点餐、烹饪食物、包装和交付食物给客户。最开始的时候,点餐员会将顾客的订单直接传达给厨师,然后等待食物烹饪完成,然后再将其交付给客户。这种方式存在一些问题:
等待时间:点餐员可能需要等待一段时间,直到厨师准备好食物。这导致了服务速度较慢。
同步问题:如果厨师繁忙,点餐员必须等待,不能处理新的订单。
故障容忍性:如果点餐员或厨师出现问题,整个点餐过程可能会中断。
为了解决这些问题,餐厅引入了消息队列系统,还是上面的例子。现在的订单处理过程如下:
顾客在点餐柜台上点餐并支付,点餐员将订单信息输入到点餐系统,但订单不再直接传递给厨师。
订单信息被放入订单队列中,等待处理。厨师从队列中获取订单并开始烹饪。
同时,点餐员可以接受更多订单,将它们加入队列。这意味着点餐员不必等待烹饪完成,而是可以快速处理更多订单。
一旦订单完成烹饪,它被放回队列,等待交付。送餐员从队列中获取订单并将其交付给客户。
消息队列系统的作用类似于上述的例子,具有类似的优势:
解耦合:点餐员和厨师不再需要直接通信,他们通过队列进行互动,各自独立工作。
流量调节:点餐员可以接受更多订单,而不会影响食物的烹饪和交付速度。订单等待在队列中,直到厨师和送餐员准备好接受它们。
可伸缩性:餐厅可以雇佣更多的点餐员、厨师和送餐员来应对繁忙时段,而不必更改整个点餐系统。
容错性:即使点餐员、厨师或送餐员出现问题,队列中的订单仍然安全,等待下一个可用的处理程序。
在一个典型的秒杀系统中,需要解决如何有效处理大量请求的问题。通常,这个过程可以分为五个主要步骤:风险控制、库存锁定、生成订单、短信通知和更新统计数据。其中,风险控制和库存锁定是决定秒杀成功的关键步骤,而其他步骤则可以在稍后异步处理。
原本的处理流程是,应用将请求发送到网关,然后依次调用这五个步骤,最后将结果返回给应用。但在实际场景中,只有风险控制和库存锁定两个步骤决定了秒杀的结果,其他步骤并不需要即时完成。
因此,一种优化方法是,当服务器完成风险控制和库存锁定后,即可立即响应用户请求,同时将请求的相关数据放入消息队列中,以便后续的步骤异步处理。
在这个场景中,采用消息队列进行异步处理带来了多个好处:
更快的响应速度:用户能够迅速得到秒杀结果,无需等待后续步骤的完成。
并发处理:通过异步执行,系统能够在秒杀期间更高效地利用服务器资源,处理更多的秒杀请求。
降低系统压力:在秒杀结束后,系统可以有序地处理生成订单、短信通知和更新统计数据等步骤,减少了系统的高峰负载。
继续讨论秒杀系统的设计,我们已经引入了消息队列来实现异步处理,但我们还需要解决一个关键问题:如何防止过多的请求压垮系统?
一个健壮的程序应该具备自我保护的能力,即在面对大规模请求时,它能够在自身承受范围内处理尽可能多的请求,同时拒绝无法处理的请求,以确保系统正常运行。然而,很多现实中的程序并不总是如此"健壮",而直接拒绝请求会影响用户体验。
因此,我们需要设计一个强大的架构来保护后端服务。我们的设计思路是通过消息队列来隔离网关和后端服务,以实现流量控制和后端服务的保护。
引入消息队列后,整个秒杀流程如下:
网关接收到请求后,将请求放入请求消息队列。
后端服务从请求消息队列中获取APP请求,执行后续的秒杀处理流程,然后返回结果。
当秒杀活动开始时,如果在短时间内涌入大量秒杀请求,这些请求不会直接冲击到后端秒杀服务。相反,它们会在消息队列中排队等待处理,后端服务会按照其最大处理能力从消息队列中获取请求并进行处理。
对于超时的请求,可以直接丢弃,APP可以将没有响应的请求视为秒杀失败。此外,运维人员可以随时增加后端秒杀服务的实例数量以进行水平扩容,而无需对系统的其他部分进行任何更改。
这种设计的优点在于它可以根据下游处理能力自动调节流量,实现了“削峰填谷”的效果。然而,这种方法也有一些代价:
增加了系统调用链的环节,从而导致总体响应时间延长。
需要将同步调用改为异步消息传递,增加了系统的复杂性。
消息队列的另一个重要作用是实现系统之间的解耦。让我们再以电商领域为例,来说明解耦的重要性和作用。
在电商系统中,订单数据是核心信息,每当创建一个新订单时,涉及到多个下游系统的操作:
支付系统需要触发支付过程。
风险控制系统需要审核订单的合法性。
客服系统需要通知用户,通常通过短信发送信息。
经营分析系统需要更新统计数据。
随着业务的扩大,这些下游系统会不断增加和变化,而且每个系统通常只需要订单数据的某个子集。维护订单服务的开发团队不得不不断投入大量精力来适应这些下游系统的不断增长和变化。每次下游系统的接口发生变更,都要求订单模块进行修改和重新上线。对于电商这类核心服务来说,这种紧密的耦合几乎是不可接受的。
为了解决这一问题,许多电商选择引入消息队列。通过引入消息队列,订单服务只需要在订单发生变化时向一个名为"Order"的主题发布一条消息,而所有下游系统都会订阅这个主题。这使得每个下游系统都可以及时获取到完整的订单数据,而不论下游系统的增加、减少或者具体需求如何变化。
这种设计带来的好处是,无论下游系统的变动如何,订单服务无需做出任何修改,从而实现了订单服务与下游服务之间的解耦。这种解耦设计使系统更加灵活和可维护,减少了对核心服务的依赖和干预,提高了系统的稳定性和可扩展性。
上面举例了消息队列最常被使用的三种场景:异步处理、流量限制、服务解耦,消息队列的使用范围肯定不仅仅局限于这些,但这里就不作多的列举了。
简单的说,我们在单体应用里面需要用队列解决的问题,在分布式系统中大多数都可以用消息队列来解决。
同时我们也应该明白,引入消息队列可能造成的一些问题和局限性:
引入消息队列带来的延迟问题
增加了系统的复杂度
可能产生数据不一致的问题