这个是MQ领域的基本问题,很多面试官也会问这样的问题,其实本质上还是问你使用消息队列如何保证幂等性的问题。
- 比如RabbitMQ、RocketMQ、Kafka都有可能出现消息重复消费的问题,因为者问题通常不是MQ自己保证的,是由我们开发人员来保证的。
举个Kafka怎样重复消费的例子:
Kafka实际有个offset的概念,就是每个消息写进去,都有一个offset,代表消息的序号,然后consumer消费数据之后,每隔一段时间,会把自己消费过的消息的offset提交一下,表示我已经消费过了,下次重启就会继续从上次消费到的offset来继续消费。但是总有意外出现,比如我们之前生产经常遇到的,就是有时候重启系统,如果碰到着急的,直接kill进程,再重启。这样导致consumer有些消息处理了,但是没有来得及提交offset,重启之后,少数消息会再次消费一次,这样就造成了重复消费的原因。
其实重复消费不可怕,可怕就是没有考虑消费后,怎么保证幂等性。
假设有个系统,消费一条消息就往数据库插入一条数据,要是一个消息重复两次,那不就插入两条数据了,但是你要是消费到第二次时候,判断一下是否已经消费过了,若是就直接扔了,这样不就保留一条数据了,从而保证数据的正确性。
一条数据重复出现两次,数据库就只有一条数据,这样就保证了系统的幂等性。
幂等性就是一个数据或者一个请求,给你重复来多次,你得确保对应的数据是不会改变的,不能出错。
怎么保证消息队列消费的幂等性?
- 比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update 一下好吧。
- 比如你是写 Redis,那没问题了,反正每次都是 set,天然幂等性。
- 比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的 id,类似订单 id 之类的东西,然后你这里消费到了之后,先根据这个 id 去比如 Redis 里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个 id 写 Redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。
- 比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。