幂等性

青旅半醒 2022-11-19 04:03 359阅读 0赞

幂等性

1.什么是幂等性?

对于同一笔业务操作,不管调用多少次,得到的结果都是一样的。

2.幂等性技术方案

1.悲观锁

获取数据的时候加锁获取。select * from table_xxx where id=‘xxx’ for update;

注意:id字段一定是主键或者唯一索引,不然是锁表

2.乐观锁

  1. 通过版本号实现update table_xxx set name=‘xxx’,version=version+1 where version=#version

3.唯一索引

通过唯一索引或唯一组合索引来防止新增数据存在脏数据,当表存在唯一索引,并发时新增报错时,当前事务会被回滚,最终最有一个操作会成功

4.分布式锁

使用redis进行分布式锁。

3.方法例子

以支付宝充值功能的,我们需要给支付宝提供一个回调接口,支付宝回调信息中会携带(out_trade_no【商户订单号】,trade_no【支付宝交易号】),trade_no在支付宝中是唯一的,out_trade_no在商户系统中是唯一的。

回调接口实现有以下实现方式1,2,3,4。

1.jvm加锁方式

使用java中的Lock加锁,来防止并发操作,过程如下:

1.接收到支付宝支付成功请求
2.调用java中的Lock加锁
3.根据trade_no查询当前订单是否处理过
4.如果订单已处理直接返回,若未处理,继续向下执行
5.开启本地事务
6.本地系统给用户加钱
7.将订单状态置为成功
8.提交本地事务
9.释放Lock锁

分析问题:
Lock只能在一个jvm中起效,如果多个请求都被同一套系统处理,上面这种使用Lock的方式是没有问题的,不过互联网系统中,多数是采用集群方式部署系统,同一套代码后面会部署多套,如果支付宝同时发来多个通知经过负载均衡转发到不同的机器,上面的锁就不起效了。此时对于多个请求相当于无锁处理了,会出现本地会给用户加两次钱的结果。此时我们需要分布式锁来做处理。

2.悲观锁方式

使用数据库中悲观锁实现。悲观锁是依靠数据库来实现的。数据中悲观锁使用for update来实现,过程如下:

1.接收到支付宝支付成功请求
2.打开本地事物
3.查询订单信息并加悲观锁

  1. select * from t_order where order_id = trade_no for update;

4.判断订单是已处理
5.如果订单已处理直接返回,若未处理,继续向下执行
6.给本地系统给用户加钱
7.将订单状态置为成功
8.提交本地事物

重点在于for update,对for update,做一下说明:
1.当线程A执行for update,数据会对当前记录加锁,其他线程执行到此行代码的时候,会等待线程A释放锁之后,才可以获取锁,继续后续操作。
2.事物提交时,for update获取的锁会自动释放。

不过存在一些缺点:
1.如果业务处理比较耗时,并发情况下,后面线程会长期处于等待状态,占用了很多线程,让这些线程处于无效等待状态,我们的web服务中的线程数量一般都是有限的,如果大量线程由于获取for update锁处于等待状态,不利于系统并发操作。

3.乐观锁方式

依靠数据库中的乐观锁来实现。

1.接收到支付宝支付成功请求
2.查询订单信息

  1. select * from t_order where order_id = trade_no;

3.判断订单是已处理
4.如果订单已处理直接返回,若未处理,继续向下执行
5.打开本地事物
6.给本地系统给用户加钱
7.将订单状态置为成功,注意这块是重点,伪代码:

  1. update t_order set status = 1 where order_id = trade_no where status = 0;
  2. //上面的update操作会返回影响的行数num
  3. if(num==1){
  4. //表示更新成功
  5. 提交事务;
  6. }else{
  7. //表示更新失败
  8. 回滚事务;
  9. }

注意:
update t_order set status = 1 where order_id = trade_no where status = 0; 是依靠乐观锁来实现的,status=0作为条件去更新
执行这条sql的时候,如果有多个线程同时到达这条代码,数据内部会保证update同一条记录会排队执行,最终最有一条update会执行成功,其他未成功的,他们的num为0,然后根据num来进行提交或者回滚操作。

4.唯一约束方式

依赖数据库中唯一约束来实现。

我们可以创建一个表:

  1. CREATE TABLE `t_uq_dipose` (
  2. `id` bigint(20) NOT NULL AUTO_INCREMENT,
  3. `ref_type` varchar(32) NOT NULL DEFAULT '' COMMENT '关联对象类型',
  4. `ref_id` varchar(64) NOT NULL DEFAULT '' COMMENT '关联对象id',
  5. PRIMARY KEY (`id`),
  6. UNIQUE KEY `uq_1` (`ref_type`,`ref_id`) COMMENT '保证业务唯一性'
  7. ) ENGINE=InnoDB;

对于任何一个业务,有一个业务类型(ref_type),业务有一个全局唯一的订单号,业务来的时候,先查询t_uq_dipose表中是否存在相关记录,若不存在,继续放行。

过程如下:

1.接收到支付宝支付成功请求
2.查询t_uq_dipose(条件ref_id,ref_type),可以判断订单是否已处理

  1. select * from t_uq_dipose where ref_type = '充值订单' and ref_id = trade_no;

3.判断订单是已处理
4.如果订单已处理直接返回,若未处理,继续向下执行
5.打开本地事物
6.给本地系统给用户加钱
7.将订单状态置为成功
8.向t_uq_dipose插入数据,插入成功,提交本地事务,插入失败,回滚本地事务,伪代码:

  1. try{
  2. insert into t_uq_dipose (ref_type,ref_id) values ('充值订单',trade_no);
  3. 提交本地事务:
  4. }catch(Exception e){
  5. 回滚本地事务;
  6. }

说明:
对于同一个业务,ref_type是一样的,当并发时,插入数据只会有一条成功,其他的会违法唯一约束,进入catch逻辑,当前事务会被回滚,最终最有一个操作会成功,从而保证了幂等性操作。
关于这种方式可以写成通用的方式,不过业务量大的情况下,t_uq_dipose插入数据会成为系统的瓶颈,需要考虑分表操作,解决性能问题。
上面的过程中向t_uq_dipose插入记录,最好放在最后执行,原因:插入操作会锁表,放在最后能让锁表的时间降到最低,提升系统的并发性。

5.分布式方式

模拟并发抢票

4.总结

1.实现幂等性常见的方式有:悲观锁(for update)、乐观锁、唯一约束
2.几种方式,单机按照最优排序:乐观锁 > 唯一约束 > 悲观锁

发表评论

表情:
评论列表 (有 0 条评论,359人围观)

还没有评论,来说两句吧...

相关阅读

    相关

    幂等性 1.什么是幂等性? 对于同一笔业务操作,不管调用多少次,得到的结果都是一样的。 2.幂等性技术方案 1.悲观锁 获取数据的时候加锁获取。sel

    相关 接口

    一、什么是幂等性 接口幂等性就是用户对于同一操作发起的一次请求或者多次请求的结果是一致的,不会因为多次点击而产生了副作用;比如说支付场景,用户购买了商品支付扣款成功,但是

    相关 实现 -接口

    接口幂等性 1.什么是幂等性 > 对于同一笔业务操作,不管调用多少次,得到的结果都是一样的。 > 也就是方法调用一次和调用多次产生的额外效果是相同的,他就具有幂

    相关

    老婆问了个问题,什么是“幂等性”?这个问题,从现象上好解释,例如今儿是618大促,购物车添加了丰富的商品,满心欢喜地点击了支付按钮,支付成 功了,但是返回的时候网络异常,不知道

    相关 接口

    1. 接口调用存在的问题 现如今我们的系统大多拆分为分布式SOA,或者微服务,一套系统中包含了多个子系统服务,而一个子系统服务往往会去调用另一个服务

    相关

    HTTP/1.1中对幂等性的定义是:一次和多次请求某一个资源对于资源本身应该具有同样的结果(网络超时等问题除外)。也就是说,其任意多次执行对资源本身所产生的影响均与一次执行的影