高并发下,用户余额扣减保证一致

本文探讨了在高并发分布式环境中,如何处理用户余额扣减的业务,以避免数据不一致的问题。通过分析并发场景下的潜在问题,提出了一种基于比较与设置(CAS)的解决方案,即在更新数据库余额时加入初始值的校验,确保只有当余额未被其他并发操作修改时,更新才能成功。这种方法可以有效地防止并发操作导致的余额错误。在实际业务中,通过在UPDATE语句中添加WHERE子句来实现这一策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1,可以通过队列的方式处理;

2,参见: https://m.wang1314.com/doc/webapp/topic/20483105.html

高并发下,余额扣减一致性实践

藏家004  收藏于2018-11-01   转藏1次

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

一、业务场景

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

(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=$uid AND 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>好的,我现在需要回答用户关于高并发场景下如何保证数据一致性的问题。首先,我得回忆一下用户提供的引用内容,看看有哪些现有的解决方案可以整合进来。 用户提到的引用[1]里提到了金融系统使用2PC或TCC,电商用消息队列的最终一致性,跨行转账用分布式事务,高并发时用分布式锁、幂等性设计等。这些是不同场景下的方法,我需要把这些分门别类地整理出来。 引用[2]提到了直接更新数据库时用条件判断,比如在update语句里检查surplus是否足够,这样可以避免超卖。这可能属于乐观锁的一种应用,或者说是数据库层面的原子操作。 引用[3]讨论到乐观锁在高并发下可能导致大量失败,建议结合Redis处理业务日志,再异步更新数据库。这里提到了减少数据库IO的重要性,可能需要引入缓存层和异步处理机制。 接下来,我需要将这些方法系统化。通常,处理高并发数据一致性有几种常见模式:强一致性方案、最终一致性方案、以及降低并发冲突的策略。比如,强一致性可能用分布式锁或数据库事务;最终一致性可以用消息队列异步处理;而乐观锁、限流等则减少冲突。 另外,用户的问题可能需要具体的应用场景对应的解决方案,比如秒杀系统如何处理库存,这时候可能需要预减库存、队列排队等。同时,幂等性设计也是关键,防止重复请求导致的数据错误。 还要注意引用中提到的不同方案的优缺点。比如,2PC可能有性能问题,TCC需要业务逻辑拆分,而基于消息队列的方案虽然最终一致但吞吐量较高。这些都需要在回答中说明,帮助用户根据场景选择合适的方法。 可能还需要提到缓存一致性的问题,比如使用Redis时如何保证缓存和数据库的数据同步,常见的策略如延迟双删、订阅数据库变更日志等。 最后,确保回答结构清晰,分点列出不同的方法和对应的应用场景,并引用提供的参考资料作为支持。同时,生成的相关问题要围绕用户可能关心的后续步骤,比如具体实现细节、不同方案的比较、实际案例分析等。</think>### 高并发场景下保证数据一致性的方法与解决方案 在高并发场景中,数据一致性面临的核心挑战是 **并发冲突** 和 **系统性能的平衡**。以下是结合不同业务场景的实践方案: --- #### 一、强一致性方案 1. **分布式锁** - 通过Redis或Zookeeper实现互斥锁,确保同一时间只有一个线程能操作共享资源。 - 示例场景:秒杀系统中库存扣减。 - 实现示例: ```python # 使用Redis分布式锁 lock_key = "product_123" if redis.set(lock_key, 1, ex=5, nx=True): try: # 执行库存扣减 update inventory set stock = stock - 1 where id=123 and stock > 0; finally: redis.delete(lock_key) ``` - 缺点:锁竞争激烈时性能下降[^3]。 2. **数据库事务 + 乐观锁** - 利用数据库的`CAS(Compare and Set)`操作,通过版本号或条件判断实现原子更新。 - 示例SQL: ```sql UPDATE account SET balance = balance - 100, version = version + 1 WHERE user_id = 1 AND version = current_version; ``` - 优点:避免锁竞争,适合低冲突场景[^2][^3]。 --- #### 二、最终一致性方案 1. **基于消息队列的异步处理** - 将操作拆分为多个阶段,通过消息队列(如Kafka、RocketMQ)保证最终一致性。 - 典型应用:电商订单创建与库存扣减。 - 流程: ``` 1. 创建订单(本地事务) 2. 发送扣减库存消息到队列 3. 库存服务消费消息并执行操作 ``` - 优点:提升吞吐量,适合跨服务场景[^1]。 2. **TCC模式(Try-Confirm-Cancel)** - 将事务分为三个阶段: - **Try**:预留资源(如冻结库存)。 - **Confirm**:提交操作(实际扣减库存)。 - **Cancel**:回滚预留资源。 - 适用场景:金融交易等高一致性要求的系统。 --- #### 三、降低冲突的优化策略 1. **数据分片** - 将热点数据分散到不同节点(如按用户ID分库分表),减少单点压力。 - 示例:用户余额表按`user_id % 10`分片。 2. **限流与降级** - 使用令牌桶或漏桶算法限制并发请求量。 - 熔断机制:当系统压力过大时,暂时拒绝非核心请求。 3. **预扣减与异步恢复** - 秒杀场景中,先在Redis中预扣库存,后续异步同步到数据库。 - 伪代码逻辑: ```python if redis.decr("stock:123") >= 0: # 生成订单,异步更新数据库 send_message_to_queue("update_stock", product_id=123) else: # 库存不足,退回请求 ``` --- #### 四、补充设计原则 1. **幂等性设计** - 通过唯一ID(如订单号)确保重复请求不会导致数据错误。 - 实现方式:数据库唯一索引、Redis记录请求状态。 2. **读写分离** - 写操作走主库,读操作走从库,缓解主库压力。 3. **缓存一致性策略** - 更新数据库后,通过**延迟双删**(先删缓存,更新数据库,再删缓存)减少脏数据风险。 --- ### 方案对比 | 方案 | 一致性强度 | 性能影响 | 适用场景 | |---------------------|------------|----------|------------------------| | 分布式锁 | 强 | 高 | 秒杀、抢购 | | 乐观锁 | 强 | 中 | 账户余额更新 | | TCC模式 | 强 | 中 | 跨行转账、金融交易 | | 消息队列最终一致性 | 最终 | 低 | 电商订单、物流系统 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值