《架构师之路:架构设计中的100个知识点》
43.CAS应用案例
《库存扣减异常怎么办?(41)》采用CAS思想,两行代码解决并发扣减异常。
《redis解决库存并发扣减异常问题?(42)》采用CAS思想,借助redis解决并发扣减异常。
有童鞋留言,CAS还有其他应有场景吗?
场景很多,今天分享一个巧用CAS实现分布式全局唯一ID生成的场景。
如何快速生成全局唯一ID?
可以借助DB自增键(auto inc id),插入一条记录,生成一个ID:
这个方案复用了数据库的特性,其优点为:
1. 无需写额外代码;
2. 全局唯一;
3. 绝对递增;
4. 递增ID的步长确定;
业务早期“并发量小”,追求“快速实现”,这么玩没问题。
这种方案有什么缺点?
1. 数据库中记录数较多;
2. 生成ID的性能,取决于数据库插入性能;
3. 并发量大了,数据库扛不住;
可以怎么优化?
1. DB只保留max-id一条记录;
2. 增加一层服务,采用批量生成的方式降低数据库的写压力,提升整体性能;
更具体的操作如下:
步骤一,id-s服务首先拉取当前的max-id。
select max_id from T;
步骤二,批量获取一批ID,放到id-s内存里,并将max-id写回数据库。
update T set max_id=200;
如上图所示,id-s拿到了[100, 200]这一批ID,上游在获取ID时,不用每次都插入数据库,而是分配完100个ID后,再修改max-id的值,这样分配ID的整体性能就增加了100倍。
这种方案还有什么缺点?
服务没有做HA,无法保证高可用。需要冗余服务,做集群保证高可用。
冗余服务可能带来什么新的问题?
和高并发库存扣减出现的问题类似,冗余了服务后,多个服务在启动过程中,进行ID批量申请时,可能由于并发导致数据不一致。
select max_id from T;
步骤一:两个id-s同时拿到了max-id为100;
update T set max_id=200;
步骤二:两个id-s同时对数据库的max-id进行写回;
写回max-id成功后,这两个id-s都以为自己拿到了[100,200]这一批ID,导致集群会生成重复的ID。
导致bug的原因在哪里?
是并发写回时,没有对max-id的初始值进行比对:
1. id-s1写回max-id=200成功的条件是,max-id必须等于100;
2. id-s2写回max-id=200成功的条件是,max-id也必须等于100;
而实际情况是:
1. id-s1写回时,max-id是100,理应写回成功;
2. id-s2写回时,max-id已经被改成了200,不应该写回成功;
如何巧用CAS修复?
在写回时对max-id的初始条件进行比对,就能避免数据的不一致,写回SQL由:
update T set max_id=200;
升级为:
update T set max_id=200
where max_id=100;
升级之后,s1和s2只会有一个成功。另一个失败之后,重新查询数据库得到新的max-id为200,再次申请[200,300]的ID,就能够成功。
CAS优化方案的优点是:
1. 能够通过水平扩展的方式,达到分布式ID生成服务的无限性能;
2. 方案简洁性;
3. 保证不会生成重复的ID;
知其然,知其所以然。
思路比结论更重要。
==全文完==