《架构师之路:架构设计中的100个知识点》
44.ABA问题
《库存扣减异常怎么办?(41)》采用CAS思想,两行代码解决并发扣减异常。
有童鞋留言:那ABA问题呢?
今天和大家聊聊CAS下的ABA问题。
前文回顾:
如何巧用CAS机制,1行代码搞定并发导致的库存异常问题?
将库存设置接口执行的:
update stock set num=$y where sid=$sid
升级为:
update stock set num=$num_new where sid=$sid
and num=$num_old
画外音:加了一个初始条件比对。
这样的话,两个并发的库存设置,只会有一个成功,保证了数据的一致性。
可能存在什么问题?
CAS乐观锁机制确实能够提升吞吐,并保证一致性,但在极端情况下可能会出现ABA问题。
什么是ABA问题?
考虑如下操作:
1. 并发1(上):获取出数据的初始值是A,后续计划实施CAS乐观锁,期望数据仍是A的时候,修改才能成功;
2. 并发2:将数据修改成B;
3. 并发3:将数据修改回A;
4. 并发1(下):CAS乐观锁,检测发现初始值还是A,进行数据修改;
上述并发环境下,并发1在修改数据时,虽然还是A,但已经不是初始条件的A了,中间发生了A变B,B又变A的变化,此A已经非彼A,数据却成功修改,可能导致错误,这就是CAS引发的所谓的ABA问题。
库存操作,出现ABA问题并不会对业务产生影响。
再看一个堆栈操作的例子:
并发1(上):读取栈顶的元素为“A1”;
并发2:进行了2次出栈;
并发3:又进行了1次出栈;
并发1(下):实施CAS乐观锁,发现栈顶还是“A1”,于是修改为A2;
此时会出现系统错误,因为此“A1”非彼“A1”。
为什么会导致ABA问题?
ABA问题导致的原因,是CAS过程中只简单进行了“值”的校验,再有些情况下,“值”相同不会引入错误的业务逻辑(例如库存),有些情况下,“值”虽然相同,却已经不是原来的数据了。
ABA问题如何优化?
CAS不能只比对“值”,还必须确保的是原来的数据,才能修改成功。
最常见的实践是:通过“版本号”比对,一个数据一个版本,版本变化,即使值相同,也不应该修改成功。
库存的并发读写例子,引入版本号的具体实践如下:
(1)库存表由
stock(sid, num)
升级为
stock(sid, num, version)
(2)查询库存时同时查询版本号
select num from stock where sid=$sid
升级为
select num, version from stock where sid=$sid
假设有并发操作,都会将版本号查询出来
(3)设置库存时,必须版本号相同,并且版本号要修改
旧版本“值”比对CAS
update stock
set num=num_new where sid=sid
and num=$num_old
升级为“版本号”比对CAS
update stock
set num=num_new, version=version_new
where sid=sid and version=version_old
此时假设有并发操作,第一个操作,比对版本号成功,于是把库存和版本号都进行了修改。
同时存在的第二个并发操作,比对版本号发生了变化,也是库存应该修改失败。
稍作总结
1. select&set业务场景,在并发时会出现一致性问题;
2. 基于“值”的CAS乐观锁,可能导致ABA问题;
3. CAS乐观锁,必须保证修改时的“此数据”就是“彼数据”,应该由“值”比对,优化为“版本号”比对;
另外,具体到库存业务,如果之前没有设计版本号,使用“值对比”就可以了,没有必要大费周章升级为为“版本号对比”。技术方案,是为了解决问题,而不是为了炫技。
知其然,知其所以然。
思路比结论更重要。