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.要求: 总开关…...

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

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...
【磁盘】每天掌握一个Linux命令 - iostat
目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat(I/O Statistics)是Linux系统下用于监视系统输入输出设备和CPU使…...

【项目实战】通过多模态+LangGraph实现PPT生成助手
PPT自动生成系统 基于LangGraph的PPT自动生成系统,可以将Markdown文档自动转换为PPT演示文稿。 功能特点 Markdown解析:自动解析Markdown文档结构PPT模板分析:分析PPT模板的布局和风格智能布局决策:匹配内容与合适的PPT布局自动…...

html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
目录 一、👨🎓网站题目 二、✍️网站描述 三、📚网站介绍 四、🌐网站效果 五、🪓 代码实现 🧱HTML 六、🥇 如何让学习不再盲目 七、🎁更多干货 一、👨…...
jmeter聚合报告中参数详解
sample、average、min、max、90%line、95%line,99%line、Error错误率、吞吐量Thoughput、KB/sec每秒传输的数据量 sample(样本数) 表示测试中发送的请求数量,即测试执行了多少次请求。 单位,以个或者次数表示。 示例:…...

Web后端基础(基础知识)
BS架构:Browser/Server,浏览器/服务器架构模式。客户端只需要浏览器,应用程序的逻辑和数据都存储在服务端。 优点:维护方便缺点:体验一般 CS架构:Client/Server,客户端/服务器架构模式。需要单独…...
深入浅出Diffusion模型:从原理到实践的全方位教程
I. 引言:生成式AI的黎明 – Diffusion模型是什么? 近年来,生成式人工智能(Generative AI)领域取得了爆炸性的进展,模型能够根据简单的文本提示创作出逼真的图像、连贯的文本,乃至更多令人惊叹的…...

软件工程 期末复习
瀑布模型:计划 螺旋模型:风险低 原型模型: 用户反馈 喷泉模型:代码复用 高内聚 低耦合:模块内部功能紧密 模块之间依赖程度小 高内聚:指的是一个模块内部的功能应该紧密相关。换句话说,一个模块应当只实现单一的功能…...

JDK 17 序列化是怎么回事
如何序列化?其实很简单,就是根据每个类型,用工厂类调用。逐个完成。 没什么漂亮的代码,只有有效、稳定的代码。 代码中调用toJson toJson 代码 mapper.writeValueAsString ObjectMapper DefaultSerializerProvider 一堆实…...

基于单片机的宠物屋智能系统设计与实现(论文+源码)
本设计基于单片机的宠物屋智能系统核心是实现对宠物生活环境及状态的智能管理。系统以单片机为中枢,连接红外测温传感器,可实时精准捕捉宠物体温变化,以便及时发现健康异常;水位检测传感器时刻监测饮用水余量,防止宠物…...