Business scenario analysis
In the logic of purchasing goods, it is necessary to query and deduct the balance of the user‘s wallet
Exception: if the same user concurrently executes multiple services, there is a certain probability of data inconsistency in the “query + deduction” business
Tips: if there is no restriction on the frequency of single interface requests, users may have data inconsistency by using the means of concurrent requests
Deduction scenario
Step 1: query user wallet balance from database
SELECT balance FROM user_wallet WHERE uid = $uid;
+---------+
| balance |
+---------+
| 100 |
+---------+
1 row in set (0.02 sec)
Step 2: business logic
Tips: the article shares the logic of handling the same user’s concurrent deduction consistency and checking the inventory
1. Query commodity price, such as 70 yuan
2. Check whether the balance of commodity price comparison is enough, and deduct money when it is enough to submit the order logic
if(goodsPrice <= userBalance) {
$newUserBalance = userBalance - goodsPrice;
}else {
Throw new userwalletexception (['msg '= >'insufficient user balance');
}
Step 3: modify the balance of the database
UPDATE user_wallet SET balance=$newUserBalance WHERE uid = $uid
In the case of no concurrency, there is no problem in this process. The original balance is 100 yuan, and the purchase of goods is 70 yuan, and the remaining 30 yuan
Abnormal scene
Step 1: a certain probability for users to simultaneously purchase business a and service B (different instances / services)parallel
The query balance is 100
Step 2: business a and business B deduct money logically, business a commodity 70 result balance 30, business B commodity 80 result balance 20
Step3:
1. Business a is modified first, and the modified balance is 30
2 after business a, modify the balance to 20
At this time, an exception appears. The original balance is 100 yuan, the total commodity price of business a and business B is 150 yuan (70 + 80), and the remaining balance is 20 yuan.
Outliers: business a and business Bparallel
The query balance is 100
Solution
Pessimistic lock
Use redis pessimistic lock. For example, if you grab a key, you can continue the operation. Otherwise, the operation is prohibited
It encapsulates a redislock out of the box
<?php
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');
}
}
Optimistic lock
useCAS
(Compare And Set)
When the set is written back, the condition of the initial state is addedcompare
Only when the initial state is unchanged, can set write back success and ensure data consistency
Will:
UPDATE user_wallet SET balance=$newUserBalance WHERE uid = $uid
Change to:
UPDATE user_wallet SET balance=$newUserBalance WHERE uid = $uid AND balance = $oldUserBalance
In this way, only one of the concurrent operations is successful, and the success is judged according to whether the affect rows is 1
epilogue
- There are many solutions, and this is just one of them
- Using redis pessimistic lock will reduce throughput