MQ 有个基本原则:
数据不能多一条,也不能少一条。不能多,就是这一篇-----不能重复消费和保证消息幂等性,不能少,就是下一篇的保证消息可靠性传输。
重复消费消息的场景:
场景1:消费者干的事是拿一条数据往数据库写一条,如果消息重复两次就写了两条,导致数据出错。
解决场景1:同一条消息消息到第二次时判断一下是否已消费过,若是则直接扔掉,一条数据出现两次但是数据库只有一条,这就保证了系统的幂等性。
何为幂等性?(官方定义):一次和多次请求某资源对于资源本身应该有相同的结果(网络超时除外)。也就是,任意多次执行对资源本身产生的影响均与一次执行的结果相同
如何保证消息队列消费的幂等性?(要结合业务):
业务场景1:从生产者拿到个数据后要写库,先根据主键查一下,如果这个数据有了就别插了直接update
业务场景2:如果是写redis的都没问题,因为每次都是set,redis天然的幂等性
业务场景3:需要让生产者发送每条数据的时候加上一个全局唯一的id,消费的时候先根据id去比如redis查一下判断是否消费过,若没有则处理然后这个id写redis,若消费过就不处理
业务场景4:如果数据库有唯一建约束了,插入只会报错,不会导致数据库出现脏数据,本身幂等了
两种方式可解决幂等性操作:
(1)唯一ID + 指纹码机制,利用数据库主键去重
思路:根据消息生成一个全局唯一ID,然后加上一个指纹码。指纹码可以系统生成也可以根据某些规则自定义拼接,目的是确定本次才做唯一,将ID+指纹码作为拼接好的值作为主键就可以去重了,在消费消息前先去数据库查看这条消息指纹码是否存在,没有就插入有就忽视。
高并发写数据库性能瓶颈:可以跟进ID进行分库分表策略,采用一些路由算法进行分流,要保证ID通过这种算法消息即使投递多次都落在同一数据库分片上,这样就由单台数据库幂等变成多库的幂等。
(2)利用Redis的原子性去实现
redis是单线程的,但是性能好也有很多原子性的命令,比如setnx命令,在接收到消息后将消息ID作为key去执行setnx命令,如果执行成功则表示没有执行过这条消息,可以进行消费(setnx命令特点:当且仅当key不存在,将key值设为value值;若key已存在该命令不做任何操作)