搜文章
推荐 原创 视频 Java开发 iOS开发 前端开发 JavaScript开发 Android开发 PHP开发 数据库 开发工具 Python开发 Kotlin开发 Ruby开发 .NET开发 服务器运维 开放平台 架构师 大数据 云计算 人工智能 开发语言 其它开发
Lambda在线 > 架构师之路 > 并发扣款一致性,幂等性问题,这个话题还没聊完!!!

并发扣款一致性,幂等性问题,这个话题还没聊完!!!

架构师之路 2019-11-08

《》,分享了同一个用户并发扣款时,有一定概率出现数据不一致,可以使用CAS乐观锁的方式,在不降低吞吐量,并且只有少量修改的情况下,保证数据的一致性。


文章发布不到24小时,就有近200的评论。

 
其中,问的比较多的是ABA问题,这个问题已经在《》中扩展。
 
其次,问的比较多的是作业题,为什么一定要用 select&set 的方式进行余额写回:

UPDATE t_yue SET money=$new_money WHERE uid=$uid AND money=$old_money;

 
为什么不能采用 直接扣减 的方法:

UPDATE t_yue SET money=money-$diff WHERE uid=$uid;

 
很人说,在并发情况下, 会将money扣成负数
 
为了保证余额不被扣成负数,再加一个where条件:

UPDATE t_yue SET money=money-$diff WHERE uid=$uid AND money-$diff>0;

这样是否可行?
画外音:额,撇开业务不谈,这个SQL用列做运算,其实是不好的,建议使用:

UPDATE t_yue SET money=money-$diff WHERE uid=$uid AND money>$diff;

 
很遗憾,仍然不行。原因在《》一文里点赞最多的评论,不幂等。
画外音:说明绝大部分同学,能够回答正确作业。
 
聊幂等性之前,先看另一个测试用例的case。
 
假设有一个服务接口,注册新用户:

bool RegisterUser($uid, $name){

         //查看uid是否已经存在

         select uid from t_user where uid=$uid;

         //不是新用户,返回失败

         if(rows>0)return false;

         else{

                   //把新用户插入用户表
                   insert into t_user values($uid, $name);
                   //返回成功
                   return true;
         }
}
 
有一个测试工程师,对该接口写了一个测试用例:

bool TestCase_RegisterUser(){

         //造一些假数据

         long uid=123;

         String name='shenjian';

         //调用被测试的接口

         bool result= RegisterUser(uid,name);

         //预期注册成功,对结果进行断言判断

         Assert(result,true);

         //返回测试结果

         return result;

}

 
这是不是一个好的测试用例?
这个用例存在什么问题?
你会发现, 相同条件下 ,这个 测试用例执行两次,得到的结果不一样
(1)第一次执行,第一次造数据,调用接口,注册成功;
(2)第二次执行,又造了一次相同的数据,调用接口,注册会失败;
不是一个好的测试用例,多次执行结果不同
 
什么是幂等性?
相同条件下,执行同一请求,得到的结果相同,才符合幂等性。
画外音:Google一下,比我解释得更好,但意思应该说清楚了。
 
如何将上面的测试用例改为符合“幂等性”的测试用例呢?
 
只需要加一行代码:

bool TestCase_RegisterUser(){

         //造一些假数据

         long uid=123;

         String name=’shenjian’;

         //先删除这个伪造的用户

         DeleteUser(uid);

         //调用被测试的接口

         bool result= RegisterUser(uid,name);

         //预期注册成功,对结果进行断言判断

         Assert(result,true);

         //返回测试结果

         return result;

}

这样,在相同条件下, 不管这个用例执行多少次,得到的测试结果都是相同的
 
是不是对幂等性有点感觉了。
 
读请求,一般是幂等的。

写请求,视情况而定:
  • insert x,一般来说不是幂等的,重复插入得到的结果不一定一样

  • delete x,一般来说是幂等的,删除多次得到的结果仍相同

  • set a=x是幂等的

  • set a=a-x不是幂等的

 
因此,这么扣减余额:
UPDATE t_yue SET money=$new_money WHERE uid=$uid AND money=$old_money;
是幂等操作
 
要是这么扣减余额:
UPDATE t_yue SET money=money-$diff WHERE uid=$uid AND money-$diff>0;
不是幂等操作
 
聊到这里,或许有朋友要抬杠了,测试用例会重复执行,扣款怎么会重复执行呢?
重试。
 
重试,是异常处理里很常见的手段。
 
你在写业务的时候有没有写过这样的代码:

result = DoSomething();

if(false==result || TIMEOUT){

         //错误,或者超时,重试一次

         result= DoSomething();

}

return result;

 
当然,又会有朋友抬杠了,我从来不重试!!!
画外音:额,这是合格,还是不合格呢?
 
你可以决定业务代码怎么写,你不能决定底层框架代码怎么写:
(1) 站点框架 有没有自动重试?
(2) 服务框架 有没有自动重试?
(3) 服务连接池 数据库连接池 有没有自动重试?
画外音:
(1)服务化分层的架构中,建议只入口层重试,服务层不要重试,防止雪崩;
(2)dubbo底层,调用超时是默认重试的,这个设计不好;
 
因此,在有重试的架构体系里,幂等性是需要考虑的一个问题。
 
现在该懂了,为啥扣款和充值业务,一般使用:
  • select&set,配合CAS方案

而不使用:
  • set money-=X方案

画外音:充了100电话费,怎么多了200块?

知其然,知其所以然 ,希望大家有收获。
架构师之路-分享技术思路
相关文章:
《》
《》


还有很多朋友评论:版本号,CAS只能在低并发中使用,高并发会有大量的修改失败。或许大家对“高并发”有所误解,下一篇聊一聊相关问题。

版权声明:本站内容全部来自于腾讯微信公众号,属第三方自助推荐收录。《并发扣款一致性,幂等性问题,这个话题还没聊完!!!》的版权归原作者「架构师之路」所有,文章言论观点不代表Lambda在线的观点, Lambda在线不承担任何法律责任。如需删除可联系QQ:516101458

文章来源: 阅读原文

相关阅读

关注架构师之路微信公众号

架构师之路微信公众号:road5858

架构师之路

手机扫描上方二维码即可关注架构师之路微信公众号

架构师之路最新文章

精品公众号随机推荐