实现分布式锁的方式有很多,可以通过redis、mysql、zk等方式去实现一个分布式锁。什么情况下需要用到分布式锁呢?一种是多个微服务实例,另外一种是在多线程的情况下可能也需要用到。
今天主要介绍用redis怎么去实现分布式锁。
实现分布式锁,需要考虑3个要素:
1.锁过期时间
2.锁的释放
3.锁的续期

1.锁过期时间:之所以要设置锁的过期时间是因为在服务宕机的场景下,没办法去主动释放锁,因此只能基于redis的缓存过期时间来辅助释放。

2.锁的释放:锁的释放一般是在try{}catch(){}finally{}中的finally里面去释放,这样可以保证就算业务发生报错,也能即使把锁释放掉

3.锁的续期:有一种场景是当前的业务执行时间比锁的过期时间还要长,就会出现,当前业务还没执行完,锁就被释放掉了,为了避免这种情况,需要单独为这个锁重启一个线程,专门用来观察业务是否执行完毕,如果在锁快到期之前,业务还没执行完,那么该线程会更新这个锁的过期时间。当然如果业务时间很短,能保证每次执行的业务时间在过期时间内,那么就没必要去增加一个锁的续期

以下为代码实现:
1.加锁,加锁成功后再触发watchDog,一般redis加锁可以直接通过setnx或者hsetnx的方式,也可以用lua表达式,一般推荐用lua表达式,因为这样可以写出更丰富的逻辑,并且能保证一条lua脚本是在一个原子操作里面执行的。

    public boolean lock(String id, int keyExpireTime, int watchTime) throws InterruptedException {
        for (; ; ) {
            //HSET命令返回OK ,则证明获取锁成功
            String script = "if (redis.call('exists', KEYS[1]) ~= 1) then "
                    + "return redis.call('hsetnx', KEYS[1], KEYS[2],ARGV[1]) "
                    + "else "
                    + "return 0 "
                    + "end";
            List<String> keys = new ArrayList<>();
            keys.add(LOCK_KEY);
            keys.add(id);
            List<String> arg = new ArrayList<>();
            arg.add(""+keyExpireTime);
            String result = null;
            Jedis jedis = null;
            try {
                jedis = jedisPool.getResource();
                result = jedis.eval(script, keys, arg).toString();
            } catch (Exception e) {
                e.getStackTrace();
            }finally {
                jedis.close();
            }
            if ("1".equals(result)) {

                Runnable r = ()->{
                    try {
                        watchDog(id, keyExpireTime,watchTime);
                    } catch (Exception e) {
                        e.getStackTrace();
                        throw new RuntimeException(e);
                    }
                };
                pool.execute(r);
                System.out.println(id+"获取到锁");
                return true;
            }
        }
    }

2.解锁:

    public boolean unlock(String id) {
        Jedis jedis = jedisPool.getResource();
        List<String> keys = new ArrayList<>();
        keys.add(LOCK_KEY);
        keys.add(id);
        //LUA表达式
        String script =
                "if (redis.call('hexists', KEYS[1], KEYS[2]) == 1) then " +
                        "return redis.call('del',KEYS[1]) " +
                        "else " +
                        "return 0 " +
                        "end";
        String result = jedis.eval(script, keys, Collections.singletonList(id)).toString();
        jedis.close();
        boolean flag = "1".equals(result) ? true : false;
        if (flag){
            System.out.println(id+"解锁");
        }
        return flag;
    }

3.续期(watchDog)

    public boolean watchDog(String uuid, int keyExpireTime,int time) {
        //每time秒去检查一下线程任务是否执行完毕,如果没有执行完毕,那么需要对锁进行续签
        String script = "if (redis.call('hexists', KEYS[1],KEYS[2]) == 1) then "
                + "return redis.call('expire', KEYS[1], ARGV[1]) "
                + "else  "
                + "return 0 "
                + "end ";

        List<String> arg = new ArrayList<>();
        List<String> keys = new ArrayList<>();
        keys.add(LOCK_KEY);
        keys.add(uuid);
        arg.add(keyExpireTime+"");

        String result = null;
        Jedis jedis = jedisPool.getResource();
        try {
            result = jedis.eval(script, keys, arg).toString();
        } catch (Exception e) {
            e.getStackTrace();
            throw new RuntimeException(e);
        }finally {
            jedis.close();
        }
        boolean flag = "1".equals(result) ? true : false;
        if (flag){
            try {
                System.out.println(uuid+"续期");
                Thread.sleep(time*1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return watchDog(uuid,keyExpireTime,time);
        }else {
            System.out.println("续期结束");
            return false;
        }
    }