基于redis的分布式锁

2018-08-30  本文已影响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实现的分布式锁的优缺点各是什么?

上一篇下一篇

猜你喜欢

热点阅读