Java中redis分布式锁的实现方法

这篇文章主要介绍“Java中redis分布式锁的实现方法”,在日常操作中,相信很多人在Java中redis分布式锁的实现方法问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java中redis分布式锁的实现方法”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!

成都创新互联公司主营玛曲网站建设的网络公司,主营网站建设方案,成都app软件开发公司,玛曲h5重庆小程序开发搭建,玛曲网站营销推广欢迎玛曲等地区企业咨询

redis分布式锁

1.什么是分布式锁?

分布式锁是用于解决多个进程互斥地访问共享资源时产生的问题。

举个例子,当你做新增操作时,为了防止重复插入,需要进行“查找->是否存在->不存在->添加”的逻辑判断。

如果有两个线程同时进入这个逻辑判断,那么两个线程同时进入“不存在”这个判断时,就会有两次插入操作。

一般为了解决这种问题,Java中可以用synchronized等线程安全的方法来解决,但是当应用是多实例部署时,就需要用分布式锁来解决了。

2.分布式锁有哪些?

1.基于数据库

2.基于redis

3.基于中间件(zookeeper)

3.分布式锁的使用条件

使用分布式锁需要满足以下三个条件

1.互斥。即锁只能由一个线程获取。

2.不会死锁。线程崩溃等原因导致长时间获取锁不释放,要有机制能自动释放锁。

3.不能误解锁。加锁和解锁必须要是同一个线程。

4.redis分布式锁的原理

为什么使用redis作为分布式锁呢?

最主要的原因是redis是单线程访问的,指令是按队列一条条顺序执行。

我们只需要把上锁这个操作的指令写成一个脚本,让redis执行即可。

5.具体步骤

5.1.获取锁对象

锁对象包括两个值key和requestId。

key即为锁的key,一般是场景+唯一标识

requestId是锁的值,用于解锁时能保证不会误解锁,一般是key+时间戳。

public RedisLockBean getLockBean(String lock, String id){

    String LockKey = lock.concat(id);
    String requestId = LockKey.concat(String.valueOf(System.currentTimeMillis()));

    return new RedisLockBean().setKey(LockKey).setRequestId(requestId);
}
5.2.加锁

使用代码执行命令

SET key requestId NX PX 10000

NX: 如果不存在就设置

PX milliseconds: 键的过期时间设置为多少毫秒

private static final String SET_IF_ABSENT = "NX";
private static final String SET_WITH_EXPIRE_TIME = "PX";
private Long expireTime = 10000L;

public Boolean setLock(RedisLockBean lockBean){

    RedisCallback stringRedisCallback = (connection) ->{
        JedisCommands commands  = (JedisCommands) connection.getNativeConnection();
        return commands.set(lockBean.getKey(), lockBean.getRequestId(), SET_IF_ABSENT, SET_WITH_EXPIRE_TIME, expireTime);
    };

    String result = (String) redisTemplate.execute(stringRedisCallback);
    return !StringUtils.isEmpty(result);
}
5.3.判断是否获取到锁,如果没有获取到,可循环获取,或抛出异常。
5.4.解锁

执行命令

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

static {
    StringBuilder sb = new StringBuilder();
    sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
    sb.append("then ");
    sb.append("    return redis.call(\"del\",KEYS[1]) ");
    sb.append("else ");
    sb.append("    return 0 ");
    sb.append("end ");
    UNLOCK_LUA = sb.toString();
}

public Boolean releaseLock(RedisLockBean lockBean){
    // 释放锁的时候,有可能因为持锁之后方法执行时间大于锁的有效期,此时有可能已经被另外一个线程持有锁,所以不能直接删除
    try {
        List keys = new ArrayList<>();
        keys.add(lockBean.getKey());
        List args = new ArrayList<>();
        args.add(lockBean.getRequestId());
    
        // 使用lua脚本删除redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁
        // spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本的异常,所以只能拿到原redis的connection来执行脚本
        RedisCallback callback = (connection) -> {
            Object nativeConnection = connection.getNativeConnection();
            // 集群模式和单机模式虽然执行脚本的方法一样,但是没有共同的接口,所以只能分开执行
            // 集群模式
            if (nativeConnection instanceof JedisCluster) {
                return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args);
            }
    
            // 单机模式
            else if (nativeConnection instanceof JedisCommands) {
                return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args);
            }
            return 0L;
        };
        Long result = (Long) redisTemplate.execute(callback);
    
        return result != null && result > 0;
    } catch (Exception e) {
        log.error("release lock occured an exception", e);
    }
    return false;
}

到此,关于“Java中redis分布式锁的实现方法”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注创新互联网站,小编会继续努力为大家带来更多实用的文章!


当前标题:Java中redis分布式锁的实现方法
URL标题:http://pwwzsj.com/article/giggii.html