消息队列将我们系统从同步调用改成了异步调用,将我们的系统进行解耦,但随之也带来一个问题,那便是如何保证消息使命必达。
举个例子,在电商系统中,用户下单后,我们通常是使用异步消息去通知购物车系统清空对应的购物车物品的。如果我们不能够保证消息使命必达,那么就意味着有些用户下完单之后,他的购物车还有原来这些物品,造成不好的体验。所以,一个可用的消息队列,必须是可靠,那么如何去保证消息生产到消费的过程中,是可靠的呢。
一般来说,消息队列队列有三个重要步骤,分别是生产-存储-消费。这一点很多同学都会没有注意到,甚至很多人调用消息队列的时候都会经常忘记去处理异常。在通常情况下,生产消息都是可以成功的,但是,在一些极端的条件下,例如网络波动,或者机器宕机,往往会造成消息入队失败,这就要求我们需要处理对应的异常。在消息入队失败的时候,进行重试或者直接回滚相应的事务,并且告诉前端处理失败。
其次,则是消息的存储阶段。虽然我们的消息队列已经经历了非常多场景的考验,但是在生产环境中,应用的重启也是常态。为了避免应用重启带了的数据丢失,我们最好是将数据进行落盘,即便是我们重启应用,也不会造成数据丢失。其次,硬件的损坏不可避免,服务器也是有使用寿命的,也是有可能遭遇到意外,如果我们的数据只存在一台机器上,一旦机器损坏,就有可能会丢失数据,所以,常见的做法是每条消息都会至少在两台机器上写成功才会返回成功。为什么是两台机器呢,因为从概率学的角度上来说,同一个集群在不同机房的两台机器,同时遇到故障的概率几乎为0。
最后则是在消息的消费阶段,很多同学有非常不好的编写代码习惯,就是不处理异常,无论代码执行的结果如何,都会告诉消息队列消息消费成功,这是非常不好的。我们就曾经遇到这样一个例子,每次用户成交之后,我们都会回调给商家,通知商家数据变更,可以来拉取数据。这只是一个非常简单的HTTP GET请求,按道理来说,是很难失败的。但是,有些商家的开发能力差,服务器经常宕机,刚好这个商家今天就成交这一单。因为没有处理HTTP的异常,消息被判断为消费成功,就会造成商家的数据错误。
如果你以为只要做好重试就行了,那就太天真了。在现代分布式系统中,有一种特殊情况特别需要程序员注意的,那就是对于超时的处理,在程序异常中,很多异常我们都可以认为下游系统处理失败了,唯有超时异常,我们很难去判断下游系统是否已经处理成功。这就需要我们对消息队列进行设计,尽量保证我们的系统是幂等的,从而保证消息不会因为重复消费而带来新的问题。