这篇文章主要介绍“Java中redis分布式锁的实现方法”,在日常操作中,相信很多人在Java中redis分布式锁的实现方法问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Java中redis分布式锁的实现方法”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
分布式锁是用于解决多个进程互斥地访问共享资源时产生的问题。
举个例子,当你做新增操作时,为了防止重复插入,需要进行“查找->是否存在->不存在->添加”的逻辑判断。
如果有两个线程同时进入这个逻辑判断,那么两个线程同时进入“不存在”这个判断时,就会有两次插入操作。
一般为了解决这种问题,Java中可以用synchronized等线程安全的方法来解决,但是当应用是多实例部署时,就需要用分布式锁来解决了。
1.基于数据库
2.基于redis
3.基于中间件(zookeeper)
使用分布式锁需要满足以下三个条件
1.互斥。即锁只能由一个线程获取。
2.不会死锁。线程崩溃等原因导致长时间获取锁不释放,要有机制能自动释放锁。
3.不能误解锁。加锁和解锁必须要是同一个线程。
为什么使用redis作为分布式锁呢?
最主要的原因是redis是单线程访问的,指令是按队列一条条顺序执行。
我们只需要把上锁这个操作的指令写成一个脚本,让redis执行即可。
锁对象包括两个值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); }
使用代码执行命令
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<String> 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); }
执行命令
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<String> keys = new ArrayList<>(); keys.add(lockBean.getKey()); List<String> args = new ArrayList<>(); args.add(lockBean.getRequestId()); // 使用lua脚本删除redis中匹配value的key,可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁 // spring自带的执行脚本方法中,集群模式直接抛出不支持执行脚本的异常,所以只能拿到原redis的connection来执行脚本 RedisCallback<Long> 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分布式锁的实现方法”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。