千家信息网

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

发表于:2024-12-13 作者:千家信息网编辑
千家信息网最后更新 2024年12月13日,这篇文章主要介绍"Java中redis分布式锁的实现方法",在日常操作中,相信很多人在Java中redis分布式锁的实现方法问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答
千家信息网最后更新 2024年12月13日Java中redis分布式锁的实现方法

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

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 0end
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分布式锁的实现方法"的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注网站,小编会继续努力为大家带来更多实用的文章!

0