分布式锁实现


1. 分布式锁实现的三种方式

  • .数据库乐观锁;
  • .基于Redis的分布式锁;
  • .基于ZooKeeper的分布式锁。

这里主要介绍 第二种方式基于Redis的分布式锁。

2.可靠性

首先,为了确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:

1.互斥性。在任意时刻,只有一个客户端能持有锁。

2.不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。

3.具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。

4.解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

关于组件依赖自行下载

代码:

/**
    * 获取分布式同步锁
    * 
    * @param key
    * @param value 可空
    * @param expireTimeSeconds
    * @return
    */
   public static boolean getDistribLock(String key, int expireTimeSeconds)
   {
       long expired = new Date().getTime() + expireTimeSeconds + 1;
       String rs = JedisUtils.setnx(key, String.valueOf(expired), expireTimeSeconds);

       // TODO
       // 如果拿不到锁的话, 可以考虑是否发生deadlock
       return rs != null;
   }

这里用redis里面setnx方法是为了保证key的唯一性,我们具体来看看setnx()方法的实现

/**
     * 不存在则设置值,可用于同步锁
     * 
     * @param key
     * @param value
     * @param expireTimeSeconds
     * @return
     */
    public static String setnx(String key, String value, int expireTimeSeconds)
    {
        Jedis jedis = getResource();
        try
        {
            if (logger.isDebugEnabled())
            {
                logger.debug("setnx: key={}|value={}|expireTimeSeconds={}", key, value,
                        expireTimeSeconds);
            }
            else
            {
                logger.info("setnx: key={}|expireTimeSeconds={}", key, expireTimeSeconds);
            }

            return jedis.set(key, value, "NX", "EX", expireTimeSeconds);
        }
        finally
        {
            jedis.close();
        }
    }

jedis.set(key, value, “NX”, “EX”, expireTimeSeconds);分别用了5个参数,具体来看看这5个参数

第一个key, 使用key来当锁,因为key是唯一的。

第二个value 是通过 long expired = new Date().getTime() + expireTimeSeconds + 1;通过当前时间加上传进来的超时时间组成的时间戳转成字符串。 使用value的目的是要足上面的第四个条件 解铃还须系铃人,通过给value赋值知道 ,当前锁是谁请求加的,在解锁的时候就有了依据。

第三个参数"NX" 这个参数我们填的是NX,意思是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作;

第四个参数"EX" 我们要给这个key加一个过期的设置,具体时间由第五个参数决定。

第五个参数为expireTimeSeconds,与第四个参数相呼应,代表key的过期时间

set()方法的结果就是:

  • 1.当前没有锁(key不存在),就进行加锁操作,并设置过期时间,value表示加锁的客户端

  • 2.已经存在锁,不做任何处理

由于我们是单机版的Redis,我们只需要满足可靠性即可,容错性暂不考虑。

  • 互斥性: set()加入了NX参数,可以保证如果已有key存在,则函数不会调用成功,也就是只有一个客户端能持有锁,满足互斥性

  • 不会发生死锁: 由于我们对锁设置了过期时间,即使锁的持有者后续发生崩溃而没有解锁,锁也会因为到了过期时间而自动解锁(即key被删除),不会发生死锁

  • 解铃还须系铃人: 通过时间戳来赋值给value,代表加锁的客户端请求标识,那么在客户端在解锁的时候就可以进行校验是否是同一个客户端

JedisUtils工具类是项目中封装好的了,由于线上是集群环境,考勤统计和定时推送都需要用到分布式锁


文章作者: coderpwh
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 coderpwh !
  目录