转载请备注来源: 《Redis中加锁和解锁问题》 | shuwoom.com

在电商活动中,经常会有“秒杀”的抢购活动,这种场景,服务器通常面临高并发的请求处理。也就是说,在同一时间,会有大量并发的用户同时去购买某个商品,这时候,就需要保证不会出现过度出售的情况(用户最终购买的商品数量超过实际的商品数量)。这里就需要应用到redis中加锁和解锁的特性来保证每次的购物操作只有一个用户在进行,避免出现竞争导致脏数据的情况发生。

下面,我们来介绍如何正确使用加锁和解锁。

setnx

redis官方在锁操作上是建议使用set命令来进行,使用方式如下:

if ($redis->set('my:lock', 1, ['NX'])) {
        # todo

        $redis->del('my:lock');
    }

其中NX — 表示只有key不存在的时候才设置

这个方法有个问题,假如一个客户端获取到锁后发生奔溃或者一直占用着锁不释放,就会导致死锁,使得后续的用户无法获取到锁进行操作。所以这个操作需要设置一个超时时间。

setnx的改进

针对上面方法的问题,我们使用expire方法设置超时时间。但到这里就解决了问题吗?

没有!因为这里expire不是原子操作,如果在操作完setnx后客户端奔溃,这时候就没有成功设置超时时间,同样使得加锁操作面临上面的问题。

if ($redis->set('my:lock', 1, ['NX'])) {
        $redis->expire('my:lock', 10);
        # todo

        $redis->del('my:lock');
    }

为了解决这个问题,我们可以把超时时间设置跟set操作放在一起,如下所示:

if ($redis->set('my:lock', 1, ['NX', 'EX' => 10])) {
        # todo

        $redis->del('my:lock');
    }

那么到这里,所有问题解决了吗?很遗憾,还是没有。

这里会出现误删其他人锁的问题。假如客户端1在获取锁后由于超时,这时候锁会自动释放,客户端2就可以获取到锁。这时候客户端1会删除锁,而这里删除的锁实际上是客户端2的。如下图所示:

所以这里需要为每一个锁设置唯一的标识,在解锁之前要判断解得锁是否跟自己获取到的是同一个锁,如果不是就不做任何操作。

setnx+lua

这也是官方推荐的方式,使用lua+redis的方法。之所以使用lua是为了保证原子性。

在lua脚本中,一共涉及到3个操作,分贝时:GET、判断和DEL。而如果把这些逻辑放到客户端,会由于没有解决原子性问题而面临上面同样的问题。所以这里要用到lua脚本来解决。

    $script = '
if redis.call("get",KEYS[1]) == ARGV[1]
then
    return redis.call("del",KEYS[1])
else
    return 0
end
    ';
    $token = uniqid(mt_rand(), true);
    if ($redis->set('my:lock', $token, ['NX', 'EX' => 10])) {
        # todo

        $redis->eval($script, ["my:lock", $token], 1);
    } else {
        echo 'get lock failed!';
    }

转载请备注来源: 《Redis中加锁和解锁问题》 | shuwoom.com

打赏

发表评论

电子邮件地址不会被公开。