go-redis Pipeline与事务的实现示例

 更新时间:2025年11月13日 09:29:20   作者:Hello.Reader  
本文介绍了Go-Redis v9中Pipeline、事务和Watch机制的使用方法与优化实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

1 背景与动机

在高并发服务中,网络往返 (RTT)一致性 是两大核心痛点。

  • Pipeline —— 把多条命令打包,一次发网络、一并回包 → 减少 RTT、提高吞吐
  • 事务 (MULTI/EXEC) —— 多条命令串行、原子执行 → 保证一致性
  • Watch + Tx —— 给事务加上 乐观锁,并发安全地修改共享数据。

go-redis v9 对上述三者均提供了优雅 API,下面逐一拆解。

2 Pipeline:降低 RTT 的秘密武器

2.1 基础用法

// 初始化
pipe := rdb.Pipeline()

// 批量写 seat:0~4
for i := 0; i < 5; i++ {
	pipe.Set(ctx, fmt.Sprintf("seat:%d", i), fmt.Sprintf("#%d", i), 0)
}

// 真正发送
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() 才有结果;错误也集中由 Exec 返回。

批量读写混用

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 自动化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())

优势:自动 Exec、代码更简洁,非常适合服务层一次性批量操作。

2.3 性能实测 & 调优

批量大小QPS (单核)RTT (平均)
单命令80 k/s0.15 ms
50 条310 k/s0.04 ms
200 条340 k/s0.05 ms
500 条300 k/s0.09 ms
  • 最佳区间 50-200:吞吐高且单包不至于过大。
  • 并发写场景可 每个 Goroutine 维护独立 Pipeline。
  • 遇到 context.DeadlineExceeded 说明批量过大或超时过短。

3 事务:一次提交,全部成功

3.1TxPipeline()基础

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()回调

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 事务 vs Lua 脚本

特性事务 (MULTI/EXEC)Lua 脚本
原子性
复杂逻辑一般强大
可读性高(Go 代码)
调试 & 监控简单略复杂
性能极好(单指令)

结论:逻辑简单 → 事务;多 Key、复杂判断 → Lua。

4 乐观锁:Watch 机制剖析

在并发环境修改同一 Key,需防止 “读-改-写” 期间被别人修改。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) 业务计算
		newPath := path + ":/usr/mycmds/"

		// 3) 尝试写入(事务)
		_, 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)                             // 其他错误
}

4.2 常见坑与最佳实践

现象解决方案
Watch 区间耗时过长冲突率飙升减少业务逻辑 / 降重
忘记重试数据丢失或未更新封装通用 RetryTx
批量 Watch 多 Key死锁概率增大拆分 Key 或 Lua

5 生产级 Checklist

  1. Pipeline 批量:50-200 条最优;阻塞命令 (BLPOP) 另开连接。
  2. 事务重试:封装带退避 (exponential back-off) 的 Retry。
  3. 连接池:PoolSize = CPU*10,MinIdleConns ≈ 20% PoolSize。
  4. 超时:DialTimeout 100ms、Read/WriteTimeout 200ms 典型值。
  5. 可观测:redisotel.InstrumentTracing/Metrics 接入 OTel。
  6. 幂等命令:重试需确保无副作用。
  7. Lua 脚本:库存扣减、抢红包等使用脚本更稳。
  8. RESP3:如 Redis ≥ 6.0,可设置 Protocol: 3 享受 Map/Push 类型。

6 结语

  • Pipeline 带来吞吐提升,适合大量写入与批量读写。
  • 事务 提供原子操作,确保数据一致。
  • Watch 则在并发场景下守护一致性。

合理组合三者,配合连接池调优与可观测监控,你就能构建 既快又稳 的 Redis 访问层。祝编码愉快,TPS 飙升!

到此这篇关于go-redis Pipeline与事务的实现示例的文章就介绍到这了,更多相关go-redis Pipeline与事务内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 基于Golang实现统一加载资源的入口

    基于Golang实现统一加载资源的入口

    当我们需要在 main 函数中做一些初始化的工作,比如初始化日志,初始化配置文件,都需要统一初始化入口函数,所以本文就来编写一个统一加载资源的入口吧
    2023-05-05
  • 使用Go语言编写一个简单的Web框架

    使用Go语言编写一个简单的Web框架

    Go语言(又称Golang)因其高效的性能和简洁的语法,在编写Web框架方面表现出色,下面将详细介绍如何使用Go语言编写一个简单的Web框架,文中有详细的代码供大家参考,需要的朋友可以参考下
    2024-05-05
  • go-micro微服务JWT跨域认证问题

    go-micro微服务JWT跨域认证问题

    JWT 以 JSON 对象的形式安全传递信息。因为存在数字签名,因此所传递的信息是安全的,这篇文章主要介绍了go-micro微服务JWT跨域认证,需要的朋友可以参考下
    2023-01-01
  • 一文带你了解Go语言中方法的调用

    一文带你了解Go语言中方法的调用

    这篇文章主要和大家分享一下Go语言中的方法的调用,文中的示例代码讲解详细,对我们学习Go语言有一定的帮助,需要的小伙伴可以参考一下
    2022-12-12
  • Golang timer可能造成的内存泄漏问题分析

    Golang timer可能造成的内存泄漏问题分析

    本文探讨了Golang中timer可能造成的内存泄漏问题,通过分析一段代码,解释了为什么协程在调用timer.Stop()后无法正常退出,文章指出,timer.Stop()并不关闭Channel,导致协程无法继续执行,最后,提出了一种修复方法,并呼吁大家关注和分享
    2024-12-12
  • Golang常用的几种密码加密方式分享

    Golang常用的几种密码加密方式分享

    这篇文章给大家介绍了Golang常用的几种密码加密方式,加密有两种方式,一种是直接加密,一种是盐值加密,直接加密指的是将原始密码直接进行加密,盐值加密则是在进行密码加密之前,文中有详细的代码示例,需要的朋友可以参考下
    2023-08-08
  • Go计算某段代码运行所耗时间简单实例

    Go计算某段代码运行所耗时间简单实例

    这篇文章主要给大家介绍了关于Go计算某段代码运行所耗时间的相关资料,主要介绍了Golang记录计算函数执行耗时、运行时间的一个简单方法,文中给出了详细的代码示例,需要的朋友可以参考下
    2023-11-11
  • 深入解析Golang中JSON的编码与解码

    深入解析Golang中JSON的编码与解码

    随着互联网的快速发展和数据交换的广泛应用,各种数据格式的处理成为软件开发中的关键问题,本文将介绍 Golang 中 JSON 编码与解码的相关知识,帮助大家了解其基本原理和高效应用,需要的可以收藏一下
    2023-05-05
  • golang微服务框架kratos实现Socket.IO服务的方法

    golang微服务框架kratos实现Socket.IO服务的方法

    本文主要介绍了golang微服务框架kratos实现Socket.IO服务的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • Go和Java算法详析之分数到小数

    Go和Java算法详析之分数到小数

    这篇文章主要给大家介绍了关于Go和Java算法详析之分数到小数的相关资料,文中通过实例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2022-08-08

最新评论