登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  Redis

Redis shard Pub/Sub 怎样订阅分片频道

来源:17golang原创

时间:2026-10-18 10:55:04 162浏览 收藏

在 Redis Cluster 里做实时通知时,普通 SUBSCRIBE 会让消息广播到所有节点;如果业务只需要让负责某个 slot 的节点处理消息,可以改用分片 Pub/Sub。关键命令是 SSUBSCRIBE,但它不是“订阅任意频道的集群版”:同一次调用里的分片频道必须落在同一个 slot,跨 slot 要拆开订阅。

官方地址:https://redis.io/docs/latest/commands/ssubscribe/

本文只讲 Redis 7.0+ 的分片频道订阅边界,并用 go-redis/v9 给出最小接收示例。配图是静态结构说明图,不是 Redis 控制台截图,也不代表本机已经执行了示例。

SSUBSCRIBE 解决的是什么问题

Redis 官方把分片频道按与 key 相同的 slot 算法分配。订阅者需要连接到负责该频道 slot 的主节点或副本节点,发布者则使用 SPUBLISH 发布到同一个分片频道。这样消息只在对应分片范围内传播,适合按租户、订单域或业务分区隔离实时事件。

它和普通 Pub/Sub 有三个容易混淆的差别:

  • SSUBSCRIBE 订阅的是 shard channel,服务端推送消息类型是 smessage;普通频道使用 message。
  • 分片频道有 slot 约束;同一条 SSUBSCRIBE 中的频道必须属于同一 slot。
  • 命令从 Redis 7.0 开始提供,旧版本不能只靠客户端方法名解决兼容问题。
Redis 分片 Pub/Sub 中频道、hash tag、slot 与订阅节点的静态关系说明图
图1:结构说明图,查看 hash tag 如何让多个分片频道进入同一 slot,以及订阅节点与频道 slot 的关系。

频道名先用 hash tag 固定路由

要在一次调用里订阅多个相关事件,可以把共享业务标识放进大括号。例如 orders:{tenant-42}:created 与 orders:{tenant-42}:paid 使用相同的 hash tag,Redis Cluster 会用大括号中的内容计算 slot。下面两个频道可以作为一组订阅;orders:{tenant-42}:created 与 orders:{tenant-77}:created 则应视为两组。

# 两个频道共享 {tenant-42},可以放进同一次 SSUBSCRIBE
SSUBSCRIBE orders:{tenant-42}:created orders:{tenant-42}:paid

# 不同 hash tag 可能属于不同 slot,应拆成两次订阅
SSUBSCRIBE orders:{tenant-42}:created
SSUBSCRIBE orders:{tenant-77}:created

这里的重点不是把所有频道都强行塞进一个 tag,而是让“同一个订阅连接必须同时监听的频道”共享路由标识。若频道数量很多,先按 hash tag 分组,再为每组创建独立的订阅连接。

Go 中先确认订阅,再接收 smessage

go-redis/v9 的 ClusterClient.SSubscribe 返回 *PubSub。建立订阅后先调用 Receive 等待确认,再取消息通道;否则发布者可能先发消息,应用却还没完成订阅确认。

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/redis/go-redis/v9"
)

func main() {
    ctx := context.Background()
    rdb := redis.NewClusterClient(&redis.ClusterOptions{
        Addrs: []string{"127.0.0.1:7000"}, // 使用集群入口地址
    })
    defer rdb.Close() // 释放集群客户端资源

    // 共享 {tenant-42},两个频道应落在同一 slot。
    pubsub := rdb.SSubscribe(ctx,
        "orders:{tenant-42}:created",
        "orders:{tenant-42}:paid",
    )
    defer pubsub.Close() // 订阅结束时关闭专用连接

    // 先等待 ssubscribe 确认,避免过早发布造成消息竞态。
    if _, err := pubsub.Receive(ctx); err != nil {
        log.Fatal(err)
    }

    // Channel 将 smessage 转成 go-redis 的 Message;取消 ctx 会结束等待。
    for msg := range pubsub.Channel() {
        fmt.Printf("channel=%s payload=%s\\n", msg.Channel, msg.Payload)
    }
}

go-redis 会把 smessage 转换为普通的 Message 对象,业务侧仍可读取 Channel 和 Payload。如果需要在退出时主动取消部分频道,可调用 pubsub.SUnsubscribe(ctx, "orders:{tenant-42}:paid");不传频道则取消该 PubSub 上的全部分片频道。

go-redis ClusterClient SSubscribe、订阅确认、Message 与关闭资源的静态调用关系说明图
图2:调用关系说明图,查看 ClusterClient、PubSub、ssubscribe 确认、smessage 和关闭资源之间的静态关系。

跨 slot 时拆分订阅并留意重连

当业务需要同时监听多个租户时,不能把不同 hash tag 的频道直接放入同一次 SSUBSCRIBE。应按 tag 拆分,例如为 tenant-42 和 tenant-77 分别建立 PubSub;每个 PubSub 内的频道保持同 slot。这样做会增加连接和 goroutine 管理成本,但路由边界是清楚的。

还要记住三点:

  1. 发布端必须使用 SPUBLISH,并使用完全相同的频道名;普通 PUBLISH 不会替代分片发布。
  2. go-redis 的 PubSub 支持网络异常后的自动重连和重新订阅,但应用仍应处理 Channel 关闭、上下文取消和最终退出。
  3. 分片 Pub/Sub 只把消息交给在线订阅者,不提供 Stream 那样的历史消息补读;不能把它当作可靠队列。

可以按“共享 hash tag → 同一次订阅;不同 hash tag → 不同 PubSub”的规则设计频道。只要先确认 Redis 版本、再按 slot 分组,SSUBSCRIBE 就不会因为把普通频道思路搬到集群中而产生路由错误。

相关问题

SSUBSCRIBE 最低需要哪个 Redis 版本? Redis 官方命令文档标注为 Redis Open Source 7.0.0 起可用。

为什么两个分片频道不能一次订阅? 通常是频道的 hash tag 不同,导致 slot 不同;按 hash tag 拆分订阅即可。

分片 Pub/Sub 能补回断线期间的消息吗? 不能。它面向在线分发,若需要确认、重试或历史读取,应评估 Redis Streams 等持久化消息模型。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>