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

BigDecimal账户分布式原子操作

文章目录

  • 1.前言
  • 2.解决方案
    • 2.1BigDecimal分布式原子操作实现
    • 2.2注意事项
    • 2.3其他方案
      • 2.3.1基于数据库的解决方案
      • 2.3.2使用分布式协调服务
      • 2.3.3分布式计算框架
      • 2.3.4 其它方案比较
  • 3.总结

1.前言

    在微服务分布式大行其道的今天,在mysql数据库频繁使用的今天,假设让你设计一个账户系统,账户充值扣款消费退款等相关的需求,还是使用传统的mysql数据库的表来实现吗?首先说一下这种设计:如果是在最终一致性的调用下这种实现借助MQ消息队列的顺序单一消费来最终达到账户数据的最终一致性是没有啥问题的,但是如果使用的是强调用,调用接口立马就操作账户,这种会有一个什么样的问题,如果使用账户的人多了之后,并发量一上来,就算是加了分布式锁,但是也会由于myslq的事务延迟导致,账户的查询不是最新的数据导致账户数据不对,这也是mysql表设计账户会存在的巨大隐患,之前在搞乐企开票的时候就吃过这个大亏了,发票号码的计算在myslq的表中来搞的,结果就导致发票号码生成重复的问题,这个问题也是由于mysql的事务延迟导致的,在高并发下就会有问题。

2.解决方案

    采用一个分布式可以原子操作BigDecimal的结构来将账户表中的账户余额数据加载到这个结构上分布式多节点下原子操作

2.1BigDecimal分布式原子操作实现

local key = KEYS[1]
local delta = tonumber(ARGV[1])
local initValue = ARGV[2]
-- 获取当前值
local current = redis.call('GET', key)
if not current and initValue and initValue ~= "" then-- 如果 key 不存在,使用传入的初始值设置initValue = tonumber(ARGV[2])redis.call('SET', key, tostring(initValue))current = initValue
else-- 如果 key 不存在,返回 nilif not current thenreturn nilendcurrent = tonumber(current)
end
-- 计算新值
local newValue = current + delta
-- 判断是否余额不足
if newValue < 0 thenreturn "insufficient_balance"
end
-- 设置key的值并返回结果
redis.call('SET', key, tostring(newValue))
return string.format("%.2f", newValue)

    这里只分享这个lua脚本了,至于集成使用也很简单,这里就不做过多的讲解了。

2.2注意事项

    在使用这个方案的时候需要注意因为业务接口调用可能会有异常导致mysql数据库中的账户表事务回滚了,但是redis中的账户key已经变动了,所以还需要给redis中的账户key对应做一个回退补偿,加了需要减回去,减了需要加回来,加/减几次需要逆向做一个补偿,保持redis中的key的数据和mysql表中的数据是一致的,然后还需要配合上分布式锁,直接锁账户id,这种这个账户下所有人操作都是安全。

2.3其他方案

2.3.1基于数据库的解决方案

    如果你的应用已经基于某个关系型数据库或NoSQL数据库,那么可以直接利用数据库提供的事务管理功能来实现BigDecimal的原子加减。大多数现代数据库都支持ACID属性,能够确保并发情况下的数据一致性。

  • 在SQL数据库中,可以通过UPDATE语句直接对字段进行增加或减少操作,例如:UPDATE account SET balance = balance + ? WHERE id = ?

  • 对于一些NoSQL数据库,如MongoDB,也提供了类似的原子更新操作符,比如$inc操作符用于递增或递减数值。

    可以使用mybatisPlus的乐观锁来实现:

在SQL数据库中,可以通过UPDATE语句直接对字段进行增加或减少操作,例如:

UPDATE account SET balance = balance + ? WHERE id = ?

    可以使用mybatisPlus的乐观锁来实现,刚一想,之前的姿势不对,使用数据库的乐观锁的姿势不对,更新是要去写upate的接口来更新的,不要去把数据查出来在去算了更新进去,这种你查出来的数据在高并发下mysql事务延迟,查询的数据不是最新的,所以这种姿势是错误的,终于又想通了之前是哪里姿势有问题了。

2.3.2使用分布式协调服务

    一种常见的方式是使用像Apache ZooKeeper、etcd或者Consul这样的分布式协调服务。这些工具提供了诸如锁机制(Locks)、选举(Leader Election)等功能,可以帮助你安全地执行原子操作。

  • ZooKeeper: 可以创建一个持久节点代表你的共享资源(如一个存储了BigDecimal值的节点),然后利用ZooKeeper的API提供的原子操作或通过获取分布式锁来对这个值进行修改。
  • etcd/Consul: 类似地,你可以使用事务特性来确保读取和更新值的过程是原子性的。比如etcd支持的Compare-And-Swap (CAS) 操作可以用来实现这一点。

2.3.3分布式计算框架

    如果是在大数据处理场景下,考虑使用如Apache Spark这样的分布式计算框架,它提供了强大的RDD(Resilient Distributed Datasets)或DataFrame API,可以轻松地对大规模数据集执行聚合操作,包括对BigDecimal类型的加法和减法运算。

2.3.4 其它方案比较

方案描述是否推荐
**ZooKeeper CAS 重试机制性能差、吞吐量低
基于数据库乐观锁使用版本号字段,在更新时检查
ETCD Compare-and-Swap (CAS)支持事务和原子操作,适合云原生系统
Hazelcast 分布式 Map内存级一致性,提供原子更新方法

3.总结

    上面这种思路其实还可以拓展一下,把账户相关的数据使用JSON的方式使用lua脚本在redis中执行,lua脚本解析JSON原子处理账户的各种相关的操作,去除mysql表数据存储账户数据,这种就可以不用逆向补偿操作了,但是如果lua脚本过长还是会影响性能,lua解析JSON需要集成一些开源的库到redis上,需要重启redis服务,所以这个就比较麻烦,redis唯一一个自带解析JSON的库是cjson可以不用安装其他JSON解析的库来实现,写也比较复杂,但是有必要还是可以尝试一下这种进阶的方案的,本次分享到此结束,希望我的分享对你有所启发和帮助,请一键三连,么么么哒!

http://www.xdnf.cn/news/19316.html

相关文章:

  • IOT安全学习之IoT_Sec_Tutorial
  • 历史数据分析——寒武纪
  • Wi-Fi技术——MAC特性
  • 【人工智能99问】Qwen3中的QK归一化是什么?(34/99)
  • LeetCode 3459.最长 V 形对角线段的长度:记忆化搜索——就一步步试
  • 备份压缩存储优化方案:提升效率与节省空间的完整指南
  • 鸿蒙开发入门:ArkTS 运算符与分支循环全解析(含实战案例 + 避坑指南)
  • ES6 面试题及详细答案 80题 (13-21)-- 数组与字符串扩展
  • Zynq开发实践(FPGA之平台免费IP)
  • GitHub Spark深度体验:是革命前夜,还是又一个“大厂玩具”?
  • 浅层与深层语义分析的NLP进化论
  • libmodbus移植
  • spi总线
  • Python 实战:内网渗透中的信息收集自动化脚本(6)
  • 【Unity3D实例-功能-切换武器】切换武器(一)动画配置
  • FPGA CIC抽取滤波器设计
  • HarmonyOS 应用开发:基于API 12及以上的新特性与实践
  • TensorFlow 面试题及详细答案 120道(81-90)-- 其他框架/工具
  • 内核Sched调度关于find_idlest_cpu选核逻辑
  • OpenCV 图像处理实战与命令行参数配置:从轮廓检测到模板匹配
  • AI 重构内容创作:从文案生成到视频剪辑,创作者该如何与 AI 协同共生?
  • 一个投骰子赌大小的游戏
  • H264几个参数说明
  • Maya基础:烘焙动画
  • 网络爬虫是自动从互联网上采集数据的程序
  • VSCode的launch.json配置文件在C++项目调试中的全面应用
  • VB.NET 多次添加字符串数据,再转换成一个数组
  • 设计模式概述:为什么、是什么与如何应用
  • 【开题答辩全过程】以 纳雍县咚咚屋服装租赁管理系统为例,包含答辩的问题和答案
  • Java全栈开发面试实录:从基础到微服务的实战解析