PHP concurrent deduction to ensure data consistency

PHP concurrent deduction to ensure data consistency

#🐘 Business scenario analysis

In the logic of the user's purchase of goods, it is necessary to query and deduct the balance of the user's wallet

Exception: If the same user executes multiple services concurrently, there is a certain probability that there will be data inconsistencies in the business of "query + deduction"

Tips: If there is no restriction on the frequency of requests for a single interface, users may also have data inconsistencies when using concurrent requests

#Deduction scene

#Step1: Query user wallet balance from database

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

#Step2: Business logic

Tips: Article sharing handles the consistency of concurrent deductions for the same user, skip the logic of checking inventory

  1. 查询商品价格,比如70元
  2. 商品价格对比余额是否足够,足够时进行扣款提交订单逻辑
if(goodsPrice <= userBalance) {
	$newUserBalance = userBalance - goodsPrice;  
}else {
	throw new UserWalletException(['msg' => '用户余额不足']);
}

#Step3: Modify the balance of the database

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

In the absence of concurrency, there is no problem with this process, the original balance is 100, the purchase of 70 yuan of goods, the remaining 30 yuan

#Abnormal scene

#Step1: The user concurrently purchases business A and business B (different instances/services), a certain probability of parallel query balance is 100

step1

#Step2: Business A and business B are separately deducted logically, business A product 70 results in a balance of 30, and business B product 80 results in a balance of 20

step2

#Step3:

1.Business A is modified first, and the modified balance is 30

step3

2.Modify after business B, and modify the balance to 20

step4

At this time, an exception occurred. The original balance was 100 yuan, the sum of the product prices of business A and business B was 150 yuan (70+80), and the purchase was successful and the balance was 20 yuan.

Abnormal point: business A and business B parallel query balance is 100

#solution

#Pessimistic lock: lock:

Use Redis pessimistic lock, such as grab a KEY to continue the operation, otherwise the operation is prohibited

Packaged 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

Use CAS (Compare And Set)

When the set is written back, the initial state condition compare is added. Only when the initial state is unchanged, the set write back is allowed to succeed, and the method to ensure data consistency

will:

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

to:

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

In this case, only one of the concurrent operations is executed successfully, and the success is determined according to whether the affect rows is 1

#Conclusion

  • There are many solutions, this is just one solution
  • Using Redis pessimistic lock scheme will reduce throughput

Did you enjoyed the article ?
Subscribe to the newsletter 👨‍💻

Be the first to know when I write something new! If you don't like emails, you can follow me on GitHub.