当前位置:首页 > 科技  > 软件

高并发扣款,如何保证结果一致性

来源: 责编: 时间:2024-01-10 09:35:27 312观看
导读在金融系统中,我们会跟钱打交道,而保证在高并发下场景下,对账户余额操作的一致性,是非常重要的,如果代码写的时候没考虑并发一致性,就会导致资损,本人在金融行业干了 8 年多,对这块稍微有点经验,所以这篇聊一下,如何在并发场景

在金融系统中,我们会跟钱打交道,而保证在高并发下场景下,对账户余额操作的一致性,是非常重要的,如果代码写的时候没考虑并发一致性,就会导致资损,本人在金融行业干了 8 年多,对这块稍微有点经验,所以这篇聊一下,如何在并发场景下,保证账户余额的一致性yMt28资讯网——每日最新资讯28at.com

1. 扣款流程是什么样的?

图片图片yMt28资讯网——每日最新资讯28at.com

public  void payout(long uid,var payAmount){   # 查询账户总额   var  amount= "SELECT amount FROM account WHERE uid=$uid";   # 计算账户余额   var balanceAmount = amount-payAmount;   if(balanceAmount<0) throw 异常   #更新余额     update account set amount=balanceAmount where uid=$uid;   }

以上流程如果并发量非常低的情况下是没问题的,但是如果在高并发下是很容易出现问题。yMt28资讯网——每日最新资讯28at.com

2. 在高并发下会出现什么问题?

  1. 订单a和订单 b同一时间都查询到了,账户余额为1000

图片图片yMt28资讯网——每日最新资讯28at.com

  1. 订单a扣款200,订单b扣款 100,都满足1000-减去扣款金额大于0

图片图片yMt28资讯网——每日最新资讯28at.com

  1. 执行扣款,订单 a修改账户余额为800,订单 b 修改为账户余额为900

图片图片yMt28资讯网——每日最新资讯28at.com

此时就出现问题了,如果订单 a 先执行更新,订单 b后执行,那么账户余额最终为900,反之为 800,都不正确,正确余额应该是700,那怎么处理呢?yMt28资讯网——每日最新资讯28at.com

3. 并发扣款怎么处理?

a. 使用悲观锁

在执行扣款时使用redis、zk或者数据库的for update对账户数据进行行级锁,使执行并发操作串型化操作,这里推荐使用for update操作,因为引用redis、zk还要考虑他们的异常情况,数据库最简单,也是目前的常规做法,本人曾经参与几大银行项目也是这种方式。yMt28资讯网——每日最新资讯28at.com

  1. 查询余额,在查询语句上加上for update,但是一定要注意where 条件是唯一索引,否则会导致多行数据被锁,同时必须要开始事务,否则for update没效果,使用分布式数据库中间件还要注意,for update可能会路由到读节点上。伪代码:
public  void payout(long uid,var payAmount){   try{    begin 事务      # 查询账户总额      var amount= "SELECT amount FROM account WHERE uid=$uid for update";      # 计算账户余额      var balanceAmount = amount - payAmount;      if(balanceAmount<0) throw 异常      #更新余额        update account set amount=balanceAmount where uid=$uid;       }catch(Exception e){     rollback 事务;      抛出异常;    }    commit 事务     }

b. 使用乐观锁(CAS)

乐观锁的方式也就是是CAS的方式,适合并发量不高情况,如果并发量高大概率都失败在重试,开销也不比悲观锁小,yMt28资讯网——每日最新资讯28at.com

注意这也是面试题:CAS 适合在使用场景下使用?yMt28资讯网——每日最新资讯28at.com

1. 增加版本号方式
  1. 在账户表增加乐观锁版本号
account(uid,amout,version)
  1. 查询余额时,同时查询版本号。
SELECT amount,version FROM account WHERE uid=$uid
  1. 每次更新余额时,必须版本号相等,并且版本号每次要修改。
update account set amount=余额,version=newVersion where uid=$uid and version=$oldVersion
2. 使用原有金额值比对更新

在执行账户余额更新时,where 条件中增加第一次查出来的账户余额,即初始余额,如果在执行更新时,初始余额没变则更新成功,否则肯定是更新了,同时数据库也会返回受影响的行数,来判断是否更新成功,如果没成功就再次重试。yMt28资讯网——每日最新资讯28at.com

update account set amount=余额  where uid=$uid and amount=$oldAmount

以下是伪代码,遇到失败回滚事务并抛出异常,上层调用方法要考虑捕获异常在进行重试yMt28资讯网——每日最新资讯28at.com

public void payout(long uid,var payAmount){     try{            begin 事务        # 查询账户总额        var amount= "SELECT amount FROM account WHERE uid=$uid for update";        # 计算账户余额        var balanceAmount = amount- payAmount;        if(balanceAmount<0) throw 异常        #更新余额          int count=update account set amount=$balanceAmount where uid=$uid and amount=$amount;           ###注意如果更新成功返回count为1         if(count<1){           抛出异常重试;         }      }catch(Exception e){        rollback 事务;           抛出异常;     }   commit 事务       }

具体到以上示例yMt28资讯网——每日最新资讯28at.com

订单a 执行yMt28资讯网——每日最新资讯28at.com

update account set amount=800 where uid=$uid and amount=1000;

订单b 执行yMt28资讯网——每日最新资讯28at.com

update account set amount=900 where uid=$uid and amount=1000;

以上两笔执行只有一笔能成功,因为amount 变了。yMt28资讯网——每日最新资讯28at.com

4. 使用乐观锁会不会存在aba 的问题

什么是 aba?

线程 1:获取出数据的初始值是a,如果数据仍是a的时候,修改才能成功yMt28资讯网——每日最新资讯28at.com

线程 2:将数据修改成byMt28资讯网——每日最新资讯28at.com

线程 3:将数据修改成 ayMt28资讯网——每日最新资讯28at.com

线程 1:执行cas,发现数据还是 a,进行数据修改yMt28资讯网——每日最新资讯28at.com

上述场景,线程1在修改数据时,虽然还是a,但已经不是初始条件的a了,中间发生了a变b,b又变a,此 a 非彼 a,但是成功修改了,在有些场景下会有问题,这就是 abayMt28资讯网——每日最新资讯28at.com

但是以上场景,对账户扣款不会出现问题,因为余额 1000 就是 1000,是相同的,举个例子,yMt28资讯网——每日最新资讯28at.com

订单a:获取出账户余额为 1000,期望余额是 1000的时候,才能修改成功。yMt28资讯网——每日最新资讯28at.com

订单b:取了 100,将余额修改成了900。yMt28资讯网——每日最新资讯28at.com

订单c:存进去了100,将余额修改成了 1000。yMt28资讯网——每日最新资讯28at.com

订单 a:检查账户余额为1000,进行扣款200,账户余额变成了800。yMt28资讯网——每日最新资讯28at.com

以上场景账户资金损失吗没有吧,不过为了避免产生误解,推荐还是使用版本号的方式!yMt28资讯网——每日最新资讯28at.com

5. 总结

以上我们讲了在高并发场景在如何保证结果一致性方式,在并发量高情况下推荐使用悲观锁的方式,如果并发量不高可以考虑使用乐观锁,推荐使用版本号方式。同时乐观锁场景要注意 aba 的问题。yMt28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-59661-0.html高并发扣款,如何保证结果一致性

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 一文搞懂什么是JMM重排序、内存屏障、顺序一致性

下一篇: 全面分析 Java 在 2023 年仍然流行的 25 个原因

标签:
  • 热门焦点
  • 卢伟冰长文解析K60至尊版 对Redmi有着里程碑式的意义

    在今天的Redmi后性能时代战略发布会结束之后,Redmi总经理卢伟冰又带来了一篇长文,详解了为什么 Redmi 要开启后性能时代?为什么选择和 MediaTek、Pixelworks 深度合作?以及后性
  • 石头智能洗地机A10 Plus体验:双向自清洁治好了我的懒癌

    一、前言和介绍专为家庭请假懒人而生的石头科技在近日又带来了自己的全新旗舰新品,石头智能洗地机A10 Plus。从这个产品名上就不难看出,这次石头推出的并不是常见的扫地机器
  • 2023年Q2用户偏好榜:12+256G版本成新主流

    3月份的性能榜、性价比榜和好评榜之后,就要轮到2023年的第二季度偏好榜了,上半年的新机潮已经过去,最明显的肯定就是大内存和存储的机型了,另外部分中端机也取消了屏幕塑料支架
  • Java NIO内存映射文件:提高文件读写效率的优秀实践!

    Java的NIO库提供了内存映射文件的支持,它可以将文件映射到内存中,从而可以更快地读取和写入文件数据。本文将对Java内存映射文件进行详细的介绍和演示。内存映射文件概述内存
  • 得物效率前端微应用推进过程与思考

    一、背景效率工程随着业务的发展,组织规模的扩大,越来越多的企业开始意识到协作效率对于企业团队的重要性,甚至是决定其在某个行业竞争中突围的关键,是企业长久生存的根本。得物
  • 这款新兴工具平台,让你的电脑效率翻倍

    随着信息技术的发展,我们获取信息的渠道越来越多,但是处理信息的效率却成为一个瓶颈。于是各种工具应运而生,都在争相解决我们的工作效率问题。今天我要给大家介绍一款效率
  • 腾讯盖楼,字节拆墙

    来源 | 光子星球撰文 | 吴坤谚编辑 | 吴先之&ldquo;想重温暴刷深渊、30+技能搭配暴搓到爽的游戏体验吗?一起上晶核,即刻暴打!&rdquo;曾凭借直播腾讯旗下代理格斗游戏《DNF》一
  • 新电商三兄弟,“抖快红”成团!

    来源:价值研究所作 者:Hernanderz 随着内容电商的概念兴起,抖音、快手、小红书组成的&ldquo;新电商三兄弟&rdquo;成为业内一股不可忽视的势力,给阿里、京东、拼多多带去了巨大压
  • 2022爆款:ROG魔霸6 冰川散热系统持续护航

    喜逢开学季,各大商家开始推出自己的新产品,进行打折促销活动。对于忠实的端游爱好者来说,能够拥有一款梦寐以求的笔记本电脑是一件十分开心的事。但是现在的
Top