Java全栈大联盟RabbitMQ系列之消费端幂等性保障

资源干货第一时间送达!

今天安妮就给小伙伴们列举以下几个点:

说明:以下内容非强制或必学,做到了解即可。但是,最好熟练!

1、幂等性概念

2、消费端如何保障幂等性

3、唯一ID+指纹码机制实现幂等

4、唯一ID+指纹码机制优缺点

5、利用Redis原子性实现幂等

6、Redis原子性实现幂等需考虑的问题

1、幂等性概念

首先,来看一个业务场景:

假设库存中存在100个手机,每卖一个,则count-1。

update T_REPS set count = count - 1

当库存量为1时,如果两个线程同时并发,即当前两个线程在同一时刻读取到手机库存量都为1,然后都想执行count-1操作。这种情况下,count可能会出现负数的情况。

那么如何解决这个问题呢?

在update语句下添加version条件。

update T_REPS set count = count - 1, version = version + 1 where version = 1

每执行一次count-1操作,就更新一次版本号。这样,即使两个线程读取到的库存量都是1(假设此时version值为1),都去执行count-1操作。只有有一个线程执行了count-1操作,则会更新当前的version值,version为2。那么另外一个线程再去执行

update T_REPS set count = count - 1, version = version + 1 where version = 1

时,由于verison值已经被线程1更新为2了(每次去更新数据时,它的version永远进行一个递增),所以这条sql语句不满足条件,则不会再去更新version为1时的count值了。通过这种方式,来确保线程的安全性。

幂等性概念:可能你要对一个事情进行一个操作,这个操作可能执行一百次,最终操作一百次的结果都是相同的。执行一条sql,并行的一瞬间可能要执行一百次,但无论执行多少次,这个结果都是唯一的相同的。

2、消费端如何保障幂等性

思考一个问题:

在海量订单产生的业务高峰期,如何避免消息的重复消费问题?

有好多消息到达MQ,消费端要监听大量的MQ消息,这个时候难免会出现消息的重复投递,或者网络原因导致的一些闪断,导致broker重发消息。这个时候,如果不去做幂等,就可能会出现重复消费。

消费端为了实现幂等,就意味着,我们的消息永远不会消费多次,即使我们收到了多条一样的消息,最终只对这个消息消费一次。

业界主流的幂等性操作:

1、唯一ID+指纹码机制,利用数据库主键去重;

2、利用Redis的原子性去实现;

3、唯一ID+指纹码机制实现幂等

唯一ID+指纹码机制:它的目的,是为了保证这次操作是绝对唯一的,从而利用数据库主键去重。

可能有些用户,就是在某一瞬间多次频繁的几次消费。比如用户同一段时间内多次频繁的执行转账操作。指纹码可能是业务规则,或者是时间戳加上具体的银行给我们返回的唯一的信息码,并不一定是系统生成的,而是一些业务规则去拼接起来的东西。通过操作消息的唯一性,利用数据库主键去重,保障消息的幂等性消费。如果消息成功被消费,我们是会对消息进行入库操作的。

select count(*) from T_ORDER where ID = 唯一ID + 指纹码

如果数据库没有查询到这条消息,则返回为0,再执行插入操作。

如果数据库查询到存在消息记录,则返回为1,表示该消息已经被操作了,这个时候消费端就不会再去消费了。

4、唯一ID+指纹码机制优缺点

优点:实现简单,易于理解

缺点:在高并发情况下,如果操作的是同一个数据库,就会有数据库写入的性能瓶颈。因为要执行insert操作,哪怕都成功了,每次都是先读操作(判断消息在数据库中是否存在记录),再执行写操作。

解决方案:跟进ID进行分库分表策略,采用一些算法路由去做一个分压分流的机制。比如ID是一串数字,可以利用哈希算法将ID路由到不同的数据库中。

5、利用Redis原子性实现幂等

Redis原子性:无论是集群模式还是单点,比如set 一个key,如果第二次再去set时,它会将这个值更新成最新的。

使用Redis进行幂等,需要考虑的问题:

第一,是否需要进行数据落库。如果落库的话,关键解决的问题是数据库和缓存如何做到原子性?

比如订单过来,利用Redis将订单存到Redis里面,第二次假如同样的订单号过来,利用Redis的幂等性把它过滤掉了。如果没过滤掉,第一件事是set order id,设置到Redis中,接下来还得对具体的订单进行一个数据库的存储,这个时候会发现,如果要落库的话,你的关键解决点事,数据库和缓存之间怎么去做到数据的一致性,怎么做到同时成功,同时失败。

加事务?Redis缓存有自己的事务,数据库有自己的事务,这两个事务不是同一个事务,肯定是不行的。总有一种极端情况会发生,即Redis写成功了,数据库写失败了。那么这个时候,第二个相同的订单号过来,到底让不让它入库呢?Redis可能直接过滤掉,但数据库中没有订单记录。

第二,如果不进行落库(不对数据进行MySql持久化存储,都存到Redis),那么都存储到缓存中,怎么去设置一个定时同步的策略?

你的数据不可能一直都放在缓存里。肯定是要把缓存数据同步到关系型数据库来持久化,这才是最稳妥的。放到缓存中,就能保证,百分之百的成功吗?如果缓存哪天出现问题,怎么去做呢?缓存的可靠性保障?利用Redis做幂等需要考虑的核心问题。

6、Redis原子性实现幂等需考虑的问题

使用Redis原子性实现幂等需要考虑一下几个问题:

1、是否让消息入库

2、若入库,如何解决数据库与缓存的数据一致性

3、若不入库,如何实现缓存的可靠性保障,如何将缓存数据定时同步到数据库

考虑清楚这些问题后,我们才可以放心的去利用Redis原子性实现幂等。

觉得有用就转发分享一下吧

大家12月份的第三个周日愉快,与你前行

精彩内容

看完本文有收获?请转发分享给更多人

关注「Java全栈大联盟」,提升大神技能

欢迎新旧粉丝(撒花),我是Java全栈大联盟安妮。大家对微信博文有什么问题都可以@我留言,我会尽快回复大家。希望以后可以和各位成为技术道友!

安妮

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20181216G053ZY00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券