背景
在消息队列系统中,生产者SDK在生产时通常存在两种类型的重试。
- 1.生产请求超时,SDK自动重试,此时重试请求的内容不变。
- 2.SDK生产请求向用户侧报错,用户可能尝试重新发送该消息内容。但对mq来说前后是不同的生产请求。
Pulsar中可以针对情况1进行消息去重,以避免SDK底层生产超时重试导致的生产重复。如果不开启消息去重则生产请求超时重试会导致消息重复存储,如下图:
如果开启消息去重则生产请求超时重试时只要有消息持久化成功后续的重试请求都不会导致消息持久化存储,如下图:
原理
Pulsar支持在Broker、Namespace、Topic级别开启消息去重功能。其中Broker级别开启开关为brokerDeduplicationEnabled默认为false。
Namespace级别开启可通过如下命令:
bin/pulsar-admin namespaces set-deduplication public/default --enable
Topic级别开启可通过如下命令:
bin/pulsar-admin namespaces set-deduplication public/default --disable
开启消息去重时,broker将每个producer最大的sequenceId存储在哈希表中。如果收到的生产请求中sequenceId小于该producer现有的sequenceId,则这条消息为重复的消息,不进行处理。Broker将每个topic的(producerName, sequenceId)定期snapshot在cursor中,当broker故障分区转移时,新broker将从snapshot重新恢复哈希表。但哈希表中producerName的最新sequenceId可能并不在snapshot中,因此broker还需要从topic ledger读取最新的N条Entry,并将entry中记录的(producerName, sequenceId)更新到哈希表中,以确保不会因为故障分区转移导致哈希表数据不完备。
- producerName:可由用户在SDK侧创建topic producer时设置,若未设置则server端在handleProducer方法里生成唯一性Name并返回给客户端
- sequenceId:Pulsar SDK为一次生产请求分配的唯一性Id
如源码所示,线程池周期性执行定时任务将该broker加载的topic的(producerName, sequenceId)进行snapshot,任务执行周期通过brokerDeduplicationSnapshotFrequencyInSeconds配置。