Redis进阶:管道、事务与发布订阅
Redis进阶:管道、事务与发布订阅摘要: 本篇深入Redis高级操作讲解Pipeline管道批量执行原理与使用、MULTI/EXEC事务配合WATCH实现乐观锁、Pub/Sub发布订阅模式的消息收发实现分享Pipeline中混入耗时操作导致后续命令延迟的踩坑经历对比Pipeline、事务与Lua脚本的适用场景。开篇故事上个月我们做用户积分批量更新1000个用户的积分要同时刷新。同事写了个for循环每个用户一条命令发到Redis。上线后Redis的QPS翻了十倍网络延迟也上去了。我改成Pipeline后1000条命令一次发完执行时间从800ms降到50ms。Redis单线程处理命令但网络往返的延迟可以优化。Pipeline、事务、发布订阅是go-redis的三个进阶能力用好了性能能提升一个量级。这篇我把三者的原理和使用场景讲清楚。一、Pipeline管道批量操作Pipeline把多条命令打包一次网络往返发到Redis结果一次性拿回来。100条命令从100次往返变成1次。packagemainimport(contextfmttimegithub.com/redis/go-redis/v9)// Pipeline基本用法funcpipelineBasic(ctx context.Context,rdb*redis.Client){pipe:rdb.Pipeline()// 注册命令不立即执行返回Cmd用于取结果cmd1:pipe.Set(ctx,key1,value1,0)cmd2:pipe.Get(ctx,key1)// Exec一次性发送所有命令pipe.Exec(ctx)fmt.Println(key1设置结果:,cmd1.Val())fmt.Println(key1的值:,cmd2.Val())}// Pipeline vs 普通操作性能对比funcpipelineBenchmark(ctx context.Context,rdb*redis.Client){constcount1000// 普通方式:逐条执行start:time.Now()fori:0;icount;i{rdb.Set(ctx,fmt.Sprintf(normal:%d,i),x,0)}fmt.Printf(普通方式 %d条: %v\n,count,time.Since(start))// Pipeline方式:批量执行starttime.Now()pipe:rdb.Pipeline()fori:0;icount;i{pipe.Set(ctx,fmt.Sprintf(pipe:%d,i),x,0)}pipe.Exec(ctx)fmt.Printf(Pipeline %d条: %v\n,count,time.Since(start))// Pipeline通常快10-20倍}TxPipeline是事务型Pipeline命令包在MULTI/EXEC里执行保证原子性。// TxPipeline:事务型Pipeline命令要么全成功要么全失败functxPipelineExample(ctx context.Context,rdb*redis.Client){pipe:rdb.TxPipeline()pipe.Incr(ctx,counter)pipe.Set(ctx,flag,done,0)pipe.Expire(ctx,counter,10*time.Minute)pipe.Exec(ctx)}二、事务(MULTI/EXEC/WATCH)Redis事务是一组命令的顺序执行中间不会被其他客户端打断。但Redis事务不支持回滚某条命令出错后面的照样执行。WATCH实现乐观锁的场景很典型。扣库存时先WATCH库存key读取当前值如果大于0就扣减。如果在WATCH和EXEC之间库存被别人改了事务自动失败需要重试。// WATCH实现乐观锁扣库存funcdeductStock(ctx context.Context,rdb*redis.Client,productIDstring)error{stockKey:fmt.Sprintf(stock:%s,productID)maxRetry:3fori:0;imaxRetry;i{// Watch监视stockKey被修改则事务失败err:rdb.Watch(ctx,func(tx*redis.Tx)error{stock,err:tx.Get(ctx,stockKey).Int()iferrredis.Nil{returnfmt.Errorf(商品不存在)}iferr!nil{returnerr}ifstock0{returnfmt.Errorf(库存不足)}// 事务内扣减库存pipe:tx.TxPipeline()pipe.Decr(ctx,stockKey)pipe.HIncrBy(ctx,sales,productID,1)_,errpipe.Exec(ctx)returnerr},stockKey)iferrnil{returnnil// 成功}// 事务冲突则重试iferrredis.TxFailedErr{fmt.Printf(第%d次重试\n,i1)continue}returnerr}returnfmt.Errorf(重试次数用完)}三、发布订阅(Pub/Sub)Pub/Sub是Redis内置的消息广播机制。发布者往channel发消息所有订阅了该channel的客户端都能收到。适合实时通知和聊天室。packagemainimport(contextfmttimegithub.com/redis/go-redis/v9)// 订阅者:监听channelfuncsubscriber(ctx context.Context,rdb*redis.Client,namestring){pubsub:rdb.Subscribe(ctx,chat_room)deferpubsub.Close()// 循环接收消息formsg:rangepubsub.Channel(){fmt.Printf([%s] %s: %s\n,name,msg.Channel,msg.Payload)}}funcmain(){rdb:redis.NewClient(redis.Options{Addr:localhost:6379})deferrdb.Close()ctx:context.Background()// 启动两个订阅者gosubscriber(ctx,rdb,客户端A)gosubscriber(ctx,rdb,客户端B)time.Sleep(time.Second)// 等订阅者就绪// 发布者发消息fori:0;i5;i{rdb.Publish(ctx,chat_room,fmt.Sprintf(消息%d,i1))time.Sleep(time.Second)}time.Sleep(2*time.Second)}Pub/Sub有个特点消息发出去没人订阅就丢了Redis不保存历史消息。需要消息可靠性用Stream或外部消息队列。四、独家踩坑:Pipeline中混入耗时操作这个坑比较隐蔽。我们在一个Pipeline里塞了50条命令其中有一条是KEYS *。结果整个Pipeline的执行时间从20ms飙到3秒后面所有命令都被阻塞了。// 问题代码:Pipeline中混入O(N)耗时命令funcbadPipeline(ctx context.Context,rdb*redis.Client){pipe:rdb.Pipeline()pipe.Set(ctx,k1,v1,0)pipe.Set(ctx,k2,v2,0)// KEYS *是O(N)操作阻塞Redis主线程// Redis单线程执行期间所有后续命令都等着pipe.Keys(ctx,*)pipe.Get(ctx,k1)// 要等KEYS *执行完才能返回pipe.Get(ctx,k2)pipe.Exec(ctx)}排查过程比较曲折。看网络延迟0.3ms正常然后看Redis的SLOWLOG发现KEYS *执行时间有2-3秒。Redis是单线程Pipeline里命令逐条执行一个慢命令阻塞整个Pipeline。修复方案很简单。把KEYS *从Pipeline拿出来单独执行更好的做法是用SCAN替代游标式遍历不阻塞主线程。// 修复后:慢命令独立执行用SCAN替代KEYSfuncfixedPipeline(ctx context.Context,rdb*redis.Client){// Pipeline只放轻量级命令pipe:rdb.Pipeline()pipe.Set(ctx,k1,v1,0)pipe.Set(ctx,k2,v2,0)pipe.Get(ctx,k1)pipe.Get(ctx,k2)pipe.Exec(ctx)// SCAN游标式遍历不阻塞varcursoruint64for{result,newCursor,_:rdb.Scan(ctx,cursor,*,100).Result()fmt.Println(扫描到:,result)cursornewCursorifcursor0{break// 遍历完成}}}经验就是Pipeline里只放O(1)或O(log N)的轻量命令。KEYS、FLUSHALL、大范围SORT这些重操作要独立执行或用SCAN替代。五、对比分析特性Pipeline事务(MULTI/EXEC)Lua脚本原子性无命令间可插入其他客户端命令有顺序执行不被打断有整个脚本原子执行网络往返1次1次1次条件逻辑不支持不支持WATCH只做冲突检测支持脚本内可写if/for错误回滚不涉及不回滚出错继续执行脚本报错不回滚已执行部分适用场景批量读写无依赖需要原子性的简单操作复杂条件判断的原子操作调试难度低中高选择思路很直接。批量读写无依赖用Pipeline。需要原子性但逻辑简单用事务。逻辑复杂且必须原子执行用Lua脚本。日常开发Pipeline用得最多事务次之Lua脚本用在扣库存这类需要条件判断的场景。总结与预告Pipeline是Redis性能优化第一手段把N次网络往返压成1次。事务配合WATCH能实现乐观锁但Redis事务不支持回滚。Pub/Sub适合实时广播不保证消息到达需要可靠消息用Stream。下一篇讲Redis缓存策略深入缓存穿透、击穿、雪崩三种经典问题的解决方案。

相关新闻

AI辅助数学研究实战:Claude与Python生态破解黎曼猜想探索

AI辅助数学研究实战:Claude与Python生态破解黎曼猜想探索

1. 项目概述:当AI“助手”开始挑战数学皇冠最近在AI和数学的交叉圈子里,有个事儿讨论得挺热闹。起因是有开发者声称,利用Claude这个AI模型,在一天半的时间里,对黎曼猜想这个“数学界皇冠上的明珠”取得了某种“新纪录”…

2026/10/17 6:36:00 阅读更多 →
线上问诊AI会误诊、开错药吗?MedGuard声明级风险守门员实测:声明级F1达0.7677、REAL子集精度95.73%,9大基线平均领先21.2%

线上问诊AI会误诊、开错药吗?MedGuard声明级风险守门员实测:声明级F1达0.7677、REAL子集精度95.73%,9大基线平均领先21.2%

一、研究背景 远程医疗在新冠之后飞速普及,互联网医院、AI 问诊助手把"看上病"的地理边界抹平了。但便利的另一面是风险:医生看不见患者的表情、触不到查体,只能依赖屏幕上的文字;家属一句"他状态挺好的"就可…

2026/10/17 6:36:03 阅读更多 →
Redis入门:go-redis基本操作与连接池

Redis入门:go-redis基本操作与连接池

Redis入门:go-redis基本操作与连接池摘要: 本篇讲解Go操作Redis的核心库go-redis v9,包括客户端连接配置、String/Hash/List/Set/ZSet五种数据类型的基本操作、连接池参数调优(PoolSize、MinIdleConns、PoolTimeout),分享连接池耗尽导致请求超时的踩坑经…

2026/10/17 6:36:04 阅读更多 →

最新新闻

MySQL 5.7.40离线安装包部署指南:glibc2.12兼容与生产级配置

MySQL 5.7.40离线安装包部署指南:glibc2.12兼容与生产级配置

简介:本资源为MySQL 5.7.40官方二进制离线安装包,专为x86_64架构、glibc 2.12及以上版本的Linux系统设计,面向数据库运维工程师、Linux系统管理员及后端开发人员,解决无外网环境下的MySQL快速部署与初始化问题。压缩包共379个文件…

2026/10/18 14:22:30 阅读更多 →
2024电赛H题自动行驶小车:MSPM0G3507+JY61P工程实现与避坑指南

2024电赛H题自动行驶小车:MSPM0G3507+JY61P工程实现与避坑指南

简介:针对2024年全国大学生电子设计竞赛H题开发的自动行驶小车完整工程源码,采用德州仪器MSPM0G3507作为主控,配合JY61P九轴姿态传感器与OLED显示模块,完整实现姿态解算、双电机差速转向和速度控制、运行状态实时显示等功能。资源…

2026/10/18 14:22:30 阅读更多 →
匿名管道实现Linux进程池:IPC并发任务分发实战

匿名管道实现Linux进程池:IPC并发任务分发实战

刚接触Linux系统编程那会儿,我被一道面试题卡了很久:如果只允许用进程,不引入额外依赖,你会怎么设计一个轻量级的并发任务分发机制?有人答共享内存加锁,有人答消息队列,其实在父子进程协同的场景…

2026/10/18 14:22:30 阅读更多 →
前端性能优化实战:从全局Loading到大数据渲染卡顿的治理方案

前端性能优化实战:从全局Loading到大数据渲染卡顿的治理方案

先别急着上虚拟列表,也别一股脑把loading都换成骨架屏。直接说结论:只要你的页面同时出现“一个全局loading转圈”和“一次性拉回几千行数据”这两件事,主线程卡顿几乎是必然的,而且根源往往不在你最先怀疑的那个环节。这不是我故…

2026/10/18 14:22:30 阅读更多 →
Vue+Node.js电子外设商城系统架构设计与实现

Vue+Node.js电子外设商城系统架构设计与实现

这个项目标题拿到手里,第一反应是"又一个商城系统",但真把它拆开看,"电子外设"这四个字才是关键。我前后做过几个商城类的项目,从图书到生鲜都碰过,电子外设这个品类在商品建模、订单流程、库存管…

2026/10/18 14:22:30 阅读更多 →
老旧ARM盒子部署可道云:Docker自建私有网盘的完整实战

老旧ARM盒子部署可道云:Docker自建私有网盘的完整实战

玩ARM盒子的人,迟早会走到“自建私有网盘”这一步。手头一台吃灰的ARM设备,刷上Armbian 5.77(Debian 9),跑在老旧内核上,想要一个能传大文件、能在线看文档、能挂到电脑上当网络硬盘的Web网盘,可…

2026/10/18 14:21:29 阅读更多 →

日新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/18 0:00:55 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 0:55:02 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →