vlambda博客
学习文章列表

如何设计一个消息队列(进阶)

可靠投递

每当要发送消息传输等可能导致不可靠事情之前,都先将消息存下来,然后再发送。当失败或者说不知道超时的时候,消息还是待发送,启用一个定时任务不断轮询所有待发送消息。

对于各种不确定(超时、宕机、消息没有送达、送达后数据没有落地、数据落地后没有回复收到)。对于发送方来说就是消息没有送达

重推消息会面临消息重复。但是重复还能处理,丢失就不能找回了。作为一个成熟的消息队列,应该保证各个环节减少重复投递的可能性。不是所有的系统都要求最终一致性。如果一个论坛系统,一个重复的话题比丢失了一个发布更加无法让用户接受

消费确认

当broker把消息投递到消费者后,消费者可以立刻响应收到这个消息。但收到消息只是第一步,能不能处理不一定。可能因为消费能力的问题,系统的负荷不能处理这个消息;或者刚才状态机里提到的消息不是我想要接收的消息,主动要求重发。

把消息的送达和消息的处理分开,是真正实现了消息队列的本质解耦。所以,允许消费者主动进行消费确认是必要的。

对于消费者来说,正确ack没有特殊的,对于reject和error来说,需要特殊说明。

reject可以用滑动窗口或线程池来实现。error时要允许业务方主动ack error,并可以与broker约定下次投递时间

重复消息和顺序消息

重复消息是不能100%避免的,除非可以允许丢失。

顺序消息如果允许消息丢失,且发送方到服务到消费者都是单点单线程才能满足,所以绝对的顺序消息是不可能实现的

重复消息可以用两种通用的方法解决

版本号

下游业务维护一个版本号,每次只接收比自己版本号大的。

如果到来的数据是乱序的,每次就只接受比当前版本号大一的消息,并在一个周期内一直保持各个消息的版本号,来保证顺序

状态机

版本号的问题是发送方必须要求消息带业务版本号。下游要存储消息的版本号,且要严格保证顺序。

如果只是涉及状态的流转,可以使用状态机,下线状态只能接收上线,如果接收到错误的状态机。消费者只需要告诉生产者无法处理并要求重发就行。而且重发有一定次数限制,避免死循环。

中间件对于重复消息的处理

指定版本号和状态机都会使消费者的业务更加复杂,而且不能解决所有问题

可以在broker记录消息id,直到投递成功后清除。重复id到来不做处理,只要发送者在清楚周期内能感知到消息投递成功,就不会产生重复消息。

事务

消息队列应该满足事务的一致性

解决方案有两种

  1. 分布式事务

  2. 本地事务,本地落地,补偿发送

分布式事务成本太高,对于交易密集型或IO密集型,没有办法接受这么高的延迟

本地事务配置较复杂,绑架业务方,对于消息延迟高敏感的业务不适用

一个完整的消息队列应该定义清楚自己可以投递的消息类型,如事务型消息,本地非持久型消息,以及非可靠消息。对于不同的业务场景应做不同的选择。另外事务的使用应该尽量低成本、透明化。

性能相关

异步、同步