前言
回答这个问题,首先别听到重复消息这个问题就一无所知。先大概说一说可能会有哪些重复消费的问题。
首先,比如
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
。如果消费过了,那就别处理了,保证别重复处理相同的消息即可。 - 比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。