巧用CAS解决数据一致性问题

在高并发的分布式环境下,对于数据的查询与修改容易引发一致性问题,本文将分享一种非常简单但有效的优化方法。

一、业务场景

业务场景为,购买商品的过程要对余额进行查询与修改,大致的业务流程如下:

(1)从数据库查询用户现有余额 SELECT money FROM t_yue WHERE uid=$uid,不妨设查询出来的$old_money=100元

(2)业务层实施业务逻辑,比如购买一个80元的商品,并且打九折

if($old_money> 80*0.9) $new_money=$old_money-80*0.9=28

(3)将数据库中的余额进行修改 UPDAtE t_yue SET money=$new_money WHERE uid=$uid

在并发量低的情况下,这个流程没有任何问题,原有金额100元,购买了80元的九折商品(72元),剩余28元。

 

二、潜在的问题

在分布式环境中,如果并发量很大,这种“查询+修改”的业务很容易出现数据不一致。极限情况下,可能出现这样的异常流程:

(1)业务1和业务2同时查询余额,是100元

(2)业务1和业务2进行逻辑计算,算出各自业务的余额,假设业务1算出的余额是28元,业务2算出的余额是38元

(3)业务1对数据库中的余额先进行修改,设置成28元。业务2对数据库中的余额后进行修改,设置成38元。

此时异常出现了,原有金额100元,业务1扣除了72元,业务2扣除了62元,最后剩余38元。

三、问题原因

高并发环境下,对同一个数据的并发读(两边都读出余额是100)与并发写(一个写回28,一个写回38)导致的数据一致性问题。

四、原因分析

业务1的写回:原有金额100,这是一个初始状态,写回金额28,理论上只有在原有金额为100的时候才允许写回成功,这一步没问题。

业务2的写回:的原有金额100,这是一个初始状态,写回金额38,理论上只有在原有金额为100的时候才允许写回成功,可实际上,这个时候数据库中的金额已经变为28了,这一步的写操作不应该成功。

五、简易解决方案

在set写回的时候,加上初始状态的条件compare,只有初始状态不变时,才允许set写回成功,这正是大家常说的“Compare And Set”(CAS),是一种常见的降低读写锁冲突,保证数据一致性的方法。

六、业务的升级

业务线使用CAS解决高并发时数据一致性问题,只需要在进行set操作时,compare一下初始值,如果初始值变换,不允许set成功。

对于上文中的业务场景,只需要将“UPDAtEt_yue SET money=$new_money WHERE uid=$uid”升级为

“UPDAtE t_yue SETmoney=$new_money WHERE uid=$uidAND money=$old_money”即可。

并发操作发生时:

业务1执行 => UPDAtE t_yue SET money=28 WHERE uid=$uid AND money=100

业务2执行 => UPDAtE t_yue SET money=38 WHERE uid=$uid AND money=100

【这两个操作同时进行时,只能有一个执行成功】

 

七、怎么判断哪个执行成功,哪个执行失败

set操作,其实无所谓成功或者失败,业务能通过affect rows得知哪个修改没有成功:

执行成功的业务,affect rows为1

执行失败的业务,affect rows为0

 

八、总结

高并发“查询并修改”的场景,可以用CAS(Compare and Set)的方式解决数据一致性问题。对应到业务,即在set的时候,加上初始条件的比对。

 

<think>嗯,用户之前让我介绍过分布式锁,现在他们问的是乐观锁如何解决分布式数据一致性问题。首先,我需要确认用户是否已经了解分布式锁的基本概念,还是需要从基础开始解释乐观锁。 用户可能是在实际项目中遇到了数据一致性问题,或者在设计分布式系统时考虑不同的锁机制。他们可能已经知道分布式锁通过互斥访问来保证一致性,但想了解另一种方法——乐观锁,特别是它在分布式环境中的应用。 我需要先解释乐观锁的基本原理,和悲观锁的区别。然后,重点放在乐观锁如何应用在分布式系统中,比如版本号或时间戳机制。要提到CAS操作,但要注意在分布式环境下CAS的局限性,可能需要结合其他机制。 用户可能关心的是乐观锁在分布式场景下的实现方式,比如使用数据库的版本字段,或者使用Redis的WATCH命令。需要举例说明,比如电商中的库存扣减,或者账户余额更新,这样用户更容易理解。 还要注意比较乐观锁和分布式锁的优缺点。用户可能想知道在什么情况下选择乐观锁更合适,比如高并发但冲突少的场景。同时,需要指出乐观锁的不足,比如自旋带来的性能问题,或者需要重试机制。 另外,用户可能对如何具体实现乐观锁感兴趣,比如在代码中如何添加版本号检查,或者使用ORM框架的支持。可能需要提到一些实际的技术栈,比如MySQL的乐观锁实现,或者使用Redis的事务。 最后,需要总结适用场景,帮助用户判断是否适合他们的用例。要确保内容结构清晰,先讲概念,再讲实现,然后优缺点和应用场景,这样用户能循序渐进地理解。</think>好的!乐观锁是一种**无锁并发控制机制**,主要用于解决分布式系统中的数据一致性问题。与悲观锁(如分布式锁)不同,它不通过强制互斥访问资源来避免冲突,而是假设并发操作冲突概率较低,通过版本控制(如版本号、时间戳)或条件检查来保证数据一致性。 --- ### **核心原理** 1. **版本标识**:为数据添加一个版本号(`version`)或时间戳字段,每次更新时检查并更新该标识。 2. **冲突检测**:在提交修改时,验证当前数据的版本是否与读取时的版本一致。 3. **失败重试**:若检测到版本不一致(其他客户端已修改数据),则放弃当前操作并重试或抛出异常。 --- ### **实现方式** #### **1. 基于数据库的乐观锁** - **示例场景**:商品库存扣减。 - **步骤**: 1. 读取数据时记录版本号(如 `version=1`)。 2. 更新时附加版本条件: ```sql UPDATE products SET stock = stock - 1, version = version + 1 WHERE id = 100 AND version = 1; ``` 3. 检查影响行数:若返回 `0`,说明版本不匹配,操作失败,需重试。 - **优点**:实现简单,无需额外组件。 - **缺点**:高并发时重试次数多,可能影响性能。 #### **2. 基于缓存的乐观锁(如 Redis)** - **示例**:使用 Redis 的 `WATCH` 命令实现乐观锁: 1. 监视(`WATCH`)某个键(如 `key:balance`)。 2. 读取当前值并计算新值。 3. 开启事务(`MULTI`)更新键值,若键被其他客户端修改过,事务会失败。 4. 失败后重试或通知用户。 - **适用场景**:高频读、低频写的场景(如计数器)。 #### **3. 分布式场景的扩展** - **结合分布式数据库**:如 TiDB 的乐观事务模型,通过两阶段提交(2PC)和冲突检测实现。 - **分布式版本控制**:通过全局版本号服务(如 ZooKeeper)生成唯一递增版本号,协调多节点操作。 --- ### **乐观锁 vs 悲观锁** | 特性 | 乐观锁 | 悲观锁(如分布式锁) | |---------------------|--------------------------------|------------------------------| | **冲突假设** | 低概率冲突 | 高概率冲突 | | **实现复杂度** | 简单(无需锁管理) | 复杂(需锁获取、释放、超时) | | **性能** | 高(无阻塞) | 较低(锁竞争可能成为瓶颈) | | **适用场景** | 读多写少、冲突较少 | 写多、冲突频繁 | | **典型应用** | 库存扣减、账户余额更新 | 分布式任务调度、独占资源访问 | --- ### **解决分布式数据一致性的关键点** 1. **版本全局唯一性** - 在分布式系统中,版本号需保证全局唯一且递增(如使用分布式 ID 生成器或数据库自增字段)。 2. **原子性操作** - 更新时必须保证“读取版本号 → 校验 → 更新数据”的原子性(如通过数据库事务或 Redis Lua 脚本)。 3. **重试策略** - 冲突时需设计合理的重试机制(如指数退避算法),避免无限重试或雪崩效应。 4. **最终一致性** - 若系统允许短暂不一致,可结合消息队列异步重试,最终达成一致性。 --- ### **典型问题解决方案** 1. **ABA 问题** - **现象**:数据版本号从 A → B → A,导致乐观锁误判未修改。 - **解决**:使用递增版本号(而非布尔值),或结合时间戳。 2. **高并发重试压力** - **现象**:大量冲突导致频繁重试,拖慢系统。 - **解决**:限制重试次数,或降级为队列串行化处理。 3. **分布式系统时钟同步** - **现象**:不同节点时间戳不一致。 - **解决**:使用逻辑时钟(如版本号)替代物理时钟。 --- ### **适用场景** 1. **电商库存扣减**:防止超卖。 2. **账户余额更新**:避免重复扣款。 3. **配置中心**:多人编辑同一配置时防覆盖。 4. **分布式文档协作**:如协同编辑中的冲突合并(类似 Google Docs)。 --- ### **总结** 乐观锁通过**版本控制 + 冲突检测**机制,以轻量级的方式解决分布式数据一致性问题,尤其适合**低冲突、高并发**的场景。其核心优势在于无锁设计,减少了阻塞和死锁风险,但需结合合理的重试策略和原子性操作保证可靠性。在实际应用中,需根据业务特点(如冲突频率、一致性要求)选择乐观锁或悲观锁。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值