Business Scenario Analysis

In the logic of purchasing goods, users need to query and deduct the balance of their wallets. If the same user concurrently executes multiple services for query and deduction services, 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

1: Queries the wallet balance from the database

SELECT balance FROM user_wallet WHERE uid = $uid; + -- -- -- -- -- -- -- -- -- + | balance | + -- -- -- -- -- -- -- -- -- + | 100 | + -- -- -- -- -- -- -- -- -- + 1 row in the set (0.02 SEC)Copy the code

2: indicates service 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 '=>' UserWalletException ']); }Copy the code

3: Modifies 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

1: Users concurrently purchase services A and B (different instances or services), and the balance of parallel query is 100

2: Business A and business B will be deducted logically respectively. The result balance of 70 of business A is 30 and that of 80 of business B is 20

3:

1 Service A is modified first and the balance is 30

2 Change the balance after service B to 20

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

<? phpuse 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

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