springboot+redis+lua实现分布式锁的脚本
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分布式锁内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
- SpringBoot+Redis执行lua脚本的5种方式总结
- Springboot+Redis执行lua脚本的项目实践
- springboot使用redisTemplate操作lua脚本
- springboot中使用redis并且执行调试lua脚本
- SpringBoot通过redisTemplate调用lua脚本并打印调试信息到redis log(方法步骤详解)
- SpringBoot通过RedisTemplate执行Lua脚本的方法步骤
- SpringBoot+Redis执行lua脚本的方法步骤
- SpringBoot利用注解来实现Redis分布式锁
- SpringBoot基于Redis的分布式锁实现过程记录
- 关于SpringBoot 使用 Redis 分布式锁解决并发问题
相关文章
创建动态代理对象bean,并动态注入到spring容器中的操作
这篇文章主要介绍了创建动态代理对象bean,并动态注入到spring容器中的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧2021-02-02Java lambda list转换map时,把多个参数拼接作为key操作
这篇文章主要介绍了Java lambda list转换map时,把多个参数拼接作为key操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧2020-08-08完美解决Server returned HTTP response code:403 for URL报错问题
在调用某个接口的时候,突然就遇到了Server returned HTTP response code: 403 for URL报错这个报错,导致获取不到接口的数据,下面小编给大家分享解决Server returned HTTP response code:403 for URL报错问题,感兴趣的朋友一起看看吧2023-03-03
最新评论