当前位置: 首页>后端>正文

28. 业务上需要顺序消费,怎么保证时序性

消息传输和消费的时序性,是消息队列应用中一个非常重要的问题
很多业务场景都需要考虑消息投递的时序性
例如,电商中的订单状态流转,数据库的binlog 分发

消息顺序消费有哪些困难

消息队列中的队列是一个有序的数据结构,消息传递是顺序的
在实际开发中,特别是分布式场景下,消息的有序性是很难保证的

分布式的时钟问题

消息的生产者,消费者和队列存储,可能分布在不同的机器上,不同的机器使用各自的本地时钟
由于服务器存在时钟偏斜等问题,本地时间会出现不一致
不能用消息发送和到达的时间戳作为时序判断标准
分布式系统下缺乏全局时钟,使得绝对的时间顺序实现起来更加困难

消息发送端和消费端的集群

生产者和消费者都是集群部署,通过 ProducerGroup 和 ConsumerGroup 的方式来运行
消息发送端发送时的时序不能用来作为消息发送的有序判断
消费端可能存在多个实例,即使队列内部是有序的
由于存在消息的分发过程,不同消费实例的顺序难以全局统一,也无法实现绝对的有序消费

消息重传等的影响

消息队列在传输消息时,可能会出现网络抖动导致的消息发送失败等
对这种场景的兼容,一般是通过进行合理地重传
消息的重传发生在什么时候是不可预知的,这也会导致消息传输出现乱序

网络及内部并发

如果单纯地依靠消息队列本身来保证,那么在跨实例的情况下
因为网络传输的不稳定会有先后顺序,以及内部消费的并发等,仍然无法实现绝对有序
保证消息绝对的有序,实现起来非常困难
除非在服务器内部,并且一个生产者对应一个消费者

解决消息队列的有序性有哪些手段呢?

消息传输的有序性和不同的消息队列,不同业务场景,以及技术方案的细节等都要关系
解决消息传输的有序性,需要依赖消息队列提供对应的方式

从消息队列自身的角度,可以分为全局有序和局部有序

  • 全局有序 —— 无法使用多分区进行性能的优化
  • 局部有序 —— 把业务消息分发到一个固定的分区,也就是单个队列内传输的方式

Kafka 顺序消息

Kafka 保证消息的 Partition 内的顺序
  • 单分区 —— 天然满足消息有序性
  • 多分区 —— 通过制定的分发策略,将同一类信息分发到同一个 Partintion 中

例如,电商系统中的订单流转信息,在写入 Kafka 时通过订单 ID 进行分发
保证同一个订单 ID 的消息都会被发送到同一个 Partition 中

比较特殊的情况——消息失败重发的场景

比如同一个订单下的消息1和2,如果1发送失败了,重发的时候可能会出现在2的后边,可以通过设置”max.in.flight.requests.per.connection“ 参数来解决

RocketMQ 顺序消息

RocketMQ 保证消息在同一个 Queue 中的顺序性,也就是满足队列的先进先出原则
如果把对应一个业务主键的消息都路由到同一个 Queue 中就可以实现消息的有序传输
并且 RocketMQ 额外支持 Tag 的方式
可以对业务消息做进一步的拆分,在消费时相对更加灵活

从业务角度保证顺序消费

消息消费的有序性,是一个业务场景的设计问题。可以在业务中进行规避,或者通过合理的设计方案来解决。

消息传输的有序性是否有必要

在你的业务中是否必须实现绝对的消息有序?或者是必须要有消息队列这样的技术手段?

比如在一个订单状态消息流转的业务场景中,
订单会有创建成功,待付款,已支付,已发货的状态,订单状态的更新需要保证有序性。
如果要实现的功能是根据发货的状态,进行物流通知用户的功能
因为这个状态是单调不可逆向的,可以只关注最后是否已经发货的状态。

业务中如何实现有序消费

  • 根据不同的业务场景,以发送端或者消费端时间戳为准
    比如在电商大促的秒杀场景中,如果要对秒杀的请求进行排队,可以使用秒杀提交时服务端的时间戳,在这个场景下,不需要保证绝对的有序。

  • 每次消息发送是生成唯一递增的ID
    在消费端进行消费时,缓存最大的序列ID,只消费超过当前最大的序列 ID 的消息
    可以保证每次只处理最新的数据,避免一些业务上的不一致问题。

  • 通过缓存时间戳的方式
    当生产者在发送消息时,添加一个时间戳,消费端在处理消息时,通过缓存时间戳的方式,判断消息产生的时间是否最新,如果不是则丢弃,否则执行下一步。

总结

消息的有序性可以分为时间上的有序和业务上的有序
绝对的时间有序实现起来时非常困难的
消息队列只是一个消息传输的解决方案
可以通过业务中不通的场景,进行合理的设计,实现业务上的有序性


https://www.xamrdz.com/backend/3kf1941664.html

相关文章: