用redis.PubSub而非轮询,因其是Redis原生事件驱动模型,支持服务端主动推送、无消息时不占资源;但不保证消息可达与回溯,契合聊天室实时优先需求。
为什么用
而不是自己轮询?
轮询会浪费连接、增加延迟、放大 Redis 压力;
是 Redis 原生的事件驱动模型,客户端订阅后,服务端有消息就推,无消息不占资源。但注意:
不保证消息可达(断连期间发布的消息直接丢弃),也不支持消息回溯——这刚好符合聊天室“实时优先、不存历史”的常见需求。
实操建议:
立即学习
“
go语言免费学习笔记(深入)
”;
每个客户端连接对应一个独立的
实例,避免多个 goroutine 并发调用
导致 panic
务必在 goroutine 中单独启动
循环,主逻辑不能阻塞在
上
连接断开时,
会返回
或网络错误,需主动退出循环并关闭
和
的 channel 命名怎么设计?
别用固定 channel 名(比如硬写
),应支持多房间。推荐格式:
,其中
来自请求参数或 JWT payload,且需校验合法性(如只允许字母数字和下划线)。
实操建议:
立即学习
“
go语言免费学习笔记(深入)
”;
发布前检查
长度(建议 ≤64 字符)和字符集,防止注入恶意 channel 名(如含空格、星号或控制字符)
订阅时用
,不要用
测试连通性——它会发到所有已订阅 channel,可能触发误通知
同一个 client 不能重复订阅同一 channel,否则
会收到多份相同消息;可用
粗略判断是否已订阅(更稳妥的做法是维护本地订阅映射)
如何安全地把用户输入转成 JSON 广播出去?
用户发来的消息是原始字符串,直接
会导致接收方无法解析结构(比如缺用户名、时间戳)。必须封装为统一 JSON 格式,且要防 XSS 和超长内容。
Redis 8.2.3
Redis 8.2.3 是一款安全优先的高性能键值存储系统。该版本紧急修复了可能引发远程代码执行(RCE)的高危漏洞(CVE-2025-62507),并解决了 HyperLogLog 及 Cuckoo Filter 等数据结构在特定场景下的崩溃问题。建议所有用户立即升级,以保障生产环境的系统稳定与数据安全。
下载
实操建议:
立即学习
“
go语言免费学习笔记(深入)
”;
定义最小结构体:
,
用
字段必须做截断(如
),避免单条消息撑爆 Redis 单次 publish 限制(默认 512MB,但实际应 ≤1MB)
序列化用
,不要拼接字符串;若失败(如含不可编码的 struct 字段),直接丢弃该消息并 log 错误,**绝不能 fallback 到 raw text**
接收端用
解析,遇到非法 JSON 直接跳过,不 panic
goroutine 泄漏和连接泄漏怎么避免?
典型场景:用户关闭网页,但服务端没收到通知,
连接和监听 goroutine 一直挂着。Redis 默认 5 分钟超时踢出空闲连接,但 Go 侧不清理会导致 fd 耗尽。
实操建议:
立即学习
“
go语言免费学习笔记(深入)
”;
HTTP handler 中启动 goroutine 处理订阅后,用
作为取消信号,在
里调用
给
加超时控制:用
或
配合
,避免永久阻塞
每条消息处理完后检查
,一旦
就立即 break 循环并 close
Redis 客户端用
,不要用
或
——它们不支持 PubSub,会静默失败
真正难处理的是“半死连接”:TCP 还通,但客户端已崩溃。这时候得靠心跳+超时机制,而 Redis PubSub 本身不提供心跳。最简方案是在 HTTP long-polling 或 WebSocket 层做活跃检测,而不是指望
自身恢复。
redis.PubSubredis.PubSubPubSubredis.PubSubReceive()Listen()Receive()Receive()redis.NilPubSubSubscribePublish"chat:general""chat:" + roomIDroomIDroomIDps.Subscribe(ctx, channel)ps.Ping()Receive()ps.Channel() == channelPublishtype ChatMsg struct { Room string `json:"room"` User string `json:"user"` Text string `json:"text"` TS int64 `json:"ts"` }TStime.Now().UnixMilli()Textstring([]rune(text)[:500])json.Marshaljson.UnmarshalPubSubhttp.Request.Context()deferps.Close()ps.Receive()time.AfterFuncselectctx.Done()ctx.Err()context.Canceledredis.NewClientredis.NewFailoverClientredis.NewClusterClientPubSub