消息队列 -- 怎么处理消息队列中重复的数据

在分布式系统中,上游发来的消息队列必然会存在重复的数据,这是不可避免的。

发生原因 消息生产方发送数据时可能会重发

Producer在发送消息时比如遇到网络问题时,发送后因超时得不到服务器的ack,从而进行重发。如果发送的消息内容是银行扣款,那么发生的问题可想而知。
有人会问为啥中间件不能帮我们做排重?
中间件排重会有以下问题:

  • 性能消耗严重
    对于kafka来说消息量都是千万以上,那么排重意味着要对这些数据进行多次查询,肯定是不现实的。同时排重的时间跨度也是有可能造成数据存储成本增高。

MQTT协议给出服务质量标准

  • At most once: 至多一次。消息在传递时,最多会被送达一次。换一个说法就是,没什么消息可靠性保证,允许丢消息。一般都是一些对消息可靠性要求不太高的监控场景使用,比如每分钟上报一次机房温度数据,可以接受数据少量丢失。
  • At least once: 至少一次。消息在传递时,至少会被送达一次。也就是说,不允许丢消息,但是允许有少量重复消息出现。
  • Exactly once:恰好一次。消息在传递时,只会被送达一次,不允许丢失也不允许重复,这个是最高的等级。
名称质量标准
KafkaAt least once
RocketMQAt least once
RabbitMQAt least once

既然不能在中间件服务解决重复的消息 那么只能在消费端解决

这里说个专业名词:

幂等(Idempotence) 本来是一个数学上的概念,它是这样定义的:如果一个函数 f(x) 满足:f(f(x)) = f(x),则函数 f(x) 满足幂等性。

这个概念被拓展到计算机领域,被用来描述一个操作、方法或者服务。一个幂等操作的特点是,其任意多次执行所产生的影响均与一次执行的影响相同

那么回到一开始提到的问题:银行转账多次收到扣款的问题。如果我们保证了消费的业务代码是幂等的,那么无论多少重复的消息过来,依然与第一次执行的发生的效果一致,这个问题就迎刃而解。

那怎么实现幂等性呢?一般有以下几种办法。

  1. 使用数据库唯一索引来实现幂等

通俗点说就是每次操作都有相同的一个操作id,在操作之前做查询,如果该操作id执行过,就避免再次执行。
比如在数据库对这个唯一约束增加唯一索引,多次插入相同的数据时会直接抛出异常“org.springframework.dao.DuplicateKeyException”。或者直接使用“INSERT IF NOT EXIST”来避免多次插入数据。

  1. 使用Token 机制或者 GUID(全局唯一 ID)机制

和数据库生成唯一标识的概念是一致的,如果消息量过多都放入数据库中去判断的话,可能会造成数据库奔溃。
那么在消息消费时,先判断该消息是否已经消费过,如果没有消费过,才放入到下一步进行持久化操作。
但是在分布式系统中,多个节点可能无法互相感知各自的消费消息,可能会出现这样的情况:

  • t0 时刻:Consumer A 收到条消息,检查消息执行状态,发现消息未处理过,开始执行“账户增加 100 元”;
  • t1 时刻:Consumer B 收到条消息,检查消息执行状态,发现消息未处理过,因为这个时刻,Consumer A 还未来得及更新消息执行状态。
    那么就必须要使用第三方缓存比如redis等来做分布式锁,或者使用分布式事务。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值