当前位置: 首页 > news >正文

Java高并发场景(银行转账问题)

最近面试问到了银行转账的高并发问题,回答的不是很理想,小编整理了下,题目大概如下:
有一张银行账号表(银行账号字段、金额字段),A账号要给B账号转账,A扣款,B收款,在多线程高并发情况下,A账户的金额不能小于0,问如何设计架构比较合理?
我一开始脑抽地回答了两个方案:
方案一:事务+同步锁/分布式锁(更新sql控制扣款update的账户金额要大于扣款金额)
方案二:将数据库缓存于redis,通过lua语句去执行查询判断扣款和收款,然后保证异步通知数据库更新

先来看第一个方案哈,同步锁在单节点的情况下确实可以解决问题,但是首先颗粒度大(不管哪个转账都得排队),且复杂度高的情况下就效率慢,其次若是多节点集群的情况下,同步锁就不适用,那我们看分布式锁(redis),分布式锁确实可以降低颗粒度,可以控制到A账户作为key锁,但是面试官提到了一个概念,redis脑裂(可能产生数据丢失),因此可能出现假锁的情况,因为面试的这家公司是做数字银行的,对于风险把控很严格,因此对于这类情况风险他们对这个方案也pass掉,不过我后面补充的这个扣款时sql需要增加当前账户金额需要大于扣款金额才能扣款,这个其实是可行的,这个后续代码会演示。

再来看第二个方案哈,这个缓存于redis的方案,其实我当时为什么会这么直接想到这个方案呢,首先是因为redis的单机命令操作,以及lua能保证多语句的执行,若账户金额不够扣款则不会进行转账,但是其实金额数据一般是不会缓存在redis中的,有一定的风险性且增加了系统复杂度,若数据库异常或其他情况导致的缓存数据不一致,金额这方面无法保证。

面试官还提到关于数据库隔离级别能否解决问题,其实我验证后关于可串行化其实也是只是对当前sql语句执行进行加锁,开启事务时可串行化也并非是对事务进行加锁,依然可能出现金额问题。(查询金额存在多个一样的情况)(但是其实可串行化在我之前了解的资料里理论上应该是可行的,有强制事务串行化,即按顺序提交

代码验证

锁机制在一定程度上可以,sql条件扣款时控制也是可以

@Service
@Slf4j
public class OperateAccountImpl implements OperateAccount {@Autowiredprivate AccountMapper accountMapper;@Autowiredprivate TransactionTemplate transactionTemplate;/*** 处理账户转账方案总结:* 方案一:事务+同步锁(颗粒度大,逻辑复杂效率慢,只适用单机)/分布式锁(可能出现脑裂假锁的情况)* 方案二:事务+sql条件控制(账户金额需大于等于扣款金额,但是查询时可能出现一次可扣款数据)*/@Override
//    @Transactional(rollbackFor= Exception.class)public String transfer(String accountFrom, String accountTo, double amount) {// 设置隔离级别为串行化(这里测试出来会死锁,按理解串行化应该是事务串行化,不应该抢锁才对)//transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_SERIALIZABLE);//线程标记String threadName = "【"+Thread.currentThread().getName()+"】";String msg = "";List<String> msgList = new ArrayList<>();//同步锁也是实现方法之一
//        synchronized(this){
//
//        }try{//开启Spring事务transactionTemplate.execute(new TransactionCallbackWithoutResult() {protected void doInTransactionWithoutResult(TransactionStatus status) {try {QueryWrapper<AccountDto> queryWrapper =new QueryWrapper<>();queryWrapper.select("account","money").eq("account",accountFrom);// 查询金额是否够扣AccountDto accountDto = accountMapper.selectOne(queryWrapper);
//                        AccountDto accountDto = accountMapper.selectByForUpdate(accountFrom);if(null == accountDto){throw new Exception("账户"+accountFrom+"不存在");}log.info("{} 查询到扣款账户{} 余额:{}",threadName,accountFrom,accountDto.getMoney());if (accountDto.getMoney() < amount) {throw new Exception("余额不足,账户"+accountFrom+"只剩:"+accountDto.getMoney());}// 扣款int count1 = accountMapper.update(null,transferUpdate(-1 * amount,accountFrom));if (count1 == 0) {throw new Exception("账户"+accountFrom+"扣款失败,检查余额");}// 收款int count2 = accountMapper.update(null,transferUpdate(amount,accountTo));if (count2 == 0) {throw new Exception("账户"+accountTo+"收款失败");}log.info(threadName+"转账成功");msgList.add(threadName+"转账成功");}catch (Exception e){log.info(threadName+e.getMessage());msgList.add(threadName+e.getMessage());// 回滚status.setRollbackOnly();}}});}catch (Exception e){
//            msgList.add(e.getMessage());}//        log.info(threadName+"结束:"+msgList.toString());return msgList.get(0);}// 更新sql操作public UpdateWrapper<AccountDto> transferUpdate(double updateMoney,String account){UpdateWrapper<AccountDto> updateWrapper = Wrappers.update();// 修改表中money字段为指定的数据
//        updateWrapper.set("money", updateMoney);updateWrapper.setSql("money = money + "+ updateMoney);if (updateMoney<0){// 修改条件为account=?且大于等于扣款金额的数据updateWrapper.eq("account", account).and(wq ->wq.ge("money",-1 * updateMoney));}else{// 修改条件为account=?的数据updateWrapper.eq("account", account);}//        //若使用事务隔离级别为最高级,测试出来的结果加锁的是sql并不是事务,因此查询值依然没有顺序之分,分开sql依旧会出现问题(实际上不会扣款扣多,但是会死锁,因为会抢占sql的锁)
//        updateWrapper.eq("account", account);return updateWrapper;}
}

通过多线程并发执行测试

测试结论:
方案一:事务+同步锁(颗粒度大,逻辑复杂效率慢,只适用单机)/分布式锁(可能出现脑裂假锁的情况)
方案二:事务+sql条件控制(账户金额需大于等于扣款金额,但是查询时可能出现一次可扣款数据)

@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {@Autowiredprivate OperateAccount operateAccount;//设置固定线程池ExecutorService executorService = Executors.newFixedThreadPool(10);@RequestMapping("/transfer")public String transfer(HttpServletRequest request){//创建同步计数器(10个一起跑)CountDownLatch countDownLatch = new CountDownLatch(10);//用于堵塞线程等待全部结果CountDownLatch countDownLatch1 = new CountDownLatch(10);//扣款账户String accountFrom = request.getParameter("accountFrom");//收款账户String accountTo = request.getParameter("accountTo");//转账金额double amount = Double.parseDouble(request.getParameter("amount"));List<String> msgList = new ArrayList<>();try {// 模拟转账操作for (int i = 0; i < 10; i++) {executorService.submit(() -> {try {countDownLatch.await();//统一等待} catch (InterruptedException e) {e.printStackTrace();}String msg = operateAccount.transfer(accountFrom, accountTo, amount);msgList.add(msg);countDownLatch1.countDown();});//处理完同步计数器线程数减一,待计数器为0统一执行所有转账操作countDownLatch.countDown();}countDownLatch1.await();executorService.shutdown();}catch (Exception e){e.printStackTrace();}//        log.info("转账结果:{}",msgList.toString());return msgList.toString();}}

小编比较菜…欢迎评论区讨论更好的方法❀❀❀

相关文章:

Java高并发场景(银行转账问题)

最近面试问到了银行转账的高并发问题&#xff0c;回答的不是很理想&#xff0c;小编整理了下&#xff0c;题目大概如下&#xff1a; 有一张银行账号表&#xff08;银行账号字段、金额字段&#xff09;&#xff0c;A账号要给B账号转账&#xff0c;A扣款&#xff0c;B收款&#x…...

TypeScript 工具类型

这些工具类型是 TypeScript 提供的强大功能&#xff0c;用于操作和转换类型。下面是每个工具类型的简要说明和示例&#xff1a; 1、Record let value: Record<string, any> { name: "", age: 0, desc: [] }; let value2: { [key: string]: any } { name: &…...

[Kotlin]创建一个私有包并使用

1.创建Kotlin测试项目 在Android Studio或其他IDE中选择“Create New Project”。选择Kotlin和Gradle作为项目类型和构建系统。指定项目名称和位置&#xff0c;完成设置。 2.创建Android Library模块 官方文档&#xff1a;创建 Android 库 | Android Studio | Android De…...

鸿蒙应用开发者高级认证指南及参考资料整理(含详细参考答案)

如何报名鸿蒙应用开发者高级认证 报名链接:点击这里进行报名。报名步骤: 点击上述链接进入报名页面。选择“立即报名”。在课程内容中找到“HarmonyOS应用开发者高级认证”,点击进入。点击“参加考试”,随后即可开始考试。考试注意事项 实名认证:考试前,请务必完成实名认…...

数据匿名化技术

不同的数据匿名化技术可用于多种行业&#xff0c;旨在从数据流中获取有用的见解&#xff0c;同时确保满足数据保护标准和法规的合规要求。 数据脱敏&#xff08;Data Masking&#xff09; 数据脱敏&#xff0c;又称数据漂白、数据去隐私化或数据变形&#xff0c;指的是对数据集…...

HTML学习笔记汇总

整理一些常见问题的Links&#xff0c;不定期更新。 Html生成自定义函数的图形&#xff08;2024/5/10&#xff09;-CSDN博客 HTML中插入图片&#xff08;2024/5/10&#xff09;-CSDN博客 Html给网页加icon图标_html icon-CSDN博客...

初始JSVMP

1.初始JSVMP JSVMP是"JavaScript Virtual Machine Protection"的缩写&#xff0c;是一种前端代码虚拟化保护技术。它的核心思想是在JavaScript代码保护过程中引入代码虚拟化&#xff0c;将目标代码转换成自定义的字节码&#xff0c;这些字节码只有特殊的解释器才能识…...

【机器学习数据可视化-04】Pyecharts数据可视化宝典

一、引言 在大数据和信息爆炸的时代&#xff0c;数据可视化成为了信息传递和展示的关键手段。通过直观的图表和图形&#xff0c;我们能够更好地理解数据&#xff0c;挖掘其背后的信息。Pyecharts&#xff0c;作为一款基于Python的数据可视化库&#xff0c;凭借其丰富的图表类型…...

通过 Java 操作 redis -- zset 有序集合基本命令

目录 使用命令 zadd&#xff0c;zrange 使用命令 zcard 使用命令 zrem 使用命令 zscore 使用命令 zrank 关于 redis zset 有序集合类型的相关命令推荐看Redis - Zset 有序集合 要想通过 Java 操作 redis&#xff0c;首先要连接上 redis 服务器&#xff0c;推荐看通过 Jav…...

力扣 516. 最长回文子序列 python AC

动态规划 class Solution:def longestPalindromeSubseq(self, s):size len(s)dp [[0] * size for _ in range(size)]for i in range(size):dp[i][i] 1for i in range(size - 1, -1, -1):for j in range(i 1, size):if s[i] s[j]:dp[i][j] dp[i 1][j - 1] 2else:dp[i][…...

数据库编程

PL/SQL程序 1.PL/SOL程序块 整个PL/SQL块分三部分&#xff1a;声明部分、执行部分、异常处理部分&#xff1b; 示例&#xff1a; declare --变量声明 v_sno varchar2(10) : ‘04001’; v_cno varchar2(10) :‘001’; v_grade number : 90; begin --程序入口 insert…...

(docker)进入容器后如何使用本机gpu

首次创建容器&#xff0c;不能直接使用本机gpu 在系统终端进行如下配置&#xff1a; 1.安装NVIDIA Container Toolkit 进入Nvidia官网Installing the NVIDIA Container Toolkit — NVIDIA Container Toolkit 1.15.0 documentation&#xff0c;安装NVIDIA Container Toolkit …...

java基础知识点总结2024版(8万字超详细整理)

java基础知识点总结2024版&#xff08;超详细整理&#xff09; 这里写目录标题 java基础知识点总结2024版&#xff08;超详细整理&#xff09;java语言的特点1.简单性2.面向对象3.分布式4.健壮性5.安全性6.体系结构中立7.可移植性8.解释性9.多线程10.动态性 初识java中的main方…...

vue中使用element的i18n语言转换(保姆式教程-保证能用)

话不多说&#xff0c;先看效果:预览地址: https://sandm00.github.io/i18n-switch/#/ 1、项目中需要使用的插件&#xff0c;vue2或vue3、element、vue-i18n、js-cookie、vuex我是在vue2中使用 npm i element-ui -S npm i js-cookie -S npm i vue-i18n8.28.2 //因为我项目使用…...

01 设计模式--单例模式

1. 单例模式 单例模式有两种实现方式&#xff1a; 1.1 饿汉模式&#xff08;Eager Initialization&#xff09;&#xff1a;在类加载时就创建单例实例&#xff0c;无论是否需要使用该实例。 饿汉模式在类加载时就创建单例实例&#xff0c;无论是否需要使用该实例。 饿汉模式…...

css backdrop-filter 实现背景滤镜

官方给出的定义是&#xff1a;backdrop-filter属性允许您将图形效果&#xff08;如模糊或颜色偏移&#xff09;应用于元素后面的区域。因为它适用于元素后面的所有内容&#xff0c;所以要查看元素或其背景的效果&#xff0c;需要透明或部分透明。 大致分为以下10种&#xff1a…...

AR人脸道具SDK解决方案,实现道具与人脸的自然融合

AR人脸道具SDK解决方案&#xff0c;实现道具与人脸的自然融合美摄科技以其卓越的技术实力和创新能力&#xff0c;为企业带来了全新的AR人脸道具SDK解决方案。这一解决方案将为企业打开全新的市场机会&#xff0c;为用户带来前所未有的互动体验。 颠覆传统&#xff0c;开启AR人…...

Windows安装RabbitMQ教程(附安装包)

需要两个安装包 Erlang 安装包: https://download.csdn.net/download/Brevity6/89274663 (自己从官网下载也可以) RabbitMQ Windows 安装包&#xff1a; https://download.csdn.net/download/Brevity6/89274667 (自己从官网下载也可以) Erlang安装 Erlang安装傻瓜式下一…...

这个问题无人能解,菜鸟勿进

前言 这是陈旧已久的草稿2021-06-23 23:25:12 现在2024-5-12 21:53:46&#xff0c;发布到[逻辑题]专栏中。 一、问题 1.描述&#xff1a; 在我的世界中建个红石电路 2.需求&#xff1a; 五个灯A、B、C、D、E、F 五个开关a、b、c、d、e、f、总开关 3.要求&#xff1a; 总开关…...

揭秘高效引流获客的艺术:转化技巧大公开

在数字化营销的海洋中&#xff0c;每个企业都如同一艘努力航行的船&#xff0c;而流量便是推动船只前行的风帆。如何有效吸引并获取潜在客户&#xff0c;即所谓的“引流获客”&#xff0c;已成为企业市场营销策略中不可或缺的一环。本文将详细探讨几种实用且高效的引流获客技巧…...

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站&#xff0c;会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后&#xff0c;网站没有变化的情况。 不熟悉siteground主机的新手&#xff0c;遇到这个问题&#xff0c;就很抓狂&#xff0c;明明是哪都没操作错误&#x…...

网络六边形受到攻击

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 抽象 现代智能交通系统 &#xff08;ITS&#xff09; 的一个关键要求是能够以安全、可靠和匿名的方式从互联车辆和移动设备收集地理参考数据。Nexagon 协议建立在 IETF 定位器/ID 分离协议 &#xff08;…...

多云管理“拦路虎”:深入解析网络互联、身份同步与成本可视化的技术复杂度​

一、引言&#xff1a;多云环境的技术复杂性本质​​ 企业采用多云策略已从技术选型升维至生存刚需。当业务系统分散部署在多个云平台时&#xff0c;​​基础设施的技术债呈现指数级积累​​。网络连接、身份认证、成本管理这三大核心挑战相互嵌套&#xff1a;跨云网络构建数据…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻

在如今就业市场竞争日益激烈的背景下&#xff0c;越来越多的求职者将目光投向了日本及中日双语岗位。但是&#xff0c;一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧&#xff1f;面对生疏的日语交流环境&#xff0c;即便提前恶补了…...

Java 语言特性(面试系列2)

一、SQL 基础 1. 复杂查询 &#xff08;1&#xff09;连接查询&#xff08;JOIN&#xff09; 内连接&#xff08;INNER JOIN&#xff09;&#xff1a;返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...

MFC内存泄露

1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...

大型活动交通拥堵治理的视觉算法应用

大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动&#xff08;如演唱会、马拉松赛事、高考中考等&#xff09;期间&#xff0c;城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例&#xff0c;暖城商圈曾因观众集中离场导致周边…...

UE5 学习系列(三)创建和移动物体

这篇博客是该系列的第三篇&#xff0c;是在之前两篇博客的基础上展开&#xff0c;主要介绍如何在操作界面中创建和拖动物体&#xff0c;这篇博客跟随的视频链接如下&#xff1a; B 站视频&#xff1a;s03-创建和移动物体 如果你不打算开之前的博客并且对UE5 比较熟的话按照以…...

渗透实战PortSwigger靶场-XSS Lab 14:大多数标签和属性被阻止

<script>标签被拦截 我们需要把全部可用的 tag 和 event 进行暴力破解 XSS cheat sheet&#xff1a; https://portswigger.net/web-security/cross-site-scripting/cheat-sheet 通过爆破发现body可以用 再把全部 events 放进去爆破 这些 event 全部可用 <body onres…...

渲染学进阶内容——模型

最近在写模组的时候发现渲染器里面离不开模型的定义,在渲染的第二篇文章中简单的讲解了一下关于模型部分的内容,其实不管是方块还是方块实体,都离不开模型的内容 🧱 一、CubeListBuilder 功能解析 CubeListBuilder 是 Minecraft Java 版模型系统的核心构建器,用于动态创…...