SpringBoot集成Redis并調(diào)用Lua腳本的示例詳解
前言
Redis 是一個(gè)高性能的內(nèi)存數(shù)據(jù)庫(kù),廣泛用于緩存、計(jì)數(shù)器、分布式鎖等場(chǎng)景。在某些業(yè)務(wù)中,我們需要對(duì)多個(gè) Redis 操作進(jìn)行原子性處理,以保證數(shù)據(jù)一致性,這時(shí)就可以使用 Redis 提供的 Lua 腳本功能。
Spring Boot 通過(guò) RedisTemplate 對(duì) Redis 進(jìn)行了良好的封裝,也支持我們方便地調(diào)用 Lua 腳本。本文將帶你一步步實(shí)現(xiàn) Spring Boot 中如何:
- 加載 Lua 腳本
- 調(diào)用 Lua 腳本
- 實(shí)現(xiàn)一個(gè)實(shí)際業(yè)務(wù)邏輯:獲取 key,若不存在則設(shè)置默認(rèn)值并返回
一、項(xiàng)目準(zhǔn)備
創(chuàng)建 Spring Boot 項(xiàng)目
你可以使用 Spring Initializr 創(chuàng)建一個(gè)新項(xiàng)目,選擇以下依賴(lài):
- Spring Web
- Spring Data Redis
- Lettuce(Redis 客戶(hù)端)
或者手動(dòng)創(chuàng)建 Maven 項(xiàng)目后添加如下依賴(lài):
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Web 支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Redis 支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Lettuce 客戶(hù)端 -->
<dependency>
<groupId>io.lettuce.core</groupId>
<artifactId>lettuce-core</artifactId>
<version>6.2.4</version>
</dependency>
二、編寫(xiě) Lua 腳本文件
我們將創(chuàng)建一個(gè)簡(jiǎn)單的 Lua 腳本:如果 key 不存在,則設(shè)置默認(rèn)值;存在則直接返回當(dāng)前值。
腳本內(nèi)容:get_or_set_default.lua
路徑建議為:src/main/resources/lua/get_or_set_default.lua
-- get_or_set_default.lua
local key = KEYS[1]
local defaultVal = ARGV[1]
local value = redis.call('GET', key)
if not value then
redis.call('SET', key, defaultVal)
value = defaultVal
end
return value
三、加載 Lua 腳本到 Redis
我們可以利用 Spring 的生命周期回調(diào)(如 @PostConstruct)來(lái)加載 Lua 腳本,并保存其 SHA1 校驗(yàn)碼,后續(xù)通過(guò) EVALSHA 來(lái)調(diào)用。
創(chuàng)建腳本加載類(lèi):LuaScriptLoader.java
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
@Service
public class LuaScriptLoader {
private final RedisTemplate<String, Object> redisTemplate;
// 存儲(chǔ)腳本的 SHA1 值
public static String GET_OR_SET_DEFAULT_SHA;
public LuaScriptLoader(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@PostConstruct
public void loadScripts() throws IOException {
Path scriptPath = Paths.get("src/main/resources/lua/get_or_set_default.lua");
String scriptContent = Files.readString(scriptPath);
DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
redisScript.setScriptText(scriptContent);
redisScript.setResultType(String.class);
// 執(zhí)行 SCRIPT LOAD 命令加載腳本
GET_OR_SET_DEFAULT_SHA = redisTemplate.execute(
redisScript,
Collections.singletonList("dummyKey"),
"defaultValue"
);
System.out.println("Lua Script SHA1: " + GET_OR_SET_DEFAULT_SHA);
}
}
注意:
- 我們使用了一個(gè) dummyKey 和 dummyValue 來(lái)執(zhí)行一次腳本,只是為了觸發(fā)腳本加載。
- 實(shí)際調(diào)用時(shí)使用的是
EVALSHA命令。
四、封裝 Lua 腳本調(diào)用服務(wù)
我們創(chuàng)建一個(gè)服務(wù)類(lèi)來(lái)封裝 Lua 腳本的調(diào)用邏輯。
創(chuàng)建服務(wù)類(lèi):RedisLuaService.java
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.Collections;
@Service
public class RedisLuaService {
private final RedisTemplate<String, Object> redisTemplate;
public RedisLuaService(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* 調(diào)用 Lua 腳本:根據(jù) key 獲取值,若不存在則設(shè)置默認(rèn)值并返回
*/
public String getOrSetDefault(String key, String defaultValue) {
return (String) redisTemplate.execute(
LuaScriptLoader.GET_OR_SET_DEFAULT_SHA,
Collections.singletonList(key),
defaultValue
);
}
}
五、創(chuàng)建 REST 接口進(jìn)行測(cè)試
為了方便測(cè)試,我們創(chuàng)建一個(gè)簡(jiǎn)單的接口。
創(chuàng)建控制器類(lèi):RedisLuaController.java
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/redis")
public class RedisLuaController {
private final RedisLuaService redisLuaService;
public RedisLuaController(RedisLuaService redisLuaService) {
this.redisLuaService = redisLuaService;
}
@GetMapping("/get-or-set")
public String getOrDefault(
@RequestParam String key,
@RequestParam(required = false, defaultValue = "default_value") String defaultValue) {
return redisLuaService.getOrSetDefault(key, defaultValue);
}
}
六、運(yùn)行測(cè)試
1. 啟動(dòng) Redis 服務(wù)
確保你本地或服務(wù)器上已經(jīng)啟動(dòng)了 Redis:
redis-server
2. 啟動(dòng) Spring Boot 應(yīng)用
使用 IDE 或命令行啟動(dòng) Spring Boot 項(xiàng)目:
mvn spring-boot:run
3. 測(cè)試訪問(wèn)
訪問(wèn)如下 URL:
http://localhost:8080/redis/get-or-set?key=test_lua&defaultValue=hello_redis
第一次請(qǐng)求:
Redis 中沒(méi)有 test_lua 鍵,會(huì)自動(dòng)設(shè)置為 hello_redis,并返回該值。
第二次請(qǐng)求:
即使不傳 defaultValue,也會(huì)返回 hello_redis,因?yàn)殒I已存在。
七、注意事項(xiàng)
| 項(xiàng)目 | 說(shuō)明 |
|---|---|
| 腳本緩存 | Redis 會(huì)緩存加載過(guò)的 Lua 腳本,但重啟后失效,需重新加載 |
| 調(diào)試?yán)щy | Lua 腳本不能輸出日志,建議先在 Redis CLI 中測(cè)試后再集成 |
| 性能考量 | Lua 腳本是單線程執(zhí)行的,避免復(fù)雜運(yùn)算 |
| 異常處理 | 在 Java 層應(yīng)對(duì)腳本執(zhí)行失敗做容錯(cuò)處理 |
| 參數(shù)傳遞 | KEYS 是數(shù)組,ARGV 是參數(shù)數(shù)組,順序要對(duì)應(yīng) |
八、進(jìn)階建議
- 將所有 Lua 腳本統(tǒng)一管理,使用配置中心或數(shù)據(jù)庫(kù)存儲(chǔ)
- 結(jié)合 AOP 或攔截器,實(shí)現(xiàn) Lua 腳本調(diào)用的自動(dòng)重試機(jī)制
- 使用 Redisson 等高級(jí)庫(kù)簡(jiǎn)化 Lua 腳本操作
- 使用 Spring Cache 抽象層結(jié)合 Lua 腳本實(shí)現(xiàn)更復(fù)雜的緩存策略
示例結(jié)構(gòu)圖
src/
├── main/
│ ├── java/
│ │ └── com.example.demo/
│ │ ├── DemoApplication.java
│ │ ├── service/
│ │ │ ├── RedisLuaService.java
│ │ │ └── LuaScriptLoader.java
│ │ └── controller/
│ │ └── RedisLuaController.java
│ └── resources/
│ └── lua/
│ └── get_or_set_default.lua
└── pom.xml
總結(jié)
本文詳細(xì)介紹了如何在 Spring Boot 中調(diào)用 Redis 的 Lua 腳本,包括:
- 添加必要的依賴(lài)
- 編寫(xiě) Lua 腳本
- 使用
RedisTemplate加載和調(diào)用腳本 - 構(gòu)建完整的調(diào)用流程與接口測(cè)試
Lua 腳本非常適合用來(lái)實(shí)現(xiàn)一些需要原子性的 Redis 操作,是構(gòu)建高并發(fā)系統(tǒng)的重要工具之一。
到此這篇關(guān)于SpringBoot集成Redis并調(diào)用Lua腳本的示例詳解的文章就介紹到這了,更多相關(guān)SpringBoot集成Redis調(diào)用Lua內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java 實(shí)戰(zhàn)項(xiàng)目之疫情防控管理系統(tǒng)詳解
讀萬(wàn)卷書(shū)不如行萬(wàn)里路,只學(xué)書(shū)上的理論是遠(yuǎn)遠(yuǎn)不夠的,只有在實(shí)戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用Java實(shí)現(xiàn)一個(gè)疫情防控管理系統(tǒng),大家可以在過(guò)程中查缺補(bǔ)漏,提升水平2021-11-11
DUCC配置平臺(tái)實(shí)現(xiàn)一個(gè)動(dòng)態(tài)化線程池示例代碼
這篇文章主要為大家介紹了DUCC配置平臺(tái)實(shí)現(xiàn)一個(gè)動(dòng)態(tài)化線程池示例代碼,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02
SpringBoot war包部署到Tomcat服務(wù)器
這篇文章主要介紹了SpringBoot war包部署到Tomcat服務(wù)器,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-03
Nacos入門(mén)過(guò)程的坑--獲取不到配置的值問(wèn)題
這篇文章主要介紹了Nacos入門(mén)過(guò)程的坑--獲取不到配置的值問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
Java中的Optional類(lèi)用法詳細(xì)講解
這篇文章詳細(xì)介紹了Java中Optional類(lèi)的使用,包括創(chuàng)建Optional對(duì)象,Optional對(duì)象的常用方法,如get、orElse、orElseGet、orElseThrow等,以及Optional類(lèi)結(jié)合Lambda表達(dá)式和StreamAPI的使用,需要的朋友可以參考下2024-10-10

