开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 5 天,点击查看活动详情
前言
回答这个问题,首先别听到重复消息这个问题就一无所知。先大概说一说可能会有哪些重复消费的问题。
首先,比如
RabbitMQ、RocketMQ、Kafka,都有可能会出现消息重复消费的问题。因为这问题通常不是
MQ自己保证的,是由开发来保证的。挑一个Kafka来举个例子,说说怎么重复消费。
Kafka 实际上有个 offset 的概念,就是每个消息写进去,都有一个 offset,代表消息的序号。
然后 consumer 消费了数据之后,每隔一段时间(定时定期),会把自己消费过的消息的 offset提交一下,表示:我已经消费过了,下次我要是重启或宕机的,就让我继续从上次消费到的 offset来继续消费吧
但是凡事总有意外,比如我们之前生产经常遇到的,如果着急直接 kill 进程了再重启。这会导致 consumer有些消息处理了,但是没来得及提交 offset。重启之后,少数消息会再次消费一次。
重复消费的出现
当前假设场景:
数据 1/2/3 依次进入 Kafka,Kafka 会给这三条数据每条分配一个 offset,代表这条数据的序号。
假设分配的 offset依次是 152/153/154。消费者从 Kafka 去消费的时候,也是按照这个顺序去消费。假如当消费者消费了 offset=153 的这条数据,刚准备去提交offset到 Zookeeper,此时消费者进程被重启了。
那么此时消费过的数据 1/2 的offset并没有提交,Kafka 也就不知道你已经消费了 offset=153 这条数据。那么重启之后,消费者会让 Kafka上次消费到的地方后面的数据继续传递过来。
由于之前的 offset 没有提交成功,那么数据 1/2 会再次传过来,如果此时消费者没有去重的话,那么就会导致重复消费。
新版的 Kafka 已经将
offset的存储从Zookeeper转移至Kafka brokers,并使用内部位移主题__consumer_offsets进行存储。
如果消费者是拿一条数据就往数据库里写一条,会导致把数据 1/2 在数据库里插入了 2 次,那么数据就出错啦。
其实重复消费不可怕,可怕的是没考虑到重复消费之后,怎么保证幂等性。
消息消费的幂等性
幂等性,通俗点说,就一个数据,或者一个请求,重复来多次,确保对应的数据是不会改变的,不能出错。
举个例子。
假设当前有个系统,消费一条消息就往数据库里插入一条数据,如果一个消息重复两次,就相当于插入了两条,数据就错了。
但是你要是消费到第二次的时候,自己判断一下是否已经消费过了,若是就直接扔了,这样就保留了一条数据,从而保证了数据的正确性。
一条数据重复出现两次,数据库里就只有一条数据,这就保证了系统的幂等性。
如何保证消息队列消费的幂等性
还得结合业务来思考,下面有几个处理思路:
-
比如拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,
update一下 -
比如是写
Redis,也不存在需要保证,反正每次都是set,天然幂等性。 -
比如不是上面两个场景,做得稍微复杂一点
需要让生产者发送每条数据的时候,里面加一个全局唯一的
id,类似订单id之类,然后消费到了之后,先根据这个id去Redis里查一下是否消费过,如果没有消费过,就处理,然后这个id写Redis。如果消费过了,那就别处理了,保证别重复处理相同的消息即可。 -
比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。