基于redis实现分布式锁

小文blog小文 2019-04-02 13:47 38人围观

我们在做业务开发时候,经常遇到一些多进程/线程对同一数据修改而产生竞态条件,为了避免修改冲突覆盖,常常用锁机制来保证数据的正常性。同一进程内多线程并发可以用语言特性实现比如java的synchronized或GO的sync同步代码包或者直接利用本地文件锁实现。但是在分布式环境中,我们需要用到分布式锁。分布式锁的实现方式有很多,比如mysql,zookereper和redis。本文就讲讲最简单的redis分布式锁实现方式(代码示例基于php)。

获取锁

$key = "redis_lock"
$value = "550e8400-e29b-41d4-a716-446655440000"
$ttl = 5000
$redisIns->set($key, $value, 'NX', 'PX', $ttl)

1.$key自定义锁名,应该保证唯一性

2.$ttl 过期时间,保证不会因某进程得到锁却意外退出导致死锁

3.set参数nx实现效果等于setnx+expire操作,但是set是原子性操作而setnx+expire是非原子的,所以应使用set完成加锁操作

4.根据返回值判断是否加锁成功,如果加锁失败可以直接丢错误码给前台或用自旋锁来竞争加锁

5.$ttl尽量要大于该进程的操作时间,避免因操作太久但是到期导致锁解除。在操作中可以通过expire进行锁续命

6.$value uuid或其它方式生成一个分布式环境下全局唯一的字符串。删除锁时候需要重新获取下该key的value,判断是否与生成的一致,如若一致,则可以删除,避免因锁提前过期导致删除掉别的进程持有的锁。

解除锁

由于删除锁时候需要先重新获取下该key的value,判断是否与生成的一致,如若一致,才可以删除,而get和del并非原子性,所以需要用lua脚本或事务实现,保证其原子性。

//基于lua脚本
$script = <<<LUA
if redis.call("get",KEYS[1]) == ARGV[1]
then
    return redis.call("del",KEYS[1])
else
    return 0
end
LUA;
$redisIns->eval($script, 1, $key, $value);
//基于事务
$options = array(
    'cas' => true,
    'watch' => $key,
    'retry' => 3,
);
$redisIns->transaction($options, function ($tx) use ($key, $value) {
    $tx->multi();
    if ($tx->get($key) == $value) {
        $tx->del($key);
    }
});


转载请注明来自小文blog,本文标题:基于redis实现分布式锁

发布评论
生活是一场戏,主角当累了,你亦可成为观众,停下脚步,歇一歇