redis缓存实现分布式锁

分布式锁的原则
1、相互排斥:任意时刻,只能有一个客户端持有锁。
2、无死锁:尺有所的客户端宕机或网络延迟下仍然额可以获得此锁。
3、有始有终:一个客户端加了锁只能自己解锁。
4、容错性:只要大部分的redis节点还存活,a那么客户端就能正常加锁和释放锁。

依赖:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

获取分布式锁:

public class RedisUtil {
    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "EX";
    /**
     * @param jedis   redis客户端
     * @param lockKey  锁
     * @param requestId  请求标识
     * @param expireTime  过期时间
     * @return
     */
    public static boolean getDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime){
        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
        if (result.equals(LOCK_SUCCESS)){
            return true;
        }
        return false;
    }
}

为什么set方法能够满足前面提到的分布式锁的原则,源码:

/**
* Set the string value as value of the key. The string can't be longer than 1073741824 bytes (1
* GB).
* @param key  唯一标识key
* @param value   存储的值value
* @param nxxx  可选项:NX、XX  其中NX表示当key不存在时才set值,XX表示当key存在时才set值
* @param expx  过期时间单位,可选项:EX|PX 其中EX为seconds,PX为milliseconds
* @param time  过期时间,单位取上一个参数
* @return Status code reply
*/
public String set(final String key, final String value, final String nxxx, final String expx,
    final long time) {
  checkIsInMultiOrPipeline();
  client.set(key, value, nxxx, expx, time);
  return client.getStatusCodeReply();
}

如上的tryDistributedLock就可以实现简单的redis分布式锁了(此set方法的原子性)
1、set方法中nxx参数为NX,表示当key不存在时才会set值,保证了互斥性;
2、set值的同时设置过期时间(过期后del此key),客户端宕机或网络延迟时不会一直持有锁,避免了死锁发生;
3、set方法中的value,比如UUID之类的,用来表示当前请求客户端的唯一性标识;
4、因为是redis单例,暂时没有考虑容错性;

解锁操作

private static final Long RELEASE_SUCCESS = 1L;
/**
* 释放锁
*
* @param jedis     redis客户端
* @param lockKey   锁的key
* @param uniqueId 请求标识
* @return 是否释放
*/
public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String uniqueId) {
    String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
    Object result = jedis.eval(luaScript, Collections.singletonList(lockKey), Collections.singletonList(uniqueId));
    return RELEASE_SUCCESS.equals(result);
}

使用Lua脚本告诉redis,如果这个key存在,且其存储的值和指定的value一致才可以删除这个key,从而释放锁,这样也保证了分布式锁的几个原则. 常见的错误释放锁会直接del这个key,没有考虑当前锁的拥有者,不符合分布式锁原则的有始有终原则;
不适用lua 脚本的话,也可以使用如下代码:

public static void releaseLock(Jedis jedis,String lockKey,String uniqueId){
      if(uniqueId.equals(jedis.get(lockKey))){
          jedis.del(lockKey);
      }
}

如上代码存在这样的场景: Client A去加锁lockKey,然后释放锁,在执行del(lockKey)之前,这时lockKey锁expire到期失效了,此时Client B尝试加锁lockKey成功,Client A接着执行释放锁操作(del),便释放了Client B的锁。

参考文章:
https://blog.csdn.net/fanrenxiang/article/details/79803037

posted @ 2019-03-27 22:21  payn  阅读(686)  评论(0)    收藏  举报