1.为什么选择缓存实现分布式锁?
基于缓存实现的分布式锁,就是将数据仅存放在系统的内存中,不写入磁盘,从而减少 I/O 读写。避免大量请求直接访问数据库,提高系统的响应能力。

2.怎么设计合理的的分布式锁?

来看一个例子:(代码无绝对对错,只有适合不适合的场景

$redis = new Redis();
public function lock($key, $expire = 5){
    $isLock = $redis->setnx($key, time() + $expire);
    if(!$isLock){
        $lockTime = $redis->get($key);
        if(time() > $lockTime){
            unlock($key);
            $isLock = $redis->setnx($key, time() + $expire);
        }
    }
    if($isLock){
        $redis->expire($key, 10);
    }
    return $isLock ? true : false;
}
public function unlock($key){
    return $redis->del($key);
}

这个例子实现的分布式锁存在什么问题?
其实这个例子已经解决了很多问题了,适合很多公司使用了
1.加锁和加过期时间不是原子性操作。当出现某个线程操作完成 setnx 之后,还没有来得及设置过期时间,线程就挂掉了,此例会先判断key是否存在,存在的话给了一个默认时间。当并发量大时,会处于等待操作,并不适合高并发场景。

2.保证不了操作的客户端就是加锁的客户端。比如在有些场景中,一个线程 A 获取到了锁之后,由于业务代码执行时间可能比较长,导致超过了锁的超时时间,自动失效,后续线程 B 又意外的持有了锁,当线程 A 再次恢复后,通过 del 命令释放锁,就错误的将线程 B 中同样 key 的锁误删除了。

解决方法:

这里其实我们可以用 :

SET lock_key unique_value NX PX 10000 命令

lock_key 就是 key 键;

unique_value 是客户端生成的唯一的标识;

NX 代表只在 lock_key 不存在时,才对 lock_key 进行设置操作;

PX 10000 表示设置 lock_key 的过期时间为 10s,这是为了避免客户端发生异常而无法释放锁。

此命令设置key 和过期时间是原子性的。当释放锁时,判断操作的客户端是否正确。如下:

$key = 'xinyanleihaoshuai';//加锁的key
$value = $key.$uid;//保证唯一性 $uid 为用户唯一uid

//set加个锁
$redis->set($key, value, ['nx', 'ex'=>10]); 

//释放锁过程
$res = unlock($key,$value);

public function unlock($key,$value){
    $cacheValue = $redis->get($key);
    if($value == $cacheValue) {
        return $redis->del($key);
    }
    return false;
}

代码如有疑问欢迎指教沟通哈。。。

3.Redis 如何解决集群情况下分布式锁的可靠性?

由于 Redis 集群数据同步到各个节点时是异步的,如果在 Redis 主节点获取到锁后,在没有同步到其他节点时,Redis 主节点宕机了,此时新的 Redis 主节点依然可以获取锁,所以多个应用服务就可以同时获取到锁。

搜了下官方解决方法:
为了避免 Redis 实例故障导致锁无法工作的问题,Redis 的开发者 Antirez 设计了分布式锁算法 Redlock。Redlock 算法的基本思路,是让客户端和多个独立的 Redis 实例依次请求申请加锁,如果客户端能够和半数以上的实例成功地完成加锁操作,那么我们就认为,客户端成功地获得分布式锁,否则加锁失败。

这样一来,即使有某个 Redis 实例发生故障,因为锁的数据在其他实例上也有保存,所以客户端仍然可以正常地进行锁操作,锁的数据也不会丢失。那 Redlock 算法是如何做到的呢?

我们假设目前有 N 个独立的 Redis 实例, 客户端先按顺序依次向 N 个 Redis 实例执行加锁操作。这里的加锁操作和在单实例上执行的加锁操作一样,但是需要注意的是,Redlock 算法设置了加锁的超时时间,为了避免因为某个 Redis 实例发生故障而一直等待的情况。

当客户端完成了和所有 Redis 实例的加锁操作之后,如果有超过半数的 Redis 实例成功的获取到了锁,并且总耗时没有超过锁的有效时间,那么就是加锁成功。