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

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.dtcms.com/a/359418.html

相关文章:

  • Linux 中进入 root 权限
  • dayjs 常用方法总结
  • Nginx 502 Bad Gateway:从 upstream 日志到 FastCGI 超时复盘
  • 【大数据技术实战】流式计算 Flink~生产错误实战解析
  • vim-plugin AI插件
  • Playwright Python教程:基础篇
  • 秋招笔记-8.31
  • 在集群级别应用 Pod 安全标准
  • 【MySQL】练习12-4:启用GTID并配置循环复制
  • Conda相关的用法
  • 人工智能之数学基础:连续型随机变量
  • Langflow 评估与迭代技术深度分析
  • 3DES加解密的算法Java Python Golang
  • 【大模型面试宝典之微调篇】(一)
  • 算法之链表
  • Windows中如何将Docker安装在E盘并将Docker的镜像和容器存储在E盘的安装目录下
  • 四数之和的一些判断怎么记忆
  • 《哲思:生命与宇宙的终极意义》
  • 分治思想与分治算法的区别
  • C6.7:输入电阻的负载效应及其CE负反馈放大器
  • 【线性代数基础 | 那忘算9】基尔霍夫(拉普拉斯)矩阵 矩阵—树定理证明 [详细推导]
  • ICode总线原理
  • Playwright Python 教程:高级篇
  • JDK 22 Windows 64位安装教程(含环境变量配置+验证步骤+附安装包下载)
  • Qwen3_moe模型代码解析
  • 数据结构与算法:线段树(三):维护更多信息
  • 运筹说 第141期 | 启发式算法:用简单规则、破解复杂问题
  • WEB漏洞挖掘篇(一) 基本概念、十大常見WEB漏洞
  • 自底向上了解CPU的运算
  • Google 的 Agent2Agent 协议 (A2A):带示例的指南