jedis+redisTemplate实现redis分布式锁

本文是一篇简单封装redis做分布式锁的工具类文章,不涉及原理,源码,不适用于redis集群!

// 利用jedis实现

@Slf4j
public class RedisLockUtil {
    private static final String LOCK_SUCCESS = "OK";
    private static final String SET_IF_NOT_EXIST = "NX";
    private static final String SET_WITH_EXPIRE_TIME = "PX";

    /**
     * @description: 获取redis锁
     * @param: [jedis, lockKey, lockValue, expire]
     * @return: boolean
     * @author: apollo
     * @date: 2019-10-17
     */
    public static boolean tryLock(Jedis jedis, String lockKey, String lockValue, int expire) {
        try {
            String result = jedis.set(lockKey, lockValue, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expire);
            return Objects.equals(result, LOCK_SUCCESS);
        } catch (Exception e) {
            log.error("获取redis锁失败,错误信息: ", e);
            return false;
        } finally {
            //释放连接
            close(jedis);
        }
    }

    /**
     * @description: 释放redis锁(使用lua脚本),需要Redis服务器支持lua脚本
     * @param: [lockKey, lockValue]
     * @return: boolean
     * @author: apollo
     * @date: 2019-10-17
     */
    public static boolean unLuaLock(Jedis jedis, String lockKey, String lockValue) {
        try {
            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
            Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(lockValue));
            return Objects.equals(result, LOCK_SUCCESS);
        } catch (Exception e) {
            log.error("释放redis锁失败,错误信息: ", e);
            return false;
        } finally {
            close(jedis);
        }
    }

    /**
     * @description: 释放redis锁, 高并发的情况下可能存在风险,无法保证get指令和del指令的原子性
     * @param: [lockKey, lockValue]
     * @return: boolean
     * @author: apollo
     * @date: 2019-10-17
     */
    public static boolean unLock(Jedis jedis, String lockKey, String lockValue) {
        try {
            String value = jedis.get(lockKey);
            if (value.equals(lockValue)) {
                jedis.del(lockKey);
                return true;
            }
            log.error("释放redis锁失败,释放别人锁资源,lockKey: {}, lockValue: {} ", lockKey, lockValue);
            return false;
        } catch (Exception e) {
            log.error("释放redis锁失败,错误信息: ", e);
            return false;
        } finally {
            close(jedis);
        }
    }

    private static void close(Jedis jedis) {
        //释放连接
        if (jedis != null) {
            jedis.close();
        }
    }

// 利用redisTemplate实现

@Slf4j
@Component
public class RedisLockUtil {
    private static final String LOCK_SUCCESS = "OK";

    private static final String SET_IF_NOT_EXIST = "NX";

    private static final String SET_WITH_EXPIRE_TIME = "PX";

    private static final String SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    @Resource
    private RedisTemplate<String, String> redisTemplate;

    /**
     * @description: 获取redis锁
     * @param: [jedis, lockKey, lockValue, expireTime]
     * @return: boolean
     * @author: apollo
     */
    public boolean tryLock(String lockKey, String lockValue, long expireTime) {
        try {
            String execute = redisTemplate.execute((RedisCallback<String>) connection -> {
                JedisCommands jedisCommands = (JedisCommands) connection.getNativeConnection();
                return jedisCommands.set(lockKey, lockValue, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
            });
            return Objects.equals(execute, LOCK_SUCCESS);
        } catch (Exception e) {
            log.error("获取redis锁失败,错误信息: ", e);
        }
        return Boolean.FALSE;
    }

    /**
     * @description: 释放redis锁(使用lua脚本),需要Redis服务器支持lua脚本
     * @param: [lockKey, lockValue]
     * @return: boolean
     * @author: apollo
     */
    public boolean releaseLuaLock(String lockKey, String lockValue) {
        try {
            Long execute = redisTemplate.execute((RedisCallback<Long>) connection -> {
                Object nativeConnection = connection.getNativeConnection();
                // 集群模式
                if (nativeConnection instanceof JedisCluster) {
                    return (Long) ((JedisCluster) nativeConnection).eval(SCRIPT, Collections.singletonList(lockKey), Collections.singletonList(lockValue));
                } else if (nativeConnection instanceof Jedis) {
                    // 单机模式
                    return (Long) ((Jedis) nativeConnection).eval(SCRIPT, Collections.singletonList(lockKey), Collections.singletonList(lockValue));
                }
                return 0L;
            });
            return Objects.nonNull(execute) && execute > 0;
        } catch (Exception e) {
            log.error("释放redis锁失败,错误信息: ", e);
        }
        return Boolean.FALSE;
    }

    /**
     * @description: 释放redis锁, 高并发的情况下可能存在风险,无法保证get指令和del指令的原子性
     * @param: [lockKey, lockValue]
     * @return: boolean
     * @author: apollo
     */
    public boolean releaseLock(String lockKey, String lockValue) {
        try {
            String value = redisTemplate.opsForValue().get(lockKey);
            if (Objects.equals(value, lockValue)) {
                return Optional.ofNullable(redisTemplate.delete(lockKey)).orElse(Boolean.FALSE);
            }
            log.error("释放redis锁失败,释放别人锁资源,lockKey: {}, lockValue: {} ", lockKey, lockValue);
        } catch (Exception e) {
            log.error("释放redis锁失败,错误信息: ", e);
        }
        return Boolean.FALSE;
    }

}

ps:大家众所周知redis是单线程模型,天然原子性!然而并不意味着redis只有一个线程,这是大错特错,redis的io多路复用模型就使用了线程池,主从同步也是fork子线程等等。redis的单线程模型可以理解为它的事件处理是单线程的,通俗一点就是,一个get指令或者是别的指令是原子性。在上述unLock()方法中,需要先get,在代码中equals,然后再del会存在风险。建议大家使用unLuaLock(), lua脚本作用就是get,equals,del, 这段脚本对于redis来说就是一个指令(事件),是天然支持原子性!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值