springboot+redis+lua實(shí)現(xiàn)分布式鎖的腳本
1 分布式鎖
Java鎖能保證一個(gè)JVM進(jìn)程里多個(gè)線程交替使用資源。而分布式鎖保證多個(gè)JVM進(jìn)程有序交替使用資源,保證數(shù)據(jù)的完整性和一致性。
分布式鎖要求
互斥。一個(gè)資源在某個(gè)時(shí)刻只能被一個(gè)線程訪問。避免死鎖。避免某個(gè)線程異常情況不釋放資源,造成死鎖。可重入。高可用。高性能。非阻塞,沒獲取到鎖直接返回失敗。
2 實(shí)現(xiàn)
1 lua腳本
為了實(shí)現(xiàn)redis操作的原子性,使用lua腳本。為了方便改腳本,將腳本單獨(dú)寫在文件里。
-- 加鎖腳本
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); // 守護(hù)線程
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 存在的問題
這個(gè)分布式鎖實(shí)現(xiàn)了互斥,redis鍵映射資源,如果存在鍵,則資源正被某個(gè)線程持有。如果不存在鍵,則資源空閑。
避免死鎖,靠的是設(shè)置reds鍵的過期時(shí)間,同時(shí)開啟守護(hù)線程動態(tài)延長redis鍵的過期時(shí)間,直到該線程任務(wù)完結(jié)。
高性能。redis是內(nèi)存數(shù)據(jù)庫,性能很高。同時(shí)lua腳本使得redis以原子性更新鎖狀態(tài),避免多次spirngboot與redis的網(wǎng)絡(luò)IO。
非阻塞。lock()方法沒有獲取到鎖立即返回false,不會阻塞當(dāng)前線程。
沒有實(shí)現(xiàn)可重入和高可用。高可用需要redis集群支持。
到此這篇關(guān)于springboot+redis+lua實(shí)現(xiàn)分布式鎖的文章就介紹到這了,更多相關(guān)springboot redis lua分布式鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)現(xiàn)調(diào)用ElasticSearch?API的示例詳解
這篇文章主要為大家詳細(xì)介紹了Java調(diào)用ElasticSearch?API的效果資料,文中的示例代碼講解詳細(xì),具有一定的參考價(jià)值,感興趣的可以了解一下2023-03-03
java lambda 表達(dá)式中的雙冒號的用法說明 ::
這篇文章主要介紹了java lambda 表達(dá)式中的雙冒號的用法說明 ::具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-09-09
SpringBoot 開發(fā)提速神器 Lombok+MybatisPlus+SwaggerUI
這篇文章主要介紹了SpringBoot 開發(fā)提速神器 Lombok+MybatisPlus+SwaggerUI,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03
Java JMH進(jìn)行基準(zhǔn)測試的使用小結(jié)
在 Java 的依賴庫中,有個(gè)大名鼎鼎的 JMH(Java Microbenchmark Harness),是由 Java虛擬機(jī)團(tuán)隊(duì)開發(fā)的 Java 基準(zhǔn)測試工具,下面小編來和大家講講它的具體使用吧2025-05-05
JavaSE中compare、compareTo的區(qū)別
本文主要介紹了JavaSE中compare、compareTo的區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05
Java結(jié)合uniapp實(shí)現(xiàn)驗(yàn)證碼功能的示例詳解
UniApp 是一個(gè)基于 Vue.js 的跨平臺應(yīng)用開發(fā)框架,允許開發(fā)者使用統(tǒng)一的代碼庫來構(gòu)建多平臺應(yīng)用,這篇文章將給大家介紹Java結(jié)合uniapp實(shí)現(xiàn)驗(yàn)證碼功能,文中通過詳細(xì)的代碼示例講解的非常詳細(xì),需要的朋友可以參考下2024-07-07

