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高并发场景(银行转账问题)
最近面试问到了银行转账的高并发问题,回答的不是很理想,小编整理了下,题目大概如下: 有一张银行账号表(银行账号字段、金额字段),A账号要给B账号转账,A扣款,B收款&#x…...
TypeScript 工具类型
这些工具类型是 TypeScript 提供的强大功能,用于操作和转换类型。下面是每个工具类型的简要说明和示例: 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作为项目类型和构建系统。指定项目名称和位置,完成设置。 2.创建Android Library模块 官方文档:创建 Android 库 | Android Studio | Android De…...
鸿蒙应用开发者高级认证指南及参考资料整理(含详细参考答案)
如何报名鸿蒙应用开发者高级认证 报名链接:点击这里进行报名。报名步骤: 点击上述链接进入报名页面。选择“立即报名”。在课程内容中找到“HarmonyOS应用开发者高级认证”,点击进入。点击“参加考试”,随后即可开始考试。考试注意事项 实名认证:考试前,请务必完成实名认…...
数据匿名化技术
不同的数据匿名化技术可用于多种行业,旨在从数据流中获取有用的见解,同时确保满足数据保护标准和法规的合规要求。 数据脱敏(Data Masking) 数据脱敏,又称数据漂白、数据去隐私化或数据变形,指的是对数据集…...
HTML学习笔记汇总
整理一些常见问题的Links,不定期更新。 Html生成自定义函数的图形(2024/5/10)-CSDN博客 HTML中插入图片(2024/5/10)-CSDN博客 Html给网页加icon图标_html icon-CSDN博客...
初始JSVMP
1.初始JSVMP JSVMP是"JavaScript Virtual Machine Protection"的缩写,是一种前端代码虚拟化保护技术。它的核心思想是在JavaScript代码保护过程中引入代码虚拟化,将目标代码转换成自定义的字节码,这些字节码只有特殊的解释器才能识…...
【机器学习数据可视化-04】Pyecharts数据可视化宝典
一、引言 在大数据和信息爆炸的时代,数据可视化成为了信息传递和展示的关键手段。通过直观的图表和图形,我们能够更好地理解数据,挖掘其背后的信息。Pyecharts,作为一款基于Python的数据可视化库,凭借其丰富的图表类型…...
通过 Java 操作 redis -- zset 有序集合基本命令
目录 使用命令 zadd,zrange 使用命令 zcard 使用命令 zrem 使用命令 zscore 使用命令 zrank 关于 redis zset 有序集合类型的相关命令推荐看Redis - Zset 有序集合 要想通过 Java 操作 redis,首先要连接上 redis 服务器,推荐看通过 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块分三部分:声明部分、执行部分、异常处理部分; 示例: declare --变量声明 v_sno varchar2(10) : ‘04001’; v_cno varchar2(10) :‘001’; v_grade number : 90; begin --程序入口 insert…...
(docker)进入容器后如何使用本机gpu
首次创建容器,不能直接使用本机gpu 在系统终端进行如下配置: 1.安装NVIDIA Container Toolkit 进入Nvidia官网Installing the NVIDIA Container Toolkit — NVIDIA Container Toolkit 1.15.0 documentation,安装NVIDIA Container Toolkit …...
java基础知识点总结2024版(8万字超详细整理)
java基础知识点总结2024版(超详细整理) 这里写目录标题 java基础知识点总结2024版(超详细整理)java语言的特点1.简单性2.面向对象3.分布式4.健壮性5.安全性6.体系结构中立7.可移植性8.解释性9.多线程10.动态性 初识java中的main方…...
vue中使用element的i18n语言转换(保姆式教程-保证能用)
话不多说,先看效果:预览地址: https://sandm00.github.io/i18n-switch/#/ 1、项目中需要使用的插件,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. 单例模式 单例模式有两种实现方式: 1.1 饿汉模式(Eager Initialization):在类加载时就创建单例实例,无论是否需要使用该实例。 饿汉模式在类加载时就创建单例实例,无论是否需要使用该实例。 饿汉模式…...
css backdrop-filter 实现背景滤镜
官方给出的定义是:backdrop-filter属性允许您将图形效果(如模糊或颜色偏移)应用于元素后面的区域。因为它适用于元素后面的所有内容,所以要查看元素或其背景的效果,需要透明或部分透明。 大致分为以下10种:…...
AR人脸道具SDK解决方案,实现道具与人脸的自然融合
AR人脸道具SDK解决方案,实现道具与人脸的自然融合美摄科技以其卓越的技术实力和创新能力,为企业带来了全新的AR人脸道具SDK解决方案。这一解决方案将为企业打开全新的市场机会,为用户带来前所未有的互动体验。 颠覆传统,开启AR人…...
Windows安装RabbitMQ教程(附安装包)
需要两个安装包 Erlang 安装包: https://download.csdn.net/download/Brevity6/89274663 (自己从官网下载也可以) RabbitMQ Windows 安装包: https://download.csdn.net/download/Brevity6/89274667 (自己从官网下载也可以) Erlang安装 Erlang安装傻瓜式下一…...
这个问题无人能解,菜鸟勿进
前言 这是陈旧已久的草稿2021-06-23 23:25:12 现在2024-5-12 21:53:46,发布到[逻辑题]专栏中。 一、问题 1.描述: 在我的世界中建个红石电路 2.需求: 五个灯A、B、C、D、E、F 五个开关a、b、c、d、e、f、总开关 3.要求: 总开关…...
揭秘高效引流获客的艺术:转化技巧大公开
在数字化营销的海洋中,每个企业都如同一艘努力航行的船,而流量便是推动船只前行的风帆。如何有效吸引并获取潜在客户,即所谓的“引流获客”,已成为企业市场营销策略中不可或缺的一环。本文将详细探讨几种实用且高效的引流获客技巧…...
ai如何助力github项目管理:从智能生成readme到自动编排changelog
今天在准备一个AI图像识别工具的开源项目时,突然意识到GitHub仓库初始化其实可以很智能。以前手动创建目录、写README的日子太费时间了,现在用AI辅助开发,整个过程流畅得像有个技术助理在身边。下面记录下我的实践过程: 智能仓库…...
ESP32智能硬件开发实战指南:从环境搭建到AI功能落地
ESP32智能硬件开发实战指南:从环境搭建到AI功能落地 【免费下载链接】xiaozhi-esp32 An MCP-based chatbot | 一个基于MCP的聊天机器人 项目地址: https://gitcode.com/GitHub_Trending/xia/xiaozhi-esp32 如何在复杂的硬件开发中快速实现AI功能集成…...
微信聊天记录年度报告怎么生成?实测这款工具,一键导出HTML还能做可视化分析
从数据到故事:用专业工具打造你的微信聊天年度可视化报告 微信聊天记录早已不只是简单的文字交流,它们承载着人际关系的发展脉络、重要时刻的见证以及日常生活的点滴。将这些碎片化的对话转化为结构化的年度报告,不仅能帮助我们回顾过去一年…...
煤矿智能化验收必备:针对睡岗、离岗识别的AI视觉解决方案
在煤矿智能化建设中,确保井下作业人员的安全与规范操作是重中之重。睡岗、离岗等违规行为不仅影响生产效率,更可能引发严重的安全事故。因此,在煤矿智能化验收环节,一套高效精准的针对睡岗、离岗识别的AI视觉解决方案不可或缺。一…...
2026好用的企业内网通讯软件:哪家更适合你?
2026年,企业数字化办公的浪潮已进入深水区。随着《数据安全法》等法规的深度落地,以及企业对核心数字资产掌控权的重视,一个显著的趋势正在发生:企业通讯市场正在经历一场深刻的“向内回归”——私有化部署正从传统行业的无奈之选…...
高效代码分析利器:cloc工具全场景使用指南
1. 为什么你需要cloc这个代码统计神器 第一次接手一个遗留项目时,我盯着密密麻麻的目录树发愁:这堆代码到底有多少实际内容?注释占比多少?不同语言的文件各有多少?直到同事推荐了cloc工具,输入一行命令就得…...
番茄小说离线阅读解决方案:打造你的个人数字图书馆
番茄小说离线阅读解决方案:打造你的个人数字图书馆 【免费下载链接】fanqienovel-downloader 下载番茄小说 项目地址: https://gitcode.com/gh_mirrors/fa/fanqienovel-downloader 在数字阅读时代,你是否曾遇到过这样的困扰:网络信号不…...
steam_api.dll是什么文件?全面解析其作用与安全修复方法
不少玩家在启动Steam游戏时,都曾被“无法启动此程序,因为计算机中丢失steam_api.dll”这样的提示拦在门外。看着这串乱码般的文件名,第一反应通常是:这是什么?为什么没了它游戏就不动了?别急,这…...
基于Phi-4-mini-reasoning的智能运维异常检测系统
基于Phi-4-mini-reasoning的智能运维异常检测系统 1. 运维监控的痛点与智能化需求 运维团队每天都要面对海量的日志数据、监控指标和系统告警。传统监控系统往往只能做到简单的阈值告警,当系统出现异常时,运维人员需要手动翻阅成千上万条日志ÿ…...
打造掌机媒体中心:wiliwili跨设备播放全攻略
打造掌机媒体中心:wiliwili跨设备播放全攻略 【免费下载链接】wiliwili 专为手柄控制设计的第三方跨平台B站客户端,目前可以运行在PC全平台、PSVita、PS4 和 Nintendo Switch上 项目地址: https://gitcode.com/GitHub_Trending/wi/wiliwili 在移动…...
