美文网首页
基于redis的分布式锁

基于redis的分布式锁

作者: 小小辉 | 来源:发表于2018-08-30 20:27 被阅读0次

    1.概述

        现在大部分公司的服务通过分布式部署实现了高可用,但是问题来了:如果有一段逻辑只希望一个应用中的一个线程进行执行,如何能够保证用户的一次操作,同时只受理一次呢?以前单机应用,可以通过java的synchronize和Lock来实现。现在则需要使用分布式锁。

        分布式锁的实现主要有redis、zookeeper、db等方式。本文主要说明用redis实现方式。

    2.方案1

        因为redis是单线程处理的,并且支持cas操作。不难想到的一个实现是:

        lock:

            setnx    key    value

        unlock:

            del  key

        其中,setnx语义为:若给定的 key 已经存在,则 SETNX 不做任何动作。  成功设置返回1、设置失败返回0。

        根据setnx的返回值判断是否加锁成功。

       问题:

            如果一个服务lock完之后,崩溃了或者重启了,那么将永远不会unlock,对于需要继续执行的场景来说,是无法接受的。

    3.方案2

       针对方案2的一些问题,不难想到对key设置一个超时就ok了。那么方案2来了:

        lock:

            set   key   value  px  timeout_millisecond  nx

        unlock:

            del key

        其中set方法支持同时设置超时和nx,这种方案是否ok?

        问题:

            如果最早拿到锁的服务器1执行结束,调用unlock失败(实际redis删除成功),另外一个服务器2加锁成功。某种补偿导致服务器1重复调用unlock。这时候问题出现了,服务器2加的锁被服务器1释放了。

    4.方案3    

       针对上面的问题:一个全备的分布式锁要求是:

        key 一致

        value 包含了持有者信息、加锁的次数(可重入)

        对于redis来说,基于lua脚本实现。代码如下(参考redisson):

            lock:

                if (redis.call('exists', key) == 0)  then 

                    redis.call('hset', key, holder_info, 1); 

                    redis.call('pexpire', key, timeout); 

                    return nil; 

                end;

            if (redis.call('hexists', key, holder_info) == 1)  then 

                    redis.call('hincrby',  key, holder_info, 1); 

                    redis.call('pexpire',  key, timeout); 

                    return nil; 

            end; 

            return redis.call('pttl', key);

    unlock:

        if (redis.call('exists', key) == 0) then 

            return 1;  

       end;

        if (redis.call('hexists', key, holder_info) == 0) then 

            return nil; //非本持有者加的锁

        end; 

        local counter = redis.call('hincrby', key, holder_info, -1); 

        if (counter > 0) then 

            redis.call('pexpire', key, timeout); 

            return 0; 

         else 

            redis.call('del', key); 

            return 1;

        end; 

        return nil;

    5.总结

        那么真正对于分布式锁的要求是什么,有以下几点:

            1. 不同服务器之间操作串行化(锁的含义)

            2.可重入(根据业务场景、代码来决定)

            3.一个服务器加的锁,不可被另外的服务器删除(误删除)

            4.具有应用崩溃恢复机制

    6.展望

        希望以下的能够引起读者的思考:

            1.如果一个应用没有获取到锁,并且业务需求是需要进行重试加锁,那么只能够轮询。是否还有别的办法?

            2.目前讨论的都是基于redis单点且服务正常的情况下。 如果redis崩溃,那么如何处理?

            3.redis、zookeeper、db实现的分布式锁的优缺点各是什么?

    相关文章

      网友评论

          本文标题:基于redis的分布式锁

          本文链接:https://www.haomeiwen.com/subject/mumrwftx.html