Java 实现分布式锁


利用redis实现分布式锁
// 分布式锁实现使用的版本
// springBoot的版本:org.springframework.boot:spring-boot:1.5.8.RELEASE
// redis的版本:org.springframework.data:spring-data-redis:1.8.8.RELEASE
// lombok的版本:org.projectlombok:lombok:1.18.6

// 标识springBoot启动时会扫描该类并创建实例放入spring容器
@Component
// 引入lombokjar包,才能使用的注解
@Slf4j
public class DistributedLock {
    private static final String LOCK_PREFIX = "REDIS_LOCK_";
    //加锁失效时间,单位:秒
    public static final int LOCK_EXPIRE = 3;

    // 此处注入的RedisTemplate实例信息,可以查看Redis与SpringBoot融合这篇工具类博客
    // 文章地址:https://carefulhuo.github.io/posts/c51e64d0/
    @Autowired
    RedisTemplate redisTemplate;

    /**
     * 尝试获取分布式锁
     *
     * @param lockKey    锁
     * @param requestId  请求标识
     * @return 是否获取成功
     */
    public boolean tryLock(String lockKey, String requestId) {
        log.info("{}开始加锁,requestId:{}", lockKey, requestId);
        String lock = LOCK_PREFIX + lockKey;
        // 利用lambda表达式
        // setNX英文全称:SET if Not Exists
        // setNX含义:如果key存在,设置失败返回0;如果key不存在,设置成功返回1
        // setNX方法:原子性的,但是该命令不能设置超时时间
        // expire方法:设置超时时间,防止死锁
        // 注意其中的RedisCallback,当redis有多个实例时,数据进行主从同步时,但主redis挂掉,随机选择一个从redis作为主redis时,不会发生多个请求获取到锁。
        // 原理如下:加锁时,会向多半的节点发送setNX命令,如果多半节点成功,则算加锁成功,那么释放锁的时候,需要想所有节点发送del命令
        return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
            if (connection.setNX(lock.getBytes(), requestId.getBytes())) {
                connection.expire(lock.getBytes(), LOCK_EXPIRE);
                return true;
            }
            return false;
        });
    }

    /**
     * 释放分布式锁
     *
     * @param lockKey   锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    public boolean releaseLock(String lockKey, String requestId) {
        log.info("{}开始释放锁,requestId:{}", lockKey, requestId);
        String lock = LOCK_PREFIX + lockKey;
        final String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end ";

        // eval命令的参数(lua脚本,返回类型,脚本的参数个数,redis的key,redis的value)
        // redis执行lua脚本是原子性的
        return (Boolean)redisTemplate.execute((RedisCallback) connection -> {
            return connection.eval(script.getBytes(), ReturnType.BOOLEAN ,1, lock.getBytes(Charset.forName("UTF-8")), requestId.getBytes(Charset.forName("UTF-8")));
        });
    }
}
-- 上述释放redis锁需要redis执行的lua脚本
-- 代码含义:
-- if 中的比较如果是true , 那么 执行 del 并返回del结果;如果 if 结果为false 直接返回 0 。
if redis.call('get', KEYS[1]) == ARGV[1]
    then
        return redis.call('del', KEYS[1])
    else
        return 0
end

文章作者: Huowy
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Huowy !
评论
  目录