我是靠谱客的博主 要减肥眼神,最近开发中收集的这篇文章主要介绍php并发扣款,PHP 并发扣款,保证数据一致性(悲观锁),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

业务场景分析

用户购买商品的逻辑中,需要对用户钱包的余额进行查询和扣款

异常:如果同一用户并发执行多个业务进行"查询+扣款"的业务中有一定概率出现数据不一致

Tips:如果没有做限制单一接口请求频率,用户使用并发请求的手段也有概率出现数据不一致

扣款场景

Step1: 从数据库查询用户钱包余额

SELECT balance FROM user_wallet WHERE uid = $uid;

+---------+

| balance |

+---------+

| 100 |

+---------+

1 row in set (0.02 sec)

复制代码

Step2: 业务逻辑

Tips: 文章分享处理同一用户并发扣款一致性,检查库存啥的逻辑略过

1. 查询商品价格,比如70元

2. 商品价格对比余额是否足够,足够时进行扣款提交订单逻辑

if(goodsPrice <= userBalance) {

$newUserBalance = userBalance - goodsPrice;

}else {

throw new UserWalletException(['msg' => '用户余额不足']);

}

复制代码

Step3: 将数据库的余额进行修改

UPDATE user_wallet SET balance=$newUserBalance WHERE uid = $uid

复制代码

在没有并发的情况下,这个流程没有任何问题,原有余额100,购买70元的商品,剩余30元

异常场景

Step1: 用户并发购买业务A和业务B(不同实例/服务),一定概率并行查询余额是100

47c17e022500f054a45905272f78d2bb.png

Step2: 业务A和业务B分别扣款逻辑处理,业务A商品70结果余额30,业务B商品80结果余额20

c8549335780f66f61450b4832ae1c532.png

Step3:

1 业务A先进行修改,修改余额为30

5ed05509d7ea4eaf025d018c9b54c57c.png

2 业务A后进行修改,修改余额为20

9a62cfc5cf80607cd1a09cda450ed907.png

此时异常出现了,原余额100元,业务A和业务B的商品价格总和150元(70+80)都购买成功且余额还剩20元。

异常点:业务A和业务B并行查询余额为100

解决方案

悲观锁

使用Redis悲观锁,例如抢到一个KEY才能继续操作,否则禁止操作

封装了一个开箱即用的RedisLock

use Ar414RedisLock;

$redis = new Redis();

$redis->connect('127.0.0.1','6379');

$lockTimeOut = 5;

$redisLock = new RedisLock($redis,$lockTimeOut);

$lockKey = 'lock:user:wallet:uid:1001';

$lockExpire = $redisLock->getLock($lockKey);

if($lockExpire) {

try {

//select user wallet balance for uid

$userBalance = 100;

//select goods price for goods_id

$goodsPrice = 80;

if($userBalance >= $goodsPrice) {

$newUserBalance = $userBalance - $goodsPrice;

//TODO set user balance in db

}else {

throw new Exception('user balance insufficient');

}

$redisLock->releaseLock($lockKey,$lockExpire);

} catch (Throwable $throwable) {

$redisLock->releaseLock($lockKey,$lockExpire);

throw new Exception('Busy network');

}

}

复制代码

乐观锁

使用CAS(Compare And Set)

在set写回的时候,加上初始状态的条件compare,只有初始状态不变的时候才允许set写回成功,保证数据一致性的方法

将:

UPDATE user_wallet SET balance=$newUserBalance WHERE uid = $uid

复制代码

改为:

UPDATE user_wallet SET balance=$newUserBalance WHERE uid = $uid AND balance = $oldUserBalance

复制代码

这样的话并发操作时只有一个是执行成功的,根据affect rows是否为1判断是否成功

结语

解决方案有很多,这只是其中一种解决方案

使用Redis悲观锁的方案会降低吞吐量

最后

以上就是要减肥眼神为你收集整理的php并发扣款,PHP 并发扣款,保证数据一致性(悲观锁)的全部内容,希望文章能够帮你解决php并发扣款,PHP 并发扣款,保证数据一致性(悲观锁)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(41)

评论列表共有 0 条评论

立即
投稿
返回
顶部