springboot+redis+lua實現(xiàn)分布式鎖的腳本
1 分布式鎖
Java鎖能保證一個JVM進程里多個線程交替使用資源。而分布式鎖保證多個JVM進程有序交替使用資源,保證數(shù)據(jù)的完整性和一致性。
分布式鎖要求
互斥。一個資源在某個時刻只能被一個線程訪問。避免死鎖。避免某個線程異常情況不釋放資源,造成死鎖??芍厝?。高可用。高性能。非阻塞,沒獲取到鎖直接返回失敗。
2 實現(xiàn)
1 lua腳本
為了實現(xiàn)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的區(qū)別是: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分布式鎖業(yè)務(wù)類
@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 存在的問題
這個分布式鎖實現(xiàn)了互斥,redis鍵映射資源,如果存在鍵,則資源正被某個線程持有。如果不存在鍵,則資源空閑。
避免死鎖,靠的是設(shè)置reds鍵的過期時間,同時開啟守護線程動態(tài)延長redis鍵的過期時間,直到該線程任務(wù)完結(jié)。
高性能。redis是內(nèi)存數(shù)據(jù)庫,性能很高。同時lua腳本使得redis以原子性更新鎖狀態(tài),避免多次spirngboot與redis的網(wǎng)絡(luò)IO。
非阻塞。lock()
方法沒有獲取到鎖立即返回false,不會阻塞當前線程。
沒有實現(xiàn)可重入和高可用。高可用需要redis集群支持。
到此這篇關(guān)于springboot+redis+lua實現(xiàn)分布式鎖的文章就介紹到這了,更多相關(guān)springboot redis lua分布式鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot3+SpringSecurity6前后端分離的項目實踐
SpringSecurity6 的用法和以前版本的有較大差別,本文主要介紹了SpringBoot3+SpringSecurity6前后端分離的項目實踐,具有一定的參考價值,感興趣的可以了解一下2023-12-12簡單易懂的java8新特性之lambda表達式知識總結(jié)
一直想針對lambda表達式作一個總結(jié),借助于這次公司安排的考試作一個入門式的總結(jié),對正在學習java的小伙伴們非常有幫助,需要的朋友可以參考下2021-05-05java爬蟲之使用HttpClient模擬瀏覽器發(fā)送請求方法詳解
這篇文章主要介紹了java爬蟲之使用HttpClient模擬瀏覽器發(fā)送請求方法詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-07-07windows?java?-jar無法啟動jar包簡單的解決方法
這篇文章主要介紹了windows?java?-jar無法啟動jar包簡單的解決方法,文中通過代碼介紹的非常詳細,對大家學習或者使用java具有一定的參考借鑒價值,需要的朋友可以參考下2024-12-12