【一次猜想引发的面试】redis的分布式锁是怎么肥事

151 阅读3分钟

面试:Redis分布式是锁如何实现的?

大弟:通过Redis的setnx(set if not exist)就可以实现了,setnx(key,value)只有当key不存在时,才会创建这个键,创建成功后就代表拿到了这个锁,执行逻辑代码后,在删除这个键就相当于释放锁。

面试:emm,如果拿到锁之后,在执行逻辑代码期间,异常了,崩溃了,还没释放锁就崩溃了,这不就死锁了么。

大弟:啊啊啊啊,对,该死,居然忘记了,在创建键之后,还要通过setex为这个键加上一个过期时间,这样就算拿到锁的客户端挂掉了,过期时间到了,这个锁也就被释放了。

面试:emm,按你说的setnx创建这个代表锁的键,setex为这个键设置一个过期时间,当setnx执行完,还没来得及执行setex它又挂了怎么办,又死锁了

大弟:(不可能,我不信,绝对不会真实发生。)啊啊啊啊,我又想起来了,这时候要做一个原子的操作,Redis2.6.12版本增加了set命令,可以直接使用set key value ex 10 nx 达到setnx setex的效果,并且这是一个原子操作。

面试:emm,用户A用户B都想操作键KEY,不过用户A先拿到了锁,但是A在执行逻辑代码时,用的时间太久了,已经超时了。这时候这个锁的键已经被删除了。 这时候等待的用户B就获得了这个锁,也开始执行逻辑代码。用户B还在执行期间,A那边执行结束了,A就进行了释放锁的操作,那此时B的锁就会被A释放掉。这个问题怎么解决。

大弟:(把逻辑代码也少点被,尽量别整这老多。)啊啊啊,其实可以解决这种重复释放的问题,A获得的锁时,在将键的值value追加个随机数xxx,这样删除的时候,判断下键对应的值与valuexxx是否相等,相等则释放。不相等,那就说明早就过期了呗。

面试:emmm,你知道一句话talk is cheap,。。。

大弟:懂懂懂,为弟研磨

def lock(conn,name,timeout=10,expire=10):
    #生成一个随机数
    identifier = str(uuid.uuid4())
    #键的key
    lockname='lock:'+name
    #设置一个获取锁的超时时间
    end=time.time()+timeout
    while time.time() < end:
    #尝试获取锁
        if conn.set(lockname,identifier,nx=True,ex=expire):
            return identifier
    #没拿到,休息会再试。
        sleep(0.01)
    #超时返回False
    return False
def unlock(conn,name,identifier):
    #通过piepline减少网络通信次数
    pipeline = conn.pipeline()
    lockname='lock:'+name
    while True:
        try:
        #监视这个键,
            pipeline.watch(lockname)
            #如果键值相等,说明当前要释放的锁,并没有过期。
            if pipeline.get(lockname) == identifier:
                pipeline.multi()
                pipeline.delete(lockname)
                pipeline.execute()
                return True
            #说明锁已经过期,此时锁被其他用户获取了
            pipeline.unwatch()
            break
        except redis.exceptions.WatchError:
            pass
    return False
    ```
面试:emmm,你在想想分布式锁还有什么问题没有?

大弟:(我靠。。。)emmm,如果是主从服务器环境下,主服务器挂了,还没有将用户A获取锁的这条命令发送给从服务器,而监视主从服务器状态的sentinel进行故障处理,将从服务器升级为主服务器,此时,用户B在获取用户A的锁也是可以获取到的,因为这个新的主服务器压根不知道A获得了锁。

面试:emmm,那这种情况应该如何保证安全?

大弟:(。。想回家)Redlock,这个Redlock需要额外增加多个独立的redis实例,当要获取锁时,向这些独立的redis实力发送请求加锁的命令,当一半以上的redis实例set成功时,就认为获取锁成功。就像选取领头Sentinel似的,超过一半就行了。

面试:emmmm

大弟: 突然好想去厕所,不行了。。。。。

面试:emmm,休息10分钟把,一会聊聊这个选举领头Sentinel

大弟:。。。。。。。。。。。。。。