架构与思维:漫谈高并发业务的CAS及ABA

news/2024/10/10 8:09:48

1 高并发场景下的难题

1.1 典型支付场景

这是最经典的场景。支付过程,要先查询买家的账户余额,然后计算商品价格,最后对买家进行进行扣款,像这类的分布式操作,
如果是并发量低的情况下完全没有问题的,但如果是并发扣款,那可能就有一致性问题。在高并发的分布式业务场景中,类似这种 “查询+修改” 的操作很可能导致数据的不一致性。
image

1.2 在线下单场景

同理,买家在电商平台下单,往往会涉及到两个动作,一个是扣库存,第二个是更新订单状态,库存和订单一般属于不同的数据库,需要使用分布式事务保证数据一致性。
image

1.3 跨行转账场景

跨行转账问题也是一个典型的分布式事务,用户A同学向B同学的账户转账500,要先进行A同学的账户-500,然后B同学的账户+500,既然是 不同的银行,涉及不同的业务平台,为了保证这两个操作步骤的一致,数据一致性方案必然要被引入
image

2 CAS方案

分布式CAS(Compare-and-Swap)模式就是一种无锁化思想的应用,它通过无锁算法实现线程间对共享资源的无冲突访问,既保证性能高效,有保证数据的强一致性,避免了上面集中问题的产生。
CAS模式包含三个基本操作数:内存地址V、旧的预期值A和要修改的新值B。在更新一个变量的时候,只有当变量的预期值A和内存地址V当中的实际值相同时,才会将内存地址V对应的值修改为B。

我们以 1.1节典型支付场景 作为例子分析(参考下图):

  • 初始余额为 800
  • 业务1和业务2同时查询余额为800
  • 业务1执行购买操作,扣减去100,结果是700,这是新的余额。理论上只有在原余额为800时,扣减的Action才能执行成功。
  • 业务2执行生活缴费操作(比如自动交电费),原余额800,扣减去200,结果是600,这是新的余额。理论上只有在原余额为800时,扣减的Action才能执行成功。可实际上,这个时候数据库中的金额已经变为600了,所以业务2的并发扣减不应该成功。

根据上面的CAS原理,在Swap更新余额的时候,加上Compare条件,跟初始读取的余额比较,只有初始余额不变时,才允许Swap成功,这是一种常见的降低读写锁冲突,保证数据一致性的方法。
image

3 引出ABA问题

在CAS(Compare-and-Swap)操作中,ABA问题是一个常见的挑战。这边假设三个操作数——内存位置(V)、预期原值(A)和新值(B)。
ABA问题是指当某个线程读取一个共享变量V的值为A,之后准备将其更新为B时,另一个线程可能已经将其从A改为了B,然后又改回了A。
此时,当前线程仍认为V的值是原始的A,因此CAS操作会将V的值更新为B,但实际上V的值已经被其他线程改变过。

image

它有如下危害:

1. 数据一致性受损,并导致业务逻辑错误
在复杂的业务逻辑中,共享变量的值往往代表了某种业务状态或条件。ABA问题可能导致这些状态或条件被意外地改变,从而引发业务逻辑错误,如库存超卖、资金重复发放等
★ 以下的图详细描述了ABA是怎么导致库存逻辑出错的:
image

2. 难以调试与定位
ABA问题通常发生在多线程环境下,且其触发条件较为隐蔽。因此,当系统出现由ABA问题导致的异常时,往往难以快速定位问题原因,增加了调试的复杂性和时间成本。

4 不同维度的处理方式

ABA出现的原因,是CAS的过程中,只关注Value值的校验。但是忽略了这个值还是不是之前的那个值,可以参考上面的库存图例。所以某些情况下,Value虽然相同,却已经不是原来的数据了。

解决方案:CAS不能只比对 Value,还必须确保的是原来的数据,才能修改成功。
一般的做法是,给 Value 设置一个Version(版本号),用来比对,一个数据一个版本,每次数据变化的时候版本跟随变化,这样的话就不会随随便便修改成功。

4.1 应用程序层

Java中的java.util.concurrent.atomic包提供了解决ABA问题的工具类。
在Go语言中,通常使用sync/atomic包提供的原子操作来处理并发问题,并引入版本号或时间戳的概念。
示例代码如下:

type ValueWithVersion struct {  Value     int32  Version   int32  
}  var sharedValue atomic.Value // 使用atomic.Value来存储ValueWithVersion的指针  func updateValue(newValue, newVersion int32) bool {  current := sharedValue.Load().(*ValueWithVersion)  if current.Value == newValue && current.Version == newVersion {  // CAS操作:只有当前值和版本号都匹配时,才更新值  newValueWithVersion := &ValueWithVersion{Value: newValue, Version: newVersion + 1}  sharedValue.Store(newValueWithVersion)  return true  }  return false  
}  

4.2 数据层

  1. CAS策略
update stock set num_val=$num_new_val where sid=$sid and num_val=$num_old_val
  1. CAS策略+Version,避免ABA问题
# 这边注意,有了version,就没必要再比较old_val了
update stock set num=$num_new_val, version=$version_new where sid=$sid and version=$version_old

5 总结

  1. 高并发下的难题:支付、下单、跨行转账
  2. CAS方案以及引发的ABA问题
  3. 不同维度的处理方式:应用层、数据层

啦啦啦啦啦啦,不写了去跑步啦

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/69657.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

The Number of Pairs

算法 数据范围一眼数学题 然而考场并没有思路 这一类题显然要将 \(\gcd{(a, b)}\) 消掉或者表示 ( \(\rm{lcm}{(a, b)}\) 可以用 \(\gcd{(a, b)}\) 表示) 考虑 \(a = \gcd{(a, b)} * k_1\) 和 \(b = \gcd{(a, b)} * k_2\) 开始化简然后可以求出 \(\rm{lcm}{(a, b)} = \frac{k_1…

.NET周刊【9月第5期 2024-09-29】

国内文章 Windows 调试工具课程 https://www.cnblogs.com/lindexi/p/18421353 本文是关于如何使用Windows调试工具解决软件故障的课程记录,适合初学者。作者介绍了解决软件崩溃的策略,从用户反馈开始,利用事件查看器和任务管理器等工具找出问题根源。事件查看器可以给出软件…

.NET 9 RC 2正式发布

距离最终版本还有一个月的时间,Microsoft 已经交付了 .NET 9 的第二个也是最后一个候选版本。.NET 团队在公告帖子中写道[1],“当我们为 11 月的 .NET 9 正式发布 (GA) 版本做准备时,我们正在对性能、稳定性和任何其他优化进行最后的润色,使其成为 .NET 9 的最佳版本。.N…

读数据工程之道:设计和构建健壮的数据系统04数据工程生命周期(下)

数据工程生命周期(下)1. 获取 1.1. 在了解数据源、所用源系统的特征以及数据的存储方式之后,你需要收集数据 1.2. 数据工程生命周期的下一阶段是从源系统中获取数据1.2.1. 源系统和获取代表了数据工程生命周期中最重要的瓶颈1.2.2. 源系统通常不在你的直接控制范围内,可能会…

两台iStoreOS路由器通过wireguard实现异地组网

一、前言 我在家中和单位宿舍申请了两条联通千兆宽带,每条均有公网ip,如何实现更多玩法呢?最近折腾了一下异地组网,这里简单记录一下 环境:路由器A,内网ip为192.168.1.1,系统为iStoreOS, 路由器B,内网ip为192.168.0.1,系统和版本号同上 至少有一条具有公网ip的宽带,…

1panel搭建frp服务端并使用openresty反向代理实现https访问

前言 这次国庆节回老家发现家里的路由器居然是我去年带过去的斐讯K2p,已经刷了openwrt,于是想着有没有更多玩法?因为家里的宽带是移动宽带,没有公网IP,所以来折腾一下frp内网穿透。 我想实现的目标是:通过不同的三级域名,来访问不同的服务。例如,访问https://op.frp.xx…

004、v3admin学习,使用ci4搭建后端服务器

1、按照php环境和composer,输入cmd的composer命令,版本是2.7.9 2、在工作目录,输入命令行composer create-project codeigniter4/appstarter ci4 ,会全自动创建工程 3、把composer下来的文件,拷贝到外面工程中。 4、用phpstorm打开工程,更新一下依赖包 5、用小皮桌面开启p…

UNRAID下安装Virtual DSM

本文基于【完结】Virtual DSM 逆向笔记 (基于libvirt的安装及升级) (jxcn.org) 感谢chk-jxcn的分享! 一、安装镜像的准备 链接:https://pan.baidu.com/s/16I89NHPTW6TDx7ACh67yiA 提取码:7hkq 下载下来的镜像是原作者chk-jxcn(jxcn.org)从 DSM 中提取出的 VDSM 7.0 的安装镜…