关于pulsar broker异常时如何保证客户端不漏发消息的一些思考

       Pulsar是可靠的消息中间件产品,“可靠”意味着保证消息几乎不丢,除非发生了所有消息副本都丢失或者不可恢复的灾难性情况消息数据才会丢失(当然这种事情发生的概率是极小的)。但是,这种对消息的“几乎不丢”只是pulsar通过多副本机制对已经生产成功的消息在可靠性上的一种承诺,并不能保证消息在pulsar的上下游处理过程中不会发生丢失,尤其是上游,这里我们想结合pulsar本身的高可用机制来讨论下数据从上游系统到pulsar之间可能发生的消息漏发最终导致业务数据丢失的问题。

       首先,客户端sdk生产消息时使用了pendingMessageQueue+ack的机制,简而言之就是业务系统使用客户端sdk提供的send接口发送消息后,sdk会先将消息放入内部的一个缓存队列,即pendingMessageQueue,然后在通过网络发送至broker。正常情况下broker接收到消息并执行存储流程,成功后向客户端返回ack,ack中带有messageId等信息,客户端收到ack之后从pendingMessageQueue中取出确认生产成功的消息并释放掉,这条消息就算生产成功了。PendingMessageQueue专门用于缓存已经向broker发送但还未收到ack的消息,如果一直未收到ack,超出生产者配置的sendTimeout,则send方法则会抛出异常表示发送失败并且也会将消息从pendingMessageQueue中释放掉,其间如果发生了链路切换,则生产者会重新发送pendingMessageQueue中的所有消息。

       在上文描述的各种场景里,最容易出现漏发消息的场景就是客户端迟迟未收到ack并发送消息超时,此时如果客户端程序捕获到异常后不能妥善处理未收到ack的消息,就有可能造成消息漏发。那该如何妥善处理发送超时的消息呢?这里给出两种处理办法,一种通过编码手动重发,如果重发依然失败,就将消息重新发送至其它如数据库的存储系统,生产者之后进入下一条消息的处理周期。那些被发送到其它系统的消息由专门的任务单独处理,比如重发,但这会消息之间的有序性;另一种是生产者不断重试发送当前消息,当前消息不发送成功就不发送后面的消息,这样做不会影响消息之间的顺序性。

       那什么情况下生产者容易发生发送超时呢?这里直接给出一种最常见的场景,即broker异常,hang死或者挂掉。在这种场景中,broker往往无法像正常stop那样去立刻关闭同zookeeper保持的会话状态,zookeeper也无法及时知晓broker的不可用,因此topic和相关broker的映射关系还暂时存在,由此造成的结果就是客户端虽然能够通过链路异常直接感知到broker的异常并不断地进行topic discovery和重连broker,但连的依然是异常的broker,反复仍然连接失败,空耗时间。这种现象是pulsar计算层本身的高可用机制造成的,也就是topic的ownership在发生异常转移时必然会有一段儿真空期,就是从broker异常开始到zookeeper发现broker异常的这段时间,约为broker持有的zookeeper客户端的会话超时时间。在此期间,topic的ownership依然在异常的broker上,直到zookeeper感知到其异常。

       综上,可以得到以下一些结论:
       1. 如果想要broker发生异常挂掉或不可用时客户端快速恢复,可以适当缩短zookeeper客户端的会话超时时间,但也不能太短,太短的话broker可能会对zookeeper状态做出误判引发不稳定;
       2. 如果想要在broker异常时客户端不漏发消息,一方面可以将sendTimeout设置大于zookeeperSessionTimeout,这样客户端可以在切换broker后重新发送未收到ack的消息;另一方面可以通过编码实现一些重试和兜底机制来妥善处理发送超时的消息;

posted @ 2026-01-14 15:08  甜甜的翠西  阅读(1)  评论(0)    收藏  举报