幂等性 mq重复消费

链接

幂等性概念:同一个接口,多次发出同一个请求,必须保证操作只执行一次。

比如下面这些情况,如果没有实现接口幂等性会有很严重的后果:

支付接口,重复支付会导致多次扣钱
订单接口,同一个订单可能会多次创建。

幂等性的解决方案

唯一索引
使用唯一索引可以避免脏数据的添加,当插入重复数据时数据库会抛异常,保证了数据的唯一性。

乐观锁

这里的乐观锁指的是用乐观锁的原理去实现,为数据字段增加一个version字段,当数据需要更新时,先去数据库里获取此时的version版本号,如:使用AtomicStampedReference包装的对象,

AtomicStampedReference 维护一个对象引用和一个整型变量“stamp”,可以对其进行原子更新。

在这里插入图片描述
在这里插入图片描述

如果当前引用等于预期引用,且当前戳记等于预期戳记,则原子地将引用和戳记的值都设置为给定的更新值。

/**
     * Atomically sets the value of both the reference and stamp
     * to the given update values if the
     * current reference is {@code ==} to the expected reference
     * and the current stamp is equal to the expected stamp.
     *
     
     如果当前引用等于预期引用,且当前版本号等于预期版本号,则原子地将引用和版本号的值都设置为给定的更新值。
     
     * @param expectedReference the expected value of the reference
     * @param newReference the new value for the reference
     * @param expectedStamp the expected value of the stamp
     * @param newStamp the new value for the stamp
     * @return {@code true} if successful
     */
    public boolean compareAndSet(V   expectedReference,
                                 V   newReference,
                                 int expectedStamp,
                                 int newStamp) {
        Pair<V> current = pair;
        return
            expectedReference == current.reference &&
            expectedStamp == current.stamp &&
            ((newReference == current.reference &&
              newStamp == current.stamp) ||
             casPair(current, Pair.of(newReference, newStamp)));
    }

悲观锁

乐观锁可以实现的往往用悲观锁也能实现,在获取数据时进行加锁,当同时有多个重复请求时其他请求都无法进行操作

分布式锁
幂等的本质是分布式锁的问题,分布式锁正常可以通过redis或zookeeper实现;在分布式环境下,锁定全局唯一资源,使请求串行化,实际表现为互斥锁,防止重复,解决幂等。

token机制
token机制的核心思想是为每一次操作生成一个唯一性的凭证,也就是token。一个token在操作的每一个阶段只有一次执行权,一旦执行成功则保存执行结果。对重复的请求,返回同一个结果。token机制的应用十分广泛。

MQ有可能发生重复消费,啥导致的?

  在一般网络环境下,都存在一定的网络延迟、网络抖动,网络问题导致消息重复发送的情况是难以避免的,毕竟网络环境无法预知,因此MQ默认允许消息重复发送。是的,只要通过网络交换数据,就无法避免这个问题。秉承着打不过就加入的原则,解决这个问题的办法就是绕过这个问题。

RabbitMQ、RocketMQ、Kafka,都有可能会出现消息重复消费的问题。因为这问题通常不是 MQ 自己保证的,而是消费方自己来保证的(确认机制)

场景示例:

  Kafka, 他实际上有个 offset 的概念(偏移量),就是每个消息写进去,都有一个 offset,代表消息的序号,然后 consumer 消费了数据之后,每隔一段时间(定时定期),会把自己消费过的消息的 offset 提交一下。代表我已经消费过了,就算消费者重启,Kafka也会让消费者继上次消费到的offset继续消费。

  kafka 中有一条数据:A、B,kafka给这条数据分一个 offset(偏移量),offset为: 10011002。消费者从 kafka 去消费的时候,也是按照这个顺序去消费。当消费者消费到 offset=1002 的这条数据(此时offset=1001还没消费完),刚提交 offset=1002 到 zookeeper,消费者进程就被重启了。此时消费过的数据 A 的 offset 还没有提交,kafka 也就不知道消费者已经消费了1001这条数据。那么重启之后,消费者会找 Kafka 把上次消费到的那个地方后面的数据继续传递过来。数据 A 再次被消费。

如果消费端收到两条一样的消息,应该怎样处理?

如何保证消息不被重复消费?如何实现幂等性?

  • 比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update就行。对了,ES的插入接口是不是就采用了插入并更新的策略?发现相同的数据就直接更新他。
  • 如果是写 Redis,那没问题,反正每次都是set,天然幂等性。
  • 比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的 id,类似订单 id 之类的东西,然后你这里消费到了之后,先根据这个 id 去 Redis 里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个 id 写 进Redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。
  • 比如基于数据库的唯一键来保证重复数据不会重复插入多条。因为有唯一键约束了,重复数据插入只会报错,不会导致数据库中出现脏数据。(类似于第一条,可以通过修改SQL,转成插入或更新的策略)
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值