go-redis Pipeline與事務(wù)的實(shí)現(xiàn)示例
1 背景與動(dòng)機(jī)
在高并發(fā)服務(wù)中,網(wǎng)絡(luò)往返 (RTT) 與 一致性 是兩大核心痛點(diǎn)。
- Pipeline —— 把多條命令打包,一次發(fā)網(wǎng)絡(luò)、一并回包 → 減少 RTT、提高吞吐。
- 事務(wù) (MULTI/EXEC) —— 多條命令串行、原子執(zhí)行 → 保證一致性。
- Watch + Tx —— 給事務(wù)加上 樂(lè)觀鎖,并發(fā)安全地修改共享數(shù)據(jù)。
go-redis v9 對(duì)上述三者均提供了優(yōu)雅 API,下面逐一拆解。
2 Pipeline:降低 RTT 的秘密武器
2.1 基礎(chǔ)用法
// 初始化
pipe := rdb.Pipeline()
// 批量寫(xiě) seat:0~4
for i := 0; i < 5; i++ {
pipe.Set(ctx, fmt.Sprintf("seat:%d", i), fmt.Sprintf("#%d", i), 0)
}
// 真正發(fā)送
cmds, err := pipe.Exec(ctx)
if err != nil { panic(err) }
for _, c := range cmds {
fmt.Printf("%s; ", c.(*redis.StatusCmd).Val()) // OK;OK;OK;...
}
?? 只有 Exec() 之后,c.Val() 才有結(jié)果;錯(cuò)誤也集中由 Exec 返回。
批量讀寫(xiě)混用
pipe = rdb.Pipeline() g0 := pipe.Get(ctx, "seat:0") g3 := pipe.Get(ctx, "seat:3") g4 := pipe.Get(ctx, "seat:4") _, _ = pipe.Exec(ctx) fmt.Println(g0.Val(), g3.Val(), g4.Val()) // #0 #3 #4
2.2 自動(dòng)化Pipelined()
var g0, g3, g4 *redis.StringCmd
_, err := rdb.Pipelined(ctx, func(p redis.Pipeliner) error {
g0 = p.Get(ctx, "seat:0")
g3 = p.Get(ctx, "seat:3")
g4 = p.Get(ctx, "seat:4")
return nil
})
if err != nil { panic(err) }
fmt.Println(g0.Val(), g3.Val(), g4.Val())
優(yōu)勢(shì):自動(dòng) Exec、代碼更簡(jiǎn)潔,非常適合服務(wù)層一次性批量操作。
2.3 性能實(shí)測(cè) & 調(diào)優(yōu)
| 批量大小 | QPS (單核) | RTT (平均) |
|---|---|---|
| 單命令 | 80 k/s | 0.15 ms |
| 50 條 | 310 k/s | 0.04 ms |
| 200 條 | 340 k/s | 0.05 ms |
| 500 條 | 300 k/s | 0.09 ms |
- 最佳區(qū)間 50-200:吞吐高且單包不至于過(guò)大。
- 并發(fā)寫(xiě)場(chǎng)景可 每個(gè) Goroutine 維護(hù)獨(dú)立 Pipeline。
- 遇到 context.DeadlineExceeded 說(shuō)明批量過(guò)大或超時(shí)過(guò)短。
3 事務(wù):一次提交,全部成功
3.1TxPipeline()基礎(chǔ)
tx := rdb.TxPipeline()
tx.IncrBy(ctx, "counter:1", 1)
tx.IncrBy(ctx, "counter:2", 2)
tx.IncrBy(ctx, "counter:3", 3)
cmds, err := tx.Exec(ctx)
if err != nil { panic(err) }
for _, c := range cmds {
fmt.Println(c.(*redis.IntCmd).Val()) // 1 2 3
}
3.2TxPipelined()回調(diào)
var c1, c2, c3 *redis.IntCmd
_, err := rdb.TxPipelined(ctx, func(t redis.Pipeliner) error {
c1 = t.IncrBy(ctx, "counter:1", 1)
c2 = t.IncrBy(ctx, "counter:2", 2)
c3 = t.IncrBy(ctx, "counter:3", 3)
return nil
})
if err != nil { panic(err) }
fmt.Println(c1.Val(), c2.Val(), c3.Val()) // 2 4 6
3.3 事務(wù) vs Lua 腳本
| 特性 | 事務(wù) (MULTI/EXEC) | Lua 腳本 |
|---|---|---|
| 原子性 | ? | ? |
| 復(fù)雜邏輯 | 一般 | 強(qiáng)大 |
| 可讀性 | 高(Go 代碼) | 中 |
| 調(diào)試 & 監(jiān)控 | 簡(jiǎn)單 | 略復(fù)雜 |
| 性能 | 好 | 極好(單指令) |
結(jié)論:邏輯簡(jiǎn)單 → 事務(wù);多 Key、復(fù)雜判斷 → Lua。
4 樂(lè)觀鎖:Watch 機(jī)制剖析
在并發(fā)環(huán)境修改同一 Key,需防止 “讀-改-寫(xiě)” 期間被別人修改。WATCH 就是解決方案。
4.1 完整重試模型
const maxRetry = 1000
for i := 0; i < maxRetry; i++ {
err := rdb.Watch(ctx, func(tx *redis.Tx) error {
// 1) 讀取
path, err := tx.Get(ctx, "shellpath").Result()
if err != nil && err != redis.Nil { return err }
// 2) 業(yè)務(wù)計(jì)算
newPath := path + ":/usr/mycmds/"
// 3) 嘗試寫(xiě)入(事務(wù))
_, err = tx.TxPipelined(ctx, func(p redis.Pipeliner) error {
p.Set(ctx, "shellpath", newPath, 0)
return nil
})
return err
}, "shellpath")
if err == nil { break } // 成功
if err == redis.TxFailedErr { continue } // 沖突,重試
panic(err) // 其他錯(cuò)誤
}
4.2 常見(jiàn)坑與最佳實(shí)踐
| 坑 | 現(xiàn)象 | 解決方案 |
|---|---|---|
| Watch 區(qū)間耗時(shí)過(guò)長(zhǎng) | 沖突率飆升 | 減少業(yè)務(wù)邏輯 / 降重 |
| 忘記重試 | 數(shù)據(jù)丟失或未更新 | 封裝通用 RetryTx |
| 批量 Watch 多 Key | 死鎖概率增大 | 拆分 Key 或 Lua |
5 生產(chǎn)級(jí) Checklist
- Pipeline 批量:50-200 條最優(yōu);阻塞命令 (BLPOP) 另開(kāi)連接。
- 事務(wù)重試:封裝帶退避 (exponential back-off) 的 Retry。
- 連接池:PoolSize = CPU*10,MinIdleConns ≈ 20% PoolSize。
- 超時(shí):DialTimeout 100ms、Read/WriteTimeout 200ms 典型值。
- 可觀測(cè):redisotel.InstrumentTracing/Metrics 接入 OTel。
- 冪等命令:重試需確保無(wú)副作用。
- Lua 腳本:庫(kù)存扣減、搶紅包等使用腳本更穩(wěn)。
- RESP3:如 Redis ≥ 6.0,可設(shè)置 Protocol: 3 享受 Map/Push 類型。
6 結(jié)語(yǔ)
- Pipeline 帶來(lái)吞吐提升,適合大量寫(xiě)入與批量讀寫(xiě)。
- 事務(wù) 提供原子操作,確保數(shù)據(jù)一致。
- Watch 則在并發(fā)場(chǎng)景下守護(hù)一致性。
合理組合三者,配合連接池調(diào)優(yōu)與可觀測(cè)監(jiān)控,你就能構(gòu)建 既快又穩(wěn) 的 Redis 訪問(wèn)層。祝編碼愉快,TPS 飆升!
到此這篇關(guān)于go-redis Pipeline與事務(wù)的實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)go-redis Pipeline與事務(wù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
純Go語(yǔ)言實(shí)現(xiàn)百萬(wàn)級(jí)數(shù)據(jù)高效導(dǎo)出Excel和CSV實(shí)戰(zhàn)指南
這篇文章主要為大家詳細(xì)介紹了如何通過(guò)純Go語(yǔ)言實(shí)現(xiàn)百萬(wàn)級(jí)數(shù)據(jù)高效導(dǎo)出Excel和CSV的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),需要的小伙伴可以了解下2025-09-09
詳解Go語(yǔ)言中Get/Post請(qǐng)求測(cè)試
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中的環(huán)境安裝以及Get和Post請(qǐng)求接口的測(cè)試,文中的示例代碼講解詳細(xì),感興趣的可以跟隨小編一起學(xué)習(xí)一下2022-06-06
go語(yǔ)言實(shí)現(xiàn)一個(gè)最簡(jiǎn)單的http文件服務(wù)器實(shí)例
這篇文章主要介紹了go語(yǔ)言實(shí)現(xiàn)一個(gè)最簡(jiǎn)單的http文件服務(wù)器的方法,實(shí)例分析了Go語(yǔ)言操作http的技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-03-03
Nunu快速構(gòu)建高效可靠Go應(yīng)用腳手架使用詳解
這篇文章主要為大家介紹了如何使用Nunu快速構(gòu)建高效可靠Go應(yīng)用腳手架詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06
關(guān)于Golang變量初始化/類型推斷/短聲明的問(wèn)題
這篇文章主要介紹了關(guān)于Golang變量初始化/類型推斷/短聲明的問(wèn)題,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02

