redis实现的锁

须要考虑的事情有:java

  1. 释放锁的线程必定要与加锁的线程一致! (生产实际案例: 多笔交易同时还同一笔借款还款计划的不一样期时,因并发,致使意外释放锁被非加锁线程释放。致使还款计划出现前期还处理还款中,后期已经还款成功)
  2. 合理控制加锁等待时间(网络抖动形成链接redis服务异常)
  3. 若是获取锁失败,须要明确锁失败的缘由( 如:链接超时、锁被暂用、异常 等状况), 合理肯定降级方案(本地缓存来作锁(前提是同一个key的请求须要负载均衡到同一台机器上) 或 按需返回true or false。)
  4. 合理设置锁的自动失效时间
  5. 程序处理finally必定要主动释放锁
  6. 在加锁和释放锁时要考虑原子性。 加锁:SETNX ;释放锁: 执行script:
    if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end

     

加锁时:redis

建立一个随机数,经过redis 提供的SET_IF_NOT_EXIST方式写入这个key。并保存随机数在线程上下文中缓存

private static final String SET_IF_NOT_EXIST = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
private static final String LOCK_SUCCESS = "OK";
private static ThreadLocal<String> ownerThreadLocal = new ThreadLocal<>();

源码以下:网络

在释放锁时: 并发

经过执行脚本的方式保证在并发下的一致性! 只有当线程上下文对象中的值与redis保存的值一致时,才能删掉这个key,释放成功。 这样能够保证加锁与释放锁的都是这个线程!负载均衡

private static final String UNLOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
   private static final long UNLOCK_SUCCESS = 1L;
public boolean unlock(String key) {
        String ownerId = ownerThreadLocal.get();
        if (StringUtils.isBlank(ownerId)) {
            return false;
        }

        try {
            List<String> keys = Collections.singletonList(key);
            //lua
            Long result = stringRedisTemplate.execute(new DefaultRedisScript<>(UNLOCK_SCRIPT, Long.class), keys, ownerId);
            if (UNLOCK_SUCCESS == result) {
                return true;
            }
        } catch (Exception e) {
            log.error("redis unlock fail", e);
        } finally {
            ownerThreadLocal.remove();
        }
        return false;
    }
相关文章
相关标签/搜索