RabbitMQ如何避免消费重复的问题?<<面试专题>>

187 阅读4分钟

面试一般会问的问题:同一条消息如何避免消息重复?或者会问如何保证消息幂 等性?其实这是同一个问题

  1. 了解什么是幂等性
幂等性也就是无论消息被消费几次,得到的结果都是同一个结果,除了对性能有一定影响外,并不会产生其他的问题。
幂等性的例子:如我们在学MySQL的时候,有时会根据条件查询结果,查询就是天然幂等性的,无论我们根据相同条件查询多少次,只要没有进行数据删除,得到的结果都是一样的。
如根据ID查询;除了查询,删除操作,根据条件更新操作等等都是幂等性的操作,操作多次与操作一次的结果不会发生变化,这就是幂等性。

了解什么是幂等性,再看看什么是非幂等的操作就可以更明白为什么需要保证消息消费的幂等性了

非幂等的操作例如向数据库插入数据,由于一般表都会有个主键ID,并且会自增或者由雪花算法自动生成ID,因此每次操作数据库插入时,尽管其他数据一样也会插入成功。这样就造成了数据的非幂等操作。
因为我们本意只想得到一条数据的插入结果,但由于消息的重复消费造成数据重复插入导致的两条及以上的数据插入,这明显不是我们想要的操作。
除了插入操作外,更新操作也是一种非幂等。

有细心的人会发现,幂等性有更新操作,非幂等也有更新操作,why?

其实建议独立想想,但说问题就要讲透彻点。已知更新操作分为两类。
一类幂等性:根据条件修改字段的值,此时修改的是整个值,比如根据id更新数据下姓名字段为XXX,此类消息被重复消费进行多次修改,都是将原来
该 ID 数据下的 姓名 字段 的值改为XXX,无论修改多少次,最终结果都是我们想要的XXX姓名的结果,且不会影响其他数据。
SQL语句一般为:update 表名 set 字段=值 where id = ?

二类非幂等: 也同样是根据条件修改字段的值,不过此类修改的方式一般是作为加减的方式操作数据的修改方式。SQL语句一般为:update 表名 set 字段=字段 - 值 where id = ?
此时若是消息被重复消费,就会造成数据中某字段的值被连续扣减,造成非我们需要得到的结果,此类就是非幂等更新了。

那我们如何避免消息被重复消费呢?

这非常简单,只需要运用我们此前学到的一款缓存中间件即可:Redis。
将不是幂等性的操作设置一个消息ID设置到Redis缓存中,并赋予一个默认的值0,每次消费时会去Redis中根据消息ID查找。
判断消息ID是否存在并且其值是否为0存在,如果存在对应消息的ID并且值为0,说明该消息没有被消费。此时进行手动或自动签收,并更新消息ID的值为1。
若是判断为消息ID存在但值为1,说明消息已经被消费过,此时只需眼进行签收操作即可。

写个伪代码看看

Boolean flag = redis.setex(messageId,O,设置存入redis的过期时间)[此时flag的值为true]
if(flag ==true){ // 值为零,未被消费过,进行消费(并且上锁)[由于设置过期时间,所以一般不会死锁]
         channel.basicAck(tag,false); // 手动签收
         set(messageId,1,过期时间) // 设置被消费过
 }else{
 // 意味着flag为false  表示有人正在消费或者已经完成消费
 // 获取对应消息id的值
 int value = redis.get(messageID)
 //  判断 如果此时 value的值为1 说明是已经消费的,如果为零,说明是正在消费的
 // 如果为1 ,进行签收,为零则不做任何处理
   if(value == 1){
   channel.basicAck(tag,false)
 }

整体思路

image.png