用Lua腳本實(shí)現(xiàn)Redis原子操作的示例
1. 環(huán)境準(zhǔn)備
依賴:在pom.xml中添加Spring Data Redis:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置RedisTemplate:
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
}
2. 編寫Lua腳本
以分布式鎖為例,實(shí)現(xiàn)加鎖和解鎖的原子操作:
加鎖腳本 lock.lua
local key = KEYS[1]
local value = ARGV[1]
local expire = ARGV[2]
-- 如果key不存在則設(shè)置,并添加過(guò)期時(shí)間
if redis.call('setnx', key, value) == 1 then
redis.call('expire', key, expire)
return 1 -- 加鎖成功
else
return 0 -- 加鎖失敗
end
解鎖腳本 unlock.lua
local key = KEYS[1]
local value = ARGV[1]
-- 只有鎖的值匹配時(shí)才刪除
if redis.call('get', key) == value then
return redis.call('del', key)
else
return 0
end
3. 加載并執(zhí)行腳本
定義腳本Bean:
@Configuration
public class LuaScriptConfig {
@Bean
public DefaultRedisScript<Long> lockScript() {
DefaultRedisScript<Long> script = new DefaultRedisScript<>();
script.setLocation(new ClassPathResource("lock.lua"));
script.setResultType(Long.class);
return script;
}
}
調(diào)用腳本:
@Service
public class RedisLockService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private DefaultRedisScript<Long> lockScript;
public boolean tryLock(String key, String value, int expireSec) {
List<String> keys = Collections.singletonList(key);
Long result = redisTemplate.execute(
lockScript,
keys,
value,
String.valueOf(expireSec)
);
return result != null && result == 1;
}
}
開發(fā)中的常見(jiàn)問(wèn)題與解決方案
1. Lua腳本緩存問(wèn)題
- 問(wèn)題:每次執(zhí)行腳本會(huì)傳輸整個(gè)腳本內(nèi)容,增加網(wǎng)絡(luò)開銷。
- 解決:Redis會(huì)自動(dòng)緩存腳本并返回SHA1值,Spring Data Redis的
DefaultRedisScript會(huì)自動(dòng)管理SHA1。確保腳本對(duì)象是單例,避免重復(fù)加載。
2. 參數(shù)傳遞錯(cuò)誤
問(wèn)題:KEYS和ARGV數(shù)量或類型不匹配,導(dǎo)致腳本執(zhí)行失敗。
解決:明確區(qū)分參數(shù)類型:
// 正確傳參示例
List<String> keys = Arrays.asList("key1", "key2"); // KEYS數(shù)組
Object[] args = new Object[]{"arg1", "arg2"}; // ARGV數(shù)組
3. Redis集群兼容性
問(wèn)題:集群模式下,所有操作的Key必須位于同一slot。
解決:使用{}定義hash tag,強(qiáng)制Key分配到同一節(jié)點(diǎn):
String key = "{user}:lock:" + userId; // 所有包含{user}的Key分配到同一節(jié)點(diǎn)
4. 腳本性能問(wèn)題
問(wèn)題:復(fù)雜Lua腳本可能阻塞Redis,影響性能。
解決:
- 避免在Lua中使用循環(huán)或復(fù)雜邏輯。
- 優(yōu)先使用Redis內(nèi)置命令(如
SETNX、EXPIRE)。
5. 異常處理
問(wèn)題:腳本執(zhí)行超時(shí)或返回非預(yù)期結(jié)果。
解決:捕獲異常并設(shè)計(jì)重試機(jī)制:
public boolean tryLockWithRetry(String key, int maxRetry) {
int retry = 0;
while (retry < maxRetry) {
if (tryLock(key, "value", 30)) {
return true;
}
retry++;
Thread.sleep(100); // 短暫等待
}
return false;
}
完整示例:分布式鎖
// 加鎖
public boolean lock(String key, String value, int expireSec) {
return redisTemplate.execute(
lockScript,
Collections.singletonList(key),
value,
String.valueOf(expireSec)
) == 1;
}
// 解鎖
public void unlock(String key, String value) {
Long result = redisTemplate.execute(
unlockScript,
Collections.singletonList(key),
value
);
if (result == null || result == 0) {
throw new RuntimeException("解鎖失?。烘i已過(guò)期或非持有者");
}
}
調(diào)試與優(yōu)化建議
Redis CLI調(diào)試:
# 直接在Redis服務(wù)器測(cè)試腳本
EVAL "return redis.call('setnx', KEYS[1], ARGV[1])" 1 mykey 123
日志配置:
# application.properties logging.level.org.springframework.data.redis=DEBUG
監(jiān)控腳本執(zhí)行時(shí)間:
# Redis慢查詢?nèi)罩? slowlog-log-slower-than 5 slowlog-max-len 128
總結(jié)
通過(guò)Lua腳本,可以輕松實(shí)現(xiàn)Redis復(fù)雜操作的原子性,解決高并發(fā)下的競(jìng)態(tài)條件問(wèn)題。在Spring Boot中,結(jié)合RedisTemplate和DefaultRedisScript,能夠高效集成Lua腳本。開發(fā)時(shí)需注意參數(shù)傳遞、集群兼容性和異常處理,避免踩坑。
到此這篇關(guān)于用Lua腳本實(shí)現(xiàn)Redis原子操作的示例的文章就介紹到這了,更多相關(guān)Lua腳本實(shí)現(xiàn)Redis原子操作內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解基于redis實(shí)現(xiàn)的四種常見(jiàn)的限流策略
限流算法在分布式領(lǐng)域是一個(gè)經(jīng)常被提起的話題,當(dāng)系統(tǒng)的處理能力有限時(shí), 如何阻止計(jì)劃外的請(qǐng)求繼續(xù)對(duì)系統(tǒng)施壓,這是一個(gè)需要重視的問(wèn)題。除了控制流量,限流還有一個(gè)應(yīng)用目的是控制用戶行為,避免垃圾請(qǐng)求2021-06-06
Redis 雙機(jī)部署完整方案(兩種架構(gòu)適配兩臺(tái)機(jī)器)
本文介紹了Redis雙機(jī)高可用標(biāo)準(zhǔn)方案:采用1主1從+雙哨兵架構(gòu),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2026-05-05
詳解RedisTemplate下Redis分布式鎖引發(fā)的系列問(wèn)題
這篇文章主要介紹了詳解RedisTemplate下Redis分布式鎖引發(fā)的系列問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
從原理到實(shí)踐分析?Redis?分布式鎖的多種實(shí)現(xiàn)方案
在分布式系統(tǒng)中,為了保證多個(gè)進(jìn)程或線程之間的數(shù)據(jù)一致性和正確性,需要使用鎖來(lái)實(shí)現(xiàn)互斥訪問(wèn)共享資源,然而,使用本地鎖在分布式系統(tǒng)中存在問(wèn)題,這篇文章主要介紹了從原理到實(shí)踐分析?Redis?分布式鎖的多種實(shí)現(xiàn)方案,需要的朋友可以參考下2024-07-07
Fastadmin中使用Redis的實(shí)現(xiàn)方法
在Fastadmin框架中使用Redis很簡(jiǎn)單,是基于thinkphp框架的操作方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2026-05-05
華為歐拉openEuler編譯安裝Redis的實(shí)現(xiàn)步驟
本文主要介紹了華為歐拉openEuler編譯安裝Redis的實(shí)現(xiàn)步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-01-01
Redis的共享session應(yīng)用實(shí)現(xiàn)短信登錄
本文主要介紹了Redis的共享session應(yīng)用實(shí)現(xiàn)短信登錄,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
基于Redis實(shí)現(xiàn)API接口訪問(wèn)次數(shù)限制
日常開發(fā)中會(huì)有一個(gè)常見(jiàn)的需求,需要限制接口在單位時(shí)間內(nèi)的訪問(wèn)次數(shù),比如說(shuō)某個(gè)免費(fèi)的接口限制單個(gè)IP一分鐘內(nèi)只能訪問(wèn)5次,該怎么實(shí)現(xiàn)呢,本文小編給大家介紹了如何基于Redis實(shí)現(xiàn)API接口訪問(wèn)次數(shù)限制,需要的朋友可以參考下2024-11-11
Redis快速實(shí)現(xiàn)分布式session的方法詳解
Session是客戶端與服務(wù)器通訊會(huì)話跟蹤技術(shù),服務(wù)器與客戶端保持整個(gè)通訊的會(huì)話基本信息。本文主要介紹了Redis快速實(shí)現(xiàn)分布式session的方法,感興趣的可以學(xué)習(xí)一下2022-01-01

