消息队列的三大重点:幂等性、有序性、可靠性

Posted by 陈树义 on 2019-01-07

消息队列是互联网公司用得最多的消息中间件,基本上了一点规模的公司都会用上消息队列。但多引入了一个中间件,也就使得技术更加复杂了。对于消息队列来说,在引入之前你需要考虑清楚下面三个问题:

  • 如何保证消息的幂等性(消息重复)?
  • 如何保证消息的顺序性(消息有序)?
  • 如何保证消息的可靠性(消息丢失)?

消息幂等性

幂等性其实是一个数学与计算机概念,其意思是:

在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。

在消息队列场景下,消息幂等性的意思是:一条完全一样的消息,它消息一次和消费无数次的结果是一样的。即消息幂等性要解决的就是消息队列中的消息重复问题。

举个例子:我们发送短信时,生产者将要发送的消息放入消息队列中,消费者从队列取出消息在将消息发送给用户。

因为消息队列可能存在重复消息,所以如果我们没有对消息队列做幂等性处理,那么可能用户就会收到多条短信,对用户造成骚扰。

对于消息中间件的幂等性问题,一般通用的处理方案是给消息一个唯一的ID,每次做业务处理之前判断是否消费过。如果消费过,那么直接抛弃该消息,不进行业务处理。

消息顺序性

顺序性指的是在一些具体的业务中,前后的业务操作必须有顺序,否则会导致业务处理错误。例如在电商系统中,订单和支付通常是两个不同的业务逻辑,我们通常会将其分拆开来处理。

这两个业务逻辑之间存在非常清晰的依赖关系:需要先生成订单,然后才能支付订单。对于这种情况,我们就说订单消息和支付消息是有顺序性的。如果他们没有顺序,那么很可能在支付订单的时候,找不到订单信息,从而导致订单失败。所以我们需要保证订单消息在支付消息之前。

对于消息中间件的消息顺序性问题,一般通用的处理方案是保证局部的消息有序。 例如对于 Kafka 来说,我们会保证 Partition 区域的消息有序性。对于上面所说的订单消息、支付消息的例子,我们一般会将订单消息和支付消息里的用户ID作为key,将其分配到同一个 partition 中,这样它们就是有序的。

消息可靠性

消息可靠性其实说的就是消息丢失,我们如何去防止消息发生丢失,或者说消息丢失之后,我们应该如何补救?

对于 Kafka 来说,可能发生消息丢失的几个节点分别是:

  • 生产者丢失消息

生产者有一个参数request.required.acks,可以设置生产者的数据一致性级别。

当其值为 0 时,表示生产者不需要等待 broker 的回复,直接发送下一条消息。这种情况下如果 broker 宕机,而生产者还是一直发送消息,那么这些数据就会全部丢失。

当其值为1时,表示生产者需要等待 broker 的 ack 响应后,才发送下一条消息。在这种情况下,broker 接收到消息,写入内存后便会发送 ack 响应,但并不会立刻写入内存。这时候如果在写入磁盘前 broker 发生宕机,那么这条消息就丢失了。

当其值为 -1 时,表示生产者需要等待所有 broker(副本)写入内存后,再发送下一条消息。这种情况下,其实还是有可能发生宕机,因为数据还是没有写入磁盘,没有持久化。生产者接收到 ack 响应,只能表明数据写入所有主、备broker的内存中而已。不过对比起上一种,这种丢失数据的可能性降低了一些。

  • broker丢失消息

当生产者将消息发送到 broker 之后,其实只是将消息缓存到内存中,再由一个线程定时刷入磁盘。这时候如果 broker 宕机,那么这部分存在内存中的数据就丢失了。要杜绝这种情况的发生,我们可以设置log.flush.interval.messages参数为1,这样每有一条消息,broker 就会立刻将消息刷入磁盘,这样就不会发生消息丢失。但是这种方式非常消耗磁盘性能,如果这么做了,那么基本上 Kafka 就没有任何吞吐量优势了。 kafka 之所以能达到几十万甚至上百万靠的就是异步刷盘,同步刷盘之后写入消息的性能只有几千一秒了。

  • 消费者丢失消息

消费者从 broker 获取到消息后,offset 如果设置为自动提交,那么 offset 就会提交到 zk 中。此时如果 consumer 线程被 kill 掉,那么这条消息就丢失了。一般情况下,要保证不丢失消息,常见做法就是取消自动提交,改为手动提交。但这样的话会导致消息重复,但幸好消息重复可以通过幂等消除,所以也并不是什么大问题。

对于消息可靠性来说,如果要保证 100% 不丢消息,那么是要从生产者、消费者、kafka 服务器三个地方入手的。每个地方都需要做相应的配置,并且做好异常处理,这样才有可能保证消息不丢失。

总结

消息的幂等性、顺序性、可靠性可以说是消息中间件需要考虑的三个基本问题,在应用到具体系统之前都必须考虑清楚它们造成的影响,以及解决方案。

扩展资料