基于Lua脚本实现Redis分布式锁(乐观锁)

大兔子大兔子 提交于 2020-01-20 10:29:25

使用redis调用lua脚本实现原子性操作
如果对redis提供的指令集不满足 可以通过lua脚本执行自定义操作来保证执行的原子性,比如实现将匹配 key 和删除 key 合并在一起执行等。
Redis 服务器会单线程原子性执行 lua 脚本,保证 lua 脚本在处理的过程中不会被任意其它请求打断。

public class RedisLock {
     
    private JedisTemplate jedisTemplate = SpringContextHolder.getBean(JedisTemplate.class);
     
    private static final Logger logger = LoggerFactory.getLogger(RedisLock.class);
    private final static String REDIS_PREFIX = "redis_prefix_";
    private final  static String NX = "NX";
    private final static String PX = "PX";
    private final static String OK = "OK";
    /**
     * 超时时间
     * 默认1秒
     */
    private Integer timeout = 1_000;
    private String lockName;
 
 
    /**
     * 记录获取锁的线程标识
     * 防止删除其他线程的key
     */
    private ThreadLocal<String> threadId = new ThreadLocal<>();
     
    public RedisLock(String lockName) {
        this.lockName = REDIS_PREFIX + lockName;
    }
     
    public RedisLock(String lockName, Integer lockTime) {
        this.lockName = REDIS_PREFIX + lockName;
        this.timeout = lockTime;
    }
     
    public boolean tryLock() {
        threadId.set(UUID.randomUUID().toString());
        String result = jedisTemplate.set(lockName, threadId.get(),NX,PX,timeout);
        logger.info("锁的key:"+ lockName +", 设置锁的结果:" + result);
        return OK.equals(result);
    }
     
    /**
     * 避免删除非自己获取得到的锁
     * @return
     */
    public boolean releaseLock() {
        String threadIdStr = threadId.get();
        jedisTemplate.compareAndDel(lockName, threadIdStr);
        threadId.remove();
        return true;
    }
}
 
 
 
/**
 *
 * @param key
 * @param value
 * @param nxxx  NX
 * @param expx EX-秒  PX-毫秒
 * @param time
 * @return
 */
public String set(final String key, final String value, final String nxxx, final String expx,
                  final int time) {
    return  execute(new JedisAction<String>() {
     
        @Override
        public String action(Jedis jedis) {
             return jedis.set(key, value,nxxx,expx,time);
        }
    });
}
 
/**
 * 比较删除
 * 保证原子性
 * @param key
 * @param value
 * @return
 */
public boolean compareAndDel(final String key, final String value) {
    return execute(new JedisAction<Boolean>() {
        String lua = "   local val = redis.call('get', KEYS[1])\n" +
                "             if val == ARGV[1] then\n" +
                "             redis.call('del', KEYS[1])\n" +
                "             return true\n" +
                "             end";
        /**
          lua 脚本保证一系列操作的原子性
         local val = redis.call('get', KEYS[1])
         if val == ARGV[1] then
         redis.call('del', KEYS[1])
         return true
         end
         */
        @Override
        public Boolean action(Jedis jedis) {
            jedis.evalsha(jedis.scriptLoad(lua), Lists.newArrayList(key), Lists.newArrayList(value));
            return true;
        }
    });
}

具体调用方法:

RedisLock redisLock = new RedisLock(key, 5000);
try {
    if (!redisLock.tryLock()) {
        throw new BusinessException(ReturnCode.ACTION_FREQ);
    }
} catch (Exception e) {
    logger.error("error", e);
    return buildResponse(ReturnCode.UNDIFINED_ERROR, null);
} finally {
   //不要等他过期
    redisLock.releaseLock();
}

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!