最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Redis?Lua腳本從入門到避坑指南

 更新時(shí)間:2026年06月11日 08:54:04   作者:西伯利亞毒蟲  
本文主要介紹了Redis?Lua腳本從入門到避坑指南,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

一、為什么是 Lua?

Redis 從 2.6.0 版本開始內(nèi)置了 Lua 解釋器。這意味著:

  • 零依賴:不需要像 Java 加 Maven 依賴、Python pip 安裝庫那樣做任何前置準(zhǔn)備。
  • 開箱即用:只要 Redis 版本 ≥ 2.6,直接用 EVAL 命令就能跑。

Lua 在 Redis 里的角色是膠水語言,用它把多個(gè) Redis 命令和業(yè)務(wù)判斷邏輯打包成一個(gè)整體,交給 Redis 服務(wù)端一次性執(zhí)行。

二、核心語法速查(Java 開發(fā)者視角)

1. 大小寫敏感

Lua 嚴(yán)格區(qū)分大小寫。return 不能寫成 ReturnKEYS 不能寫成 keys。這和 Java 一致。

2. 變量與local

local count = 10          -- 局部變量,強(qiáng)烈推薦
global_var = "hello"      -- 全局變量,危險(xiǎn),會(huì)污染環(huán)境

鐵律:所有變量都用 local 聲明。 這既是為了作用域隔離,也是為了性能。

3. 常用類型

Lua 是動(dòng)態(tài)類型語言,變量沒類型,值有類型。

類型寫法備注
數(shù)字local n = 10
字符串local s = "hello"拼接用 ..
布爾local ok = true只有 false 和 nil 為假,0 和空字符串都為真!
local x = nil
數(shù)組local arr = {"a", "b"}索引從 1 開始
字典local map = {name="Tom"}訪問:map.name 或 map["name"]

4. 邏輯運(yùn)算符

和 Java 的對(duì)照:

JavaLua
!anot a
a && ba and b
a || ba or b

Lua 的 and/or 是短路求值,返回值本身而不是強(qiáng)制轉(zhuǎn) boolean。常用于設(shè)默認(rèn)值:

local count = redis.call('GET', KEYS[1]) or 0

5. 常用內(nèi)置函數(shù)

函數(shù)作用示例
tonumber(str)字符串轉(zhuǎn)數(shù)字tonumber("123") → 123
tostring(val)轉(zhuǎn)字符串tostring(100) → "100"
type(val)判斷類型type(nil) → "nil"
string.sub(s, i, j)截取子串string.sub("hello", 1, 3) → "hel"
string.len(s)字符串長度string.len("hi") → 2
math.abs(n)絕對(duì)值math.abs(-5) → 5
math.floor(n)向下取整math.floor(3.9) → 3
math.ceil(n)向上取整math.ceil(3.1) → 4
math.max(a,b)最大值math.max(1,5) → 5

重要:ARGV 里的參數(shù)全部是字符串,需要用 tonumber() 轉(zhuǎn)成數(shù)字才能做算術(shù)運(yùn)算。

三、EVAL 命令詳解

命令格式

EVAL <腳本內(nèi)容> <key的數(shù)量> <key列表...> <arg列表...>

中間那個(gè)數(shù)字是干什么的?

它是 KEYS 和 ARGV 的分割標(biāo)識(shí),告訴 Redis “前面 N 個(gè)參數(shù)是 KEY,剩下的全是 ARGV”。

示例:

EVAL "return {KEYS[1], KEYS[2], ARGV[1], ARGV[2]}" 2 key1 key2 arg1 arg2
  • 2 表示前 2 個(gè)是 KEY:key1, key2
  • 剩下的全是 ARGV:arg1, arg2

為什么必須寫這個(gè)數(shù)字?

  1. 集群路由:Redis Cluster 需要知道哪些是 key,才能校驗(yàn)它們是否在同一個(gè) slot,并正確路由。
  2. 代碼可讀性:顯式聲明讓腳本意圖清晰,不用猜。
  3. 安全校驗(yàn):Redis 可在執(zhí)行前做靜態(tài)分析。

即使腳本完全不用 KEYS,也必須寫 0:

EVAL "return 'hello'" 0

腳本里只有 KEYS 和 ARGV 兩個(gè)預(yù)置數(shù)組嗎?

是的。 Redis 的 Lua 沙箱里,除了 KEYS 和 ARGV,沒有任何其他外部數(shù)據(jù)來源。你需要的一切數(shù)據(jù),都得在腳本里通過 redis.call 獲取。

四、Redis 與 Lua 的聯(lián)動(dòng)機(jī)制

執(zhí)行流程(四個(gè)步驟)

  1. 解析參數(shù):Redis 根據(jù) numkeys 把參數(shù)切分成 KEYS 和 ARGV。
  2. 注入沙箱:啟動(dòng) Lua 解釋器,創(chuàng)建隔離環(huán)境,注入 KEYS 和 ARGV。
  3. Lua 執(zhí)行,回調(diào) Redis:腳本通過 redis.call 或 redis.pcall 向宿主 Redis 發(fā)請(qǐng)求。腳本暫停→Redis 執(zhí)行命令→返回結(jié)果給腳本→腳本繼續(xù)。
  4. 返回結(jié)果:Lua 腳本執(zhí)行完,結(jié)果轉(zhuǎn)成 Redis 協(xié)議返回給客戶端。

redis.callvsredis.pcall

  • redis.call('GET', KEYS[1]):執(zhí)行 Redis 命令,出錯(cuò)會(huì)中斷腳本并拋錯(cuò)。
  • redis.pcall('GET', KEYS[1]):執(zhí)行 Redis 命令,出錯(cuò)不中斷,以 table 形式返回錯(cuò)誤信息。

用 pcall 可以讓你在腳本內(nèi)部做錯(cuò)誤處理:

local res = redis.pcall('GET', KEYS[1])
if type(res) == 'table' and res['err'] then
    return "出錯(cuò)了"
end

整個(gè)腳本執(zhí)行期間,其他命令必須排隊(duì)

這是真的,而且是所有命令都排隊(duì)。 原因是 Redis 的單線程模型:主線程在跑 Lua 時(shí),不可能同時(shí)處理其他請(qǐng)求。

后果:腳本必須快(毫秒級(jí)),否則整個(gè) Redis 會(huì)被阻塞,導(dǎo)致服務(wù)雪崩。

邊界:這只阻塞這一個(gè) Redis 實(shí)例的主線程,不影響你的 Java 應(yīng)用、MySQL、Nginx 等其他進(jìn)程。

五、原子性:最大的優(yōu)勢(shì)與最大的陷阱

能保證什么

絕對(duì)的執(zhí)行原子性:腳本一旦開始,絕不會(huì)被其他命令插隊(duì)。Redis 把它當(dāng)成一個(gè)不可分割的“超級(jí)命令”。

不能保證什么

沒有事務(wù)回滾。腳本執(zhí)行中途如果服務(wù)器崩潰,已執(zhí)行的寫操作不會(huì)自動(dòng)回滾。

示例:

redis.call('SET', 'key1', 'a')  -- 執(zhí)行了
-- 此時(shí)斷電
redis.call('SET', 'key2', 'b')  -- 沒執(zhí)行

結(jié)果:key1 是新值,key2 還是老樣子,數(shù)據(jù)處于不一致狀態(tài)。

解決方案

把所有校驗(yàn)邏輯放在腳本最前面,寫入操作集中在最后幾行,縮小風(fēng)險(xiǎn)窗口:

-- 先做全部檢查(不改變數(shù)據(jù))
local stock = redis.call('GET', KEYS[1])
if not stock or tonumber(stock) <= 0 then
    return -1
end
-- 檢查通過后再集中寫入
redis.call('DECR', KEYS[1])
redis.call('SADD', KEYS[2], ARGV[1])
return 1

六、經(jīng)典實(shí)戰(zhàn):秒殺場景的并發(fā)控制

不用 Lua(錯(cuò)誤方案)

stock = redis.get("stock:001")
if int(stock) > 0:
    redis.decr("stock:001")

問題:A 和 B 同時(shí) get 到 1,各自判斷 >0 后各自執(zhí)行 decr,結(jié)果庫存變 -1,超賣。

用 Lua(正確方案)

-- KEYS[1]: 庫存key, KEYS[2]: 已搶用戶set, ARGV[1]: 用戶ID
local stock = redis.call('DECR', KEYS[1])
if stock < 0 then
    redis.call('INCR', KEYS[1])  -- 回滾
    return -1  -- 賣完
end
redis.call('SADD', KEYS[2], ARGV[1])  -- 記錄用戶
return stock

效果:A 的腳本執(zhí)行期間,B 的腳本完全進(jìn)不來。A 執(zhí)行完,B 再執(zhí)行時(shí)看到的庫存已經(jīng)是 0(或 -1 被回滾),無法多搶。

七、Java 實(shí)戰(zhàn):怎么在代碼里用 Lua

1. 引入依賴(Spring Boot 項(xiàng)目)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

這自帶 Lettuce 客戶端,原生支持 Lua。

2. 定義腳本(推薦用DefaultRedisScript)

import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
// 放在靜態(tài)變量里,避免重復(fù)創(chuàng)建
private static final String SECKILL_SCRIPT = 
    "local stock = redis.call('DECR', KEYS[1]) " +
    "if stock < 0 then " +
    "    redis.call('INCR', KEYS[1]) " +
    "    return -1 " +
    "end " +
    "redis.call('SADD', KEYS[2], ARGV[1]) " +
    "return stock";
private static final RedisScript<Long> SECKILL = 
    new DefaultRedisScript<>(SECKILL_SCRIPT, Long.class);

3. 執(zhí)行腳本

@Autowired
private StringRedisTemplate stringRedisTemplate;
public Long doSeckill(String stockKey, String usersKey, String userId) {
    List<String> keys = Arrays.asList(stockKey, usersKey);
    Object[] args = { userId };
    return stringRedisTemplate.execute(
        SECKILL,   // 腳本對(duì)象
        keys,      // KEYS 列表
        args       // ARGV 列表
    );
}

4. 用EVALSHA優(yōu)化性能

每次傳完整腳本有網(wǎng)絡(luò)開銷??梢蕴崆凹虞d腳本到 Redis,后續(xù)用 SHA1 哈希調(diào)用:

// 加載腳本
String sha1 = stringRedisTemplate.getConnectionFactory()
    .getConnection().scriptLoad(SECKILL_SCRIPT.getBytes());
// 用 SHA1 執(zhí)行
DefaultRedisScript<Long> cachedScript = new DefaultRedisScript<>();
cachedScript.setSha1(sha1);
cachedScript.setResultType(Long.class);
stringRedisTemplate.execute(cachedScript, keys, args);

Spring 的 DefaultRedisScript 默認(rèn)行為:第一次執(zhí)行時(shí)用 EVAL,失敗則嘗試 EVALSHA,透明處理緩存,一般無需手動(dòng)干預(yù)。

八、工程化避坑清單(重要?。?/h2>

1. 腳本必須短小精悍

  • 禁止:大循環(huán)、復(fù)雜正則、遍歷大集合(如 SMEMBERS 百萬級(jí) Set)。
  • 原則:只做快速判斷 + 少量原子操作,耗時(shí)控制在毫秒級(jí)。

2. 永遠(yuǎn)設(shè)置超時(shí)

// 應(yīng)用層設(shè)置網(wǎng)絡(luò)超時(shí)
@Bean
public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory factory) {
    RedisTemplate<?, ?> template = new RedisTemplate<>();
    template.setConnectionFactory(factory);
    // 關(guān)鍵:設(shè)置命令超時(shí),避免被慢腳本卡死
    template.setDefaultTimeout(Duration.ofSeconds(2));
    return template;
}

3. 集群模式下,所有 KEY 必須在同一個(gè) slot

Redis Cluster 要求 Lua 腳本操作的所有 key 必須在同一節(jié)點(diǎn)。常用的技巧是用 hash tag

EVAL "..." 2 {order}:stock {order}:users

{order} 告訴 Redis,只對(duì)花括號(hào)里的部分做哈希計(jì)算,保證這兩個(gè) key 落在同一個(gè) slot。

4. 腳本里不要有死循環(huán)

while true do
    -- Redis 主線程會(huì)卡死,只能靠 `SCRIPT KILL` 強(qiáng)制中斷
    -- 如果腳本已經(jīng)執(zhí)行過寫命令,SHUTDOWN NOSAVE 是最后手段
end

Redis 7.0+ 有 lua-time-limit 保護(hù)(默認(rèn) 5 秒),但仍然不建議測(cè)試這個(gè)底線。

5. 合理拆分腳本

不要把一個(gè)大而全的腳本用于所有場景。按職責(zé)拆分:扣庫存一個(gè)腳本、發(fā)優(yōu)惠券一個(gè)腳本、記錄日志一個(gè)腳本。保持原子性邊界最小化。

6. 充分測(cè)試邊界條件

  • 庫存為 0 時(shí)
  • 參數(shù)為空時(shí)
  • 參數(shù)非法時(shí)(傳了非數(shù)字)
  • 并發(fā)壓測(cè)驗(yàn)證原子性

九、總結(jié):Lua 在 Redis 中的獨(dú)特定位

維度常規(guī)編程語言(Java/Python)Redis Lua 腳本
執(zhí)行位置客戶端服務(wù)端(靠近數(shù)據(jù))
網(wǎng)絡(luò)開銷N 次往返1 次
原子性需借助事務(wù)/分布式鎖天然原子性
邏輯能力無限受限(但夠用)
復(fù)雜度靈活,但容易出錯(cuò)簡單,腳本短小即安全

一句話總結(jié):Redis Lua 腳本解決的是“需要在一次往返中,原子性地執(zhí)行多條 Redis 命令且夾雜業(yè)務(wù)判斷”這個(gè)特定問題。它不是用來取代 Java 的,而是讓你的 Java 應(yīng)用在處理某些 Redis 操作時(shí),更安全、更高效。

到此這篇關(guān)于Redis Lua腳本從入門到避坑指南的文章就介紹到這了,更多相關(guān)Redis Lua腳本入門內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 用Redis實(shí)現(xiàn)微博關(guān)注關(guān)系

    用Redis實(shí)現(xiàn)微博關(guān)注關(guān)系

    在微博中,每一個(gè)用戶都會(huì)有一個(gè)關(guān)注列表,一個(gè)粉絲列表。用戶可以查看自己的關(guān)注,粉絲列表,也可以查看別人的關(guān)注,粉絲列表。并且,要展示列表里每個(gè)人與當(dāng)前查看者的關(guān)注狀態(tài)。
    2015-09-09
  • 銀河麒麟V10sp1服務(wù)器系統(tǒng)安裝redis不能使用的快速解決辦法

    銀河麒麟V10sp1服務(wù)器系統(tǒng)安裝redis不能使用的快速解決辦法

    這篇文章主要介紹了銀河麒麟V10sp1服務(wù)器系統(tǒng)安裝redis不能使用的快速解決辦法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-01-01
  • Redis主從復(fù)制詳解

    Redis主從復(fù)制詳解

    今天小編就為大家分享一篇關(guān)于Redis主從復(fù)制詳解,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧
    2019-01-01
  • redis+lua實(shí)現(xiàn)限流的項(xiàng)目實(shí)踐

    redis+lua實(shí)現(xiàn)限流的項(xiàng)目實(shí)踐

    redis有很多限流的算法(比如:令牌桶,計(jì)數(shù)器,時(shí)間窗口)等,在分布式里面進(jìn)行限流的話,我們則可以使用redis+lua腳本進(jìn)行限流,下面就來介紹一下redis+lua實(shí)現(xiàn)限流
    2023-10-10
  • Windows系統(tǒng)一鍵啟動(dòng)Redis腳本

    Windows系統(tǒng)一鍵啟動(dòng)Redis腳本

    本文介紹了在Windows系統(tǒng)中創(chuàng)建一鍵啟動(dòng)Redis的腳本,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-12-12
  • 詳解Redis終端操作和Redis-plus-plus接口使用教程

    詳解Redis終端操作和Redis-plus-plus接口使用教程

    這篇文章主要介紹了詳解Redis終端操作和Redis-plus-plus接口使用教程,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2026-03-03
  • Redis中Redisson布隆過濾器的學(xué)習(xí)

    Redis中Redisson布隆過濾器的學(xué)習(xí)

    布隆過濾器是一個(gè)非常長的二進(jìn)制向量和一系列隨機(jī)哈希函數(shù)的組合,可用于檢索一個(gè)元素是否存在,本文就詳細(xì)的介紹一下Redisson布隆過濾器,具有一定的參考價(jià)值,感興趣的可以了解一下
    2022-05-05
  • odoo中使用redis實(shí)現(xiàn)緩存的步驟

    odoo中使用redis實(shí)現(xiàn)緩存的步驟

    這篇文章主要介紹了odoo中使用redis實(shí)現(xiàn)緩存的步驟,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-04-04
  • 詳解如何在Windows上配置和使用Redis持久化功能

    詳解如何在Windows上配置和使用Redis持久化功能

    Redis 是一個(gè)強(qiáng)大的內(nèi)存數(shù)據(jù)庫,常用于緩存和實(shí)時(shí)數(shù)據(jù)處理,然而,由于其內(nèi)存特性,一旦服務(wù)器重啟或故障,存儲(chǔ)在 Redis 中的數(shù)據(jù)可能會(huì)丟失,為了確保數(shù)據(jù)的安全性和持久性,Redis 提供了多種持久化機(jī)制,本文將詳細(xì)介紹如何在 Windows 上配置和使用 Redis 的持久化功能
    2024-08-08
  • Redis中HyperLogLog的使用小結(jié)

    Redis中HyperLogLog的使用小結(jié)

    Redis 的 HyperLogLog 是一種概率性數(shù)據(jù)結(jié)構(gòu),用于統(tǒng)計(jì)唯一元素的數(shù)量(基數(shù)),本文主要介紹了Redis中HyperLogLog的使用小結(jié),感興趣的可以了解一下
    2025-05-05

最新評(píng)論

岱山县| 元氏县| 万宁市| 宜良县| 易门县| 海南省| 大邑县| 化州市| 怀仁县| 陇南市| 宣威市| 苍南县| 大港区| 民乐县| 隆昌县| 高邮市| 昌宁县| 怀宁县| 吉木萨尔县| 石河子市| 左权县| 石阡县| 安吉县| 扎鲁特旗| 江阴市| 三亚市| 集贤县| 汶川县| 阿尔山市| 永新县| 巩留县| 全州县| 西畴县| 济南市| 广元市| 清水县| 浪卡子县| 和硕县| 武山县| 汉寿县| 犍为县|