悲观锁和乐观锁

114 阅读2分钟

悲观锁:

总是设想最坏的情况,每次去拿数据的时候认为别人都会修改,所以每次在拿到数据的时候都会加上锁,这样别人想拿到这个数据就会阻塞直到它拿到锁(共享锁每次只给一个线程使用,其他线程阻塞,用完之后再把资源转让给其他线程)。

乐观锁:

总是设想最好的情况,每次去拿数据时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一个在此期间别人有没有去更新这个数据,可以使用版本好机智和cas算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量。

两种锁的使用场景

乐观锁适合写比较少的情况(多读的场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量,但如果写多的情况,一般经常会产生冲突,这样会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景用悲观锁比较合适

业务场景分析:

用户购买商品的逻辑中,需要对用户钱包的余额进行查询和扣款 异常:如果同一用户并发执行多个业务进行“查询+扣款”的业务中有一定概率出现数据不一致 Tips:如果没有做限制单一接口请求频率,用户使用并发请求的手段也有概率出现数据不一致

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

封装了一个开箱即用的 RedisLock github.com/ar414-com/p…

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');
    }
}
 

乐观锁使用 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 悲观锁的方案会降低吞吐量

转自链接:learnku.com/articles/42… 版权声明:著作权归作者所有。商业转载请联系作者获得授权,非商业转载请保留以上作者信息和原文链接。