分布式锁(Redis)

  1. 基于数据库的
  2. 基于redis
  3. 基于zookeeper

基于数据库

基于redis

先来看第一种

public static void demo(Jedis jedis, String lockKey, String requestId, int expireTime) {
// setnx 是set if not exist,r如果不存在,则插入,返回1 否则返回0
//lockkey就是需要获取锁的名称或者id  value是该线程id
    Long result = jedis.setnx(lockKey, requestId);
    if (result == 1) {
        // 若在这里程序突然崩溃,则无法设置过期时间,将发生死锁
        //也就是无法保证和上一个操作的原子性
        jedis.expire(lockKey, expireTime);
    }

}

改进版 redis 2.6.12

/**
     * 获取分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @expireTime 过期时间
     */
  public static boolean tryGetDistributedLock(Jedis jedis, String lockKey, String requestId, int expireTime) {
// 这个方法就可以保证过期时间和获取锁操作的原子性
        String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);

        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }

}

上面value用当前线程的id目的:

  • 如果线程在过期时间结束之前完成任务,要执行del操作,释放锁。
  • 如果线程A设置过期时间30s,30s之后该线程还没有执行完,其他线程B获取到锁
  • 这时候A执行完了,开始执行del,这时候就会删除掉B获取的锁,所以要用requestId做判断。

删除操作

if(threadId.equals(jedis.get(lockkey))){
    del(lockkey)
}

同样的,判断和删除操作无法保证原子性!

这时候解决办法就是用lua脚本

public class RedisTool {

    private static final Long RELEASE_SUCCESS = 1L;

    /**
     * 释放分布式锁
     * @param jedis Redis客户端
     * @param lockKey 锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {
//这里用到lua脚本
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        //eval 第一个是lua脚本 第二个是参数个数 从第三个开始是变量 KEY[1] KEY[2]等  后面是附加
        Object result = jedis.eval(script, 2,,Collections.singletonList(lockKey), Collections.singletonList(requestId));

        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;

    }

}

    /**
     * @param  script  要执行的脚本
     * @param numbers 指定键名参数的个数
     * @param key 脚本中的redis的key 从第三个参数开始 KEY[1] KEY[2]等等
     * @return arg 附加参数 ARGV[1] ARGV[2]等等
     */
EVAL script numbers key[key ...] arg[arg ...]


为什么用脚本就能保证原子性呢?

补充

上面的情况。线程A没有执行完,线程B获取锁,怎么解决呢?

用守护线程。 线程A在最后一秒还没有执行完的话,就继续增加过期时间。

你可能感兴趣的:(分布式锁(Redis))