Redis LIST做消息队列压测需兼顾生产者与消费者并发,单独测试虚高(1w+ QPS),联合压测骤降至约5000 QPS,主因是BRPOP阻塞串行化、连接/内存资源争抢及业务逻辑拖累;redigo吞吐略高但go-redis稳定性更好;须调优系统ulimit、tcp-backlog、GOMAXPROCS,并监控pprof指标。

用 redis 的 LIST 做消息队列时,压测结果不能只看单侧吞吐——生产者和消费者并发能力会相互拖累,真实场景下并发得打七折甚至更低。
为什么 redis.LPUSH + redis.BRPOP 压测数据虚高
单独压生产者(LPUSH 100w 条)或消费者(BRPOP 拉完 100w 条),都能跑到 1w+ QPS;但两者一并跑,各自就掉到约 5000 QPS。这不是 Redis 性能瓶颈,而是阻塞操作天然串行化 + 客户端资源争抢导致的。
-
BRPOP是阻塞命令,每个 goroutine 占一个连接、一个 socket、一个 goroutine 栈,高并发下 fd 和内存吃紧 - 消费者若带业务逻辑(比如写 MySQL),入库是同步慢操作,
BRPOP调用频次被迫拉低,反过来积压 list 长度,又拖慢后续LPUSH的响应(Redis 单线程,大 list 的 push/pop 开销上升) - 用
redigo默认连接池(MaxIdle=3,MaxActive=0)不调参,实际并发卡在几十级
如何用 Go 写出逼近真实负载的压测脚本
关键不是“发得快”,而是“控得准”:既要模拟多消费者竞争同一 list,又要避免本地资源先崩。
- 生产者侧:用
sync.WaitGroup+ 带缓冲 channel 控制并发数(比如sem := make(chan struct{}, 100)),每条消息走复用的*redis.Pool,禁用KeepAlive防连接池假复用 - 消费者侧:每个 goroutine 独立
redis.Conn(别共用 pool),循环中加time.Sleep(1 * time.Millisecond)模拟处理耗时,否则 CPU 打满但没业务意义 - 必须设
redis.DialReadTimeout和DialWriteTimeout(建议 ≤ 2s),否则BRPOP卡住会导致 goroutine 泄漏 - 记录耗时别用
time.Now().Sub()在 hot path 反复调用,改用start := time.Now().UnixMilli()预存再减
redigo vs go-redis 压测表现差异在哪
实测中,redigo 在纯 LPUSH/BRPOP 场景下吞吐略高(约 +8%),但 go-redis 的 pipeline 和 context 支持让失败重试、超时控制更稳,适合带业务逻辑的消费者。
立即学习“go语言免费学习笔记(深入)”;
-
redigo:连接复用靠redis.Pool,需手动调MaxIdle/MaxActive;BRPOP返回值是[]interface{},类型断言开销略大 -
go-redis:默认启用连接池(PoolSize=10),但压测前必须显式设MinIdleConns=10和MaxConnAge=0,否则 idle 连接被回收后重建开销明显 - 两者都建议关闭
TLS(压测环境),避免握手耗时干扰;若必须 TLS,go-redis的tls.Config.InsecureSkipVerify=true可省掉证书校验
容易被忽略的三个系统级陷阱
压测脚本跑通不等于结果可信——90% 的偏差来自环境配置。
- Linux 默认
ulimit -n是 1024,而 100 并发消费者 × 每个 2 连接(1 control + 1 data)= 200 fd,直接触发too many open files - Redis 服务端的
tcp-backlog(默认 511)和maxclients(默认 10000)若未调大,BRPOP连接会排队在内核 accept 队列,表现为延迟突增、连接拒绝 - Go 程序没设
GOMAXPROCS,容器里可能只跑 1 个 P,CPU 利用率卡在 100% 但 goroutine 调度器堵死,pprof里runtime.findrunnable占比 >20%
真正决定压测价值的,从来不是峰值 QPS 数字,而是当消费者处理延迟从 10ms 涨到 50ms 时,list 积压是否线性增长、Redis 内存是否持续上涨、goroutine 数是否稳定——这些信号藏在 /debug/pprof/goroutine?debug=2 和 /debug/pprof/heap 里,不在终端输出中。



















