用go-benchmark写redis基准测试需复用client、调用b.resettimer、合理设poolsize、区分do/pipelined/cmdable性能差异、混合读写与错误注入、关注allocs/op及内存影响。

用 go-benchmark 写 Redis 基准测试脚本
Go 里没有内置的 Redis 性能测试工具,得自己写基准测试(go test -bench),而不是靠 redis-benchmark 这类外部命令——后者测的是网络+服务端,而你通常想验证的是 Go 客户端在真实业务调用路径下的表现。
关键点是:用 testing.B 控制循环次数,复用 redis.Client 实例,避免每次 B.N 都新建连接:
func BenchmarkRedisSet(b *testing.B) {
client := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
PoolSize: 10,
})
defer client.Close()
b.ResetTimer() // 确保只测 Set 操作本身
for i := 0; i < b.N; i++ {
_ = client.Set(context.Background(), fmt.Sprintf("key:%d", i), "value", 0).Err()
}
}
-
b.ResetTimer()必须加,否则初始化 client 的耗时也会被计入 -
PoolSize要设合理值(比如 10–50),太小会排队,太大可能触发 Redis 连接数限制 - 别在循环里用
context.WithTimeout包裹每次调用——它本身有开销;如需超时,统一设在 client 初始化时的ContextTimeout
区分 Do、Cmdable 和 pipeline 的性能差异
同一操作,不同调用方式延迟和吞吐量差别明显。比如写入 100 个 key:
-
client.Set(...):走Cmdable接口,每次发一个命令,有 RTT 开销 -
client.Pipelined(...):批量发,服务端顺序执行并返回多个响应,适合高并发写入场景 -
client.Do(ctx, "SET", key, val):绕过类型安全封装,直接发原始命令,性能略高但易出错
实测(本地 Redis)中,100 次 Set 平均耗时约 8ms;同样操作用 Pipelined 降到 1.2ms 左右。但注意:Pipelined 不是事务,失败不会回滚,且所有命令必须一次性构造好——不能在 pipeline 里依赖前一条命令的结果。
立即学习“go语言免费学习笔记(深入)”;
模拟真实业务压力:混合读写 + 错误注入
纯 SET 或纯 GET 的 benchmark 很容易虚高。真实服务往往是读多写少、带 key pattern、偶发超时或连接中断。
建议在 Benchmark 中混入:
- 30%
GET、60%SET、10%INCR(模拟计数器) - 每 100 次操作随机 sleep 1–5ms(模拟业务逻辑间隙)
- 用
client.Options.MaxRetries = 2测试重试对 p99 延迟的影响 - 主动断开连接(如临时停 Redis)观察
connection refused错误是否被快速捕获,而非卡在 timeout 上
特别注意:如果用 redis.NewFailoverClient,要确保哨兵地址可连,否则首次初始化就会阻塞几秒,直接拖垮 benchmark 结果。
别忽略 redis.Client 的内存与 GC 影响
高频 benchmark 下,redis.Cmd 对象频繁创建/回收会抬高 GC 压力,导致 benchmem 显示 allocs/op 偏高,甚至掩盖真实网络瓶颈。
验证方法:
- 加
-benchmem参数运行:go test -bench=. -benchmem - 关注
BenchmarkRedisSet-8 100000 12452 ns/op 480 B/op 8 allocs/op这类输出 - 若 allocs/op > 5,说明有非必要对象分配;可尝试复用
*redis.StringCmd(不推荐)或改用Do降低封装层级
更隐蔽的问题是:client 内部的 ring buffer(如 pool.connReadBuf)默认大小为 64KB,短连接高频场景下可能触发多次扩容,这部分内存不会出现在 benchmark 的 allocs 统计里,但会影响长时间压测的稳定性。











