1 分布式锁
java锁能保证一个jvm进程里多个线程交替使用资源。而分布式锁保证多个jvm进程有序交替使用资源,保证数据的完整性和一致性。
分布式锁要求
互斥。一个资源在某个时刻只能被一个线程访问。避免死锁。避免某个线程异常情况不释放资源,造成死锁。可重入。高可用。高性能。非阻塞,没获取到锁直接返回失败。
2 实现
1 lua脚本
为了实现redis操作的原子性,使用lua脚本。为了方便改脚本,将脚本单独写在文件里。
-- 加锁脚本 if redis.call('setnx', keys[1], argv[1]) == 1 then redis.call('pexpire', keys[1], argv[2]); return true; else return false; end -- 解锁脚本 if redis.call('get', keys[1]) == argv[1] then redis.call('del', keys[1]); return true; else return false; end -- 更新锁脚本 if redis.call('get', keys[1]) == argv[1] then redis.call('pexpire', keys[1], argv[2]); -- pexpire与expire的区别是:pexpire毫秒级,expire秒级 return true; else return false; end
将脚本装在springboot容器管理的bean里。
@configuration public class redisconfig { @bean("lock") public redisscript<boolean> lockredisscript() { defaultredisscript redisscript = new defaultredisscript<>(); redisscript.setresulttype(boolean.class); redisscript.setscriptsource(new resourcescriptsource(new classpathresource("/ratelimit/lock.lua"))); return redisscript; } @bean("unlock") public redisscript<boolean> unlockredisscript() { defaultredisscript redisscript = new defaultredisscript<>(); redisscript.setresulttype(boolean.class); redisscript.setscriptsource(new resourcescriptsource(new classpathresource("/ratelimit/unlock.lua"))); return redisscript; } @bean("refresh") public redisscript<boolean> refreshredisscript() { defaultredisscript redisscript = new defaultredisscript<>(); redisscript.setresulttype(boolean.class); redisscript.setscriptsource(new resourcescriptsource(new classpathresource("/ratelimit/refresh.lua"))); return redisscript; } }
redis分布式锁业务类
@service public class lockservice { private static final long lock_expire = 30_000; private static final logger logger = loggerfactory.getlogger(lockservice.class); @autowired private redistemplate<string, object> redistemplate; @autowired @qualifier("lock") private redisscript<boolean> lockscript; @autowired @qualifier("unlock") private redisscript<boolean> unlockscript; @autowired @qualifier("refresh") private redisscript<boolean> refreshscript; public boolean lock(string key, string value) { boolean res = redistemplate.execute(lockscript, list.of(key), value, lock_expire); if (res == false) { return false; } refresh(key, value); logger.info("lock, key: {}, value: {}, res: {}", key, value, res); return res; } public boolean unlock(string key, string value) { boolean res = redistemplate.execute(unlockscript, list.of(key), value); logger.info("unlock, key: {}, value: {}, res: {}", key, value, res); return res != null && boolean.true.equals(res); } private void refresh(string key, string value) { thread t = new thread(() -> { while (true) { redistemplate.execute(refreshscript, list.of(key), value, lock_expire); try { thread.sleep(lock_expire / 2); } catch (interruptedexception e) { e.printstacktrace(); } logger.info("refresh, current time: {}, key: {}, value: {}", system.currenttimemillis(), key, value); } }); t.setdaemon(true); // 守护线程 t.start(); } }
测试类
@springboottest(classes = demoapplication.class) public class lockservicetest { @autowired private lockservice service; private int count = 0; @test public void test() throws exception { list<completablefuture<void>> tasklist = new arraylist<>(); for (int threadindex = 0; threadindex < 10; threadindex++) { completablefuture<void> task = completablefuture.runasync(() -> addcount()); tasklist.add(task); } completablefuture.allof(tasklist.toarray(new completablefuture[0])).join(); } public void addcount() { string id = uuid.randomuuid().tostring().replace("-", ""); boolean trylock = service.lock("account", id); while (!trylock) { trylock = service.lock("account", id); } for (int i = 0; i < 10_000; i++) { count++; } try { thread.sleep(100_000); } catch (exception e) { system.out.println(e); } for (int i = 0; i < 3; i++) { boolean releaselock = service.unlock("account", id); if (releaselock) { break; } } } }
3 存在的问题
这个分布式锁实现了互斥,redis键映射资源,如果存在键,则资源正被某个线程持有。如果不存在键,则资源空闲。
避免死锁,靠的是设置reds键的过期时间,同时开启守护线程动态延长redis键的过期时间,直到该线程任务完结。
高性能。redis是内存数据库,性能很高。同时lua脚本使得redis以原子性更新锁状态,避免多次spirngboot与redis的网络io。
非阻塞。lock()
方法没有获取到锁立即返回false,不会阻塞当前线程。
没有实现可重入和高可用。高可用需要redis集群支持。
到此这篇关于springboot+redis+lua实现分布式锁的文章就介绍到这了,更多相关springboot redis lua分布式锁内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论