Business Scenario Analysis
In the logic of purchasing goods, users need to query and deduct the balance of their wallets
Exception: If the same user concurrently executes multiple services for “query + deduction”, data inconsistency may occur
Tips: If there is no limit to the frequency of requests from a single interface, there is also a probability of data inconsistency when users use concurrent requests
Deductions scenario
ar414
Step1: query the user wallet balance from the database
SELECT balance FROM user_wallet WHERE uid = $uid;
+---------+
| balance |
+---------+
| 100 |
+---------+
1 row in set (0.02 sec)
Copy the code
Step2: business logic
Handle the same user concurrent deduction consistency, check the inventory of what logic skipped
1. Check the price of goods, such as 70 yuan. 2
if(goodsPrice <= userBalance) {
$newUserBalance = userBalance - goodsPrice;
}else {
throw new UserWalletException(['msg'= >'Insufficient user balance']);
}
Copy the code
Step3: Modify the database balance
UPDATE user_wallet SET balance=$newUserBalance WHERE uid = $uid
Copy the code
In the case of no concurrency, there is no problem with this process. The original balance is 100 yuan, buy 70 yuan of goods, and the remaining 30 yuan
Abnormal scenario
Step1: The user purchases service A and service B concurrently (different instances/services) with A certain probabilityparallel
The query balance is 100
Step2: Business A and business B are deducted logically respectively. The result balance of 70 of business A is 30, and the result balance of 80 of business B is 20
Step3:
1 Service A is modified first and the balance is 30
2 Change the balance to 20 after service A
At this time, there is an anomaly. The original balance is 100 yuan, and the total price of goods of business A and business B is 150 yuan (70+80), and the balance is still 20 yuan.
Exception: The balance of the parallel query of service A and service B is 100
The solution
Pessimistic locking
Use Redis pessimistic lock, such as grab a KEY to continue operation, otherwise prohibited operation
RedisLock is packaged right out of the box
use Ar414\RedisLock;
$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'); }}Copy the code
Optimistic locking
Compare And Set with CAS
In the set write back, plus the initial state of compare, only when the initial state is unchanged to allow the set write back success, to ensure data consistency method
Will:
UPDATE user_wallet SET balance=$newUserBalance WHERE uid = $uid
Copy the code
To:
UPDATE user_wallet SET balance=$newUserBalance WHERE uid = $uid AND balance = $oldUserBalance
Copy the code
In this case, only one of the concurrent operations will be successful, depending on whether affect Rows is 1
conclusion
- There are many solutions, and this is just one solution
- The Redis pessimistic locking scheme reduces throughput