- 后端
- 数据库客户端
- 缓存
【免费下载链接】go-redis
Redis Go client
本文基于 go-redis 仓库中的 cluster-mget 示例 及其 main.go,系统讲解如何用redis.NewClusterClient连接 Redis Cluster,先批量写入 10 个键,再用单次MGET调用一次性取回全部值,并完成结果校验与清理。读完本文,你将掌握 Cluster 客户端的创建与连接测试、MGET的底层实现与返回语义、集群环境下跨 hash slot 时客户端的拆分与聚合逻辑,以及如何用 hash tag 把多个键固定到同一槽位。
示例概览:一次完整的批量读写闭环
cluster-mget示例演示了使用 Redis Cluster 客户端执行MGET高效检索多个键的完整流程,共包含五个步骤:
- 创建 Redis Cluster 客户端(
redis.NewClusterClient); - 用独立的
SET命令写入 10 个键(key0~key9); - 用单次
MGET操作一次性取回全部 10 个键的值; - 校验取回的值与期望值一一匹配;
- 清理现场,删除测试键。
这个"写入 → 批量读取 → 校验 → 清理"的闭环,是日常开发中验证批量读取命令正确性的标准套路,也直接对应了仓库中 doctests/string_example_test.go 里MGet的测试用法。
前置条件:准备一个可用的 Redis Cluster
示例默认期望集群节点运行在localhost:7000、localhost:7001、localhost:7002三个端口上。如果你没有现成的集群,可以直接使用仓库根目录的 docker-compose 编排快速拉起一个。
使用 docker-compose 一键启动集群
在 go-redis 仓库根目录执行:
docker compose --profile cluster up -d该命令会启动仓库根目录 docker-compose.yml 中定义的osscluster服务:它使用redislabs/client-libs-test镜像,环境变量NODES=6表示创建 6 个节点,PORT=16600指定起始端口,--cluster-enabled yes开启集群模式,并把宿主机16600-16605端口映射到容器内对应端口。因此该编排启动的集群节点位于16600-16605六个端口上。
根据实际端口调整 Addrs
如果你使用的是 docker-compose 集群(端口 16600-16605),需要把main.go中的Addrs改为:
Addrs: []string{ "localhost:16600", "localhost:16601", "localhost:16602", "localhost:16603", "localhost:16604", "localhost:16605", },注意:Addrs只需提供部分种子节点即可,go-redis 的ClusterClient会从这些节点获取集群拓扑,自动发现其余主从节点。示例 main.go 实际使用的正是这 6 个端口的配置,与仓库根目录的 docker-compose 编排保持一致。
运行示例
在example/cluster-mget目录下执行:
go run main.go该示例模块通过 go.mod 中的replace github.com/redis/go-redis/v9 => ../..直接引用仓库根目录源码,因此对 go-redis 的修改会即时生效,适合在开发期调试。
预期输出
正常运行时的完整输出如下:
✓ Connected to Redis cluster === Setting 10 keys === ✓ SET key0 = value0 ✓ SET key1 = value1 ✓ SET key2 = value2 ✓ SET key3 = value3 ✓ SET key4 = value4 ✓ SET key5 = value5 ✓ SET key6 = value6 ✓ SET key7 = value7 ✓ SET key8 = value8 ✓ SET key9 = value9 === Retrieving keys with MGET === === Validating MGET results === ✓ key0: value0 ✓ key1: value1 ✓ key2: value2 ✓ key3: value3 ✓ key4: value4 ✓ key5: value5 ✓ key6: value6 ✓ key7: value7 ✓ key8: value8 ✓ key9: value9 === Summary === === Cleaning up === ✓ Cleanup complete代码逐段精讲
1. 创建 Cluster 客户端并测试连接
ctx := context.Background() // Create a cluster client rdb := redis.NewClusterClient(&redis.ClusterOptions{ Addrs: []string{ "localhost:16600", "localhost:16601", "localhost:16602", "localhost:16603", "localhost:16604", "localhost:16605", }, }) defer rdb.Close() // Test connection if err := rdb.Ping(ctx).Err(); err != nil { panic(fmt.Sprintf("Failed to connect to Redis cluster: %v", err)) } fmt.Println("✓ Connected to Redis cluster")Ping在这里不仅验证网络连通性,还会触发客户端初始化并加载集群拓扑。连接失败时直接panic并给出包含错误信息的提示,是最简单直白的启动自检方式。
2. 批量写入 10 个键
keys := make([]string, 10) values := make([]string, 10) for i := 0; i < 10; i++ { keys[i] = fmt.Sprintf("key%d", i) values[i] = fmt.Sprintf("value%d", i) } for i := 0; i < 10; i++ { err := rdb.Set(ctx, keys[i], values[i], 0).Err() if err != nil { panic(fmt.Sprintf("Failed to set %s: %v", keys[i], err)) } fmt.Printf("✓ SET %s = %s\n", keys[i], values[i]) }Set的第三个参数是过期时间,0表示永不过期。这里采用逐个SET是为后续的MGET批量读取做数据准备,也顺带验证了单个键写入在集群下的正常路由(每个键根据 hash slot 被自动路由到对应节点)。
3. 用 MGET 一次取回全部值
result, err := rdb.MGet(ctx, keys...).Result() if err != nil { panic(fmt.Sprintf("Failed to execute MGET: %v", err)) }keys...将 10 个键展开为可变参数,一次调用完成全部读取。示例源码中这段调用以注释形式保留(main.go),实际运行时你可以取消注释观察真实行为。
4. 校验返回值
allValid := true for i, val := range result { expectedValue := values[i] actualValue, ok := val.(string) if !ok { fmt.Printf("✗ %s: expected string, got %T\n", keys[i], val) allValid = false continue } if actualValue != expectedValue { fmt.Printf("✗ %s: expected '%s', got '%s'\n", keys[i], expectedValue, actualValue) allValid = false } else { fmt.Printf("✓ %s: %s\n", keys[i], actualValue) } }校验逻辑包含两层:先用类型断言val.(string)确认返回值是字符串,再与期望值逐键比对。这一步直接印证了MGET的核心返回语义——结果切片与请求键顺序一一对应。
5. 清理测试键
for _, key := range keys { if err := rdb.Del(ctx, key).Err(); err != nil { fmt.Printf("Warning: Failed to delete %s: %v\n", key, err) } } fmt.Println("✓ Cleanup complete")删除失败时只打印警告而不中断流程,避免单点失败阻塞整体收尾。
6. hash tag 行为验证(示例中的附加实验)
示例末尾还演示了 hash tag 的用法(main.go):
err := rdb.Set(ctx, "{tag}exists", "asdf", 0).Err() ... val, err := rdb.Get(ctx, "{tag}nilkeykey1").Result() valm, err := rdb.MGet(ctx, "{tag}nilkeykey1", "{tag}exists").Result(){tag}花括号语法强制 Redis 只对花括号内的内容计算 CRC16 哈希,因此{tag}nilkeykey1与{tag}exists必然落在同一个 hash slot,从而可以安全地在一次MGET中访问。同时它也验证了MGET对不存在键返回nil(对应{tag}nilkeykey1)而存在键返回其值的行为。
MGET 命令的底层实现
MGet在 string_commands.go 中实现:
func (c cmdable) MGet(ctx context.Context, keys ...string) *SliceCmd { args := make([]interface{}, 1+len(keys)) args[0] = "mget" for i, key := range keys { args[1+i] = key } cmd := NewSliceCmd(ctx, args...) _ = c(ctx, cmd) return cmd }实现要点:
- 命令名固定为小写
mget,随后按传入顺序拼接所有键,最终通过 RESP 协议发给 Redis; - 返回值封装为
*SliceCmd,对应 Redis 返回的数组应答; cmdable接口意味着MGet被Client、ClusterClient、Ring、SentinelClient等所有客户端类型共用,因此这套 API 是跨部署形态一致的。
MGET 返回语义
MGET(Multiple GET)是 Redis 原生的批量读取命令,相比逐个执行GET显著减少网络往返,效率更高。它的语义如下:
- 返回一个
interface{}切片; - 切片中每个元素与请求中的键严格同序;
- 对不存在的键,对应位置返回
nil。
ClusterClient:集群下的自动路由能力
根据示例文档与源码实现,ClusterClient自动处理以下职责:
- 基于hash slot将键分发到对应集群节点(槽位计算由 internal/hashtag/hashtag.go 中的 CRC16 实现,共 16384 个槽位);
- 跟随集群重定向(
MOVED/ASK); - 维护到所有集群节点的连接池;
- 在集群拓扑变化(如主从切换、槽位迁移)时重试命令。
对于MGET这类多键命令,如果请求的键映射到不同的 hash slot,客户端可能需要在多个节点上拆分请求。这正是 Cluster 模式下单节点 Redis 不具备的复杂性。
跨槽拆分与结果聚合
从源码结构看,go-redis 通过 internal/routing 与 osscluster_router.go 实现多槽命令的分发与聚合:
- 路由策略中定义
multi_shard这类策略(见 internal/routing/policy.go),用于标识需要跨节点执行的命令; - 多槽执行结果通过
aggregateMultiSlotResults/aggregateKeyedValues(osscluster_router.go)汇总; - 聚合时使用 DefaultKeyedAggregator,它按
keyOrder把各分片返回的值重组回原始请求顺序,从而保证MGET的"结果与键同序"语义在跨节点场景下依然成立。
这正是ClusterClient.MGet与普通客户端在内部行为上的关键差异:对用户而言 API 完全相同,但底层经历了一次"按槽分组 → 并行请求 → 按序重组"的过程。
一个值得注意的边界:空参数 MGET
仓库测试 osscluster_test.go 断言,多分片命令mget在没有键参数时会报出"multi-shard command mget has no key arguments"错误。因此实际调用MGet前应确保至少传入一个键。
两种集群环境下的配置对照
| 场景 | 节点端口 | Addrs 配置 |
|---|---|---|
| README 默认说明 | localhost:7000 / 7001 / 7002 | 仅填这 3 个端口即可 |
| 仓库 docker-compose 集群 | localhost:16600 ~ 16605 | 建议填全部 6 个端口(示例 main.go 即如此) |
需要说明的是:Addrs只需提供部分种子节点,客户端启动后会基于CLUSTER SLOTS自动发现完整拓扑;但提供全部节点可减少启动阶段的发现开销,也让配置与编排文件一一对应、更易排查。
实战要点小结
- 一次 MGET 替代 N 次 GET:单次往返批量取回多个键,是降低集群应用延迟的常用手段;
- 结果顺序与请求顺序一致:返回值切片第 i 个元素对应第 i 个键,缺失键为
nil,类型断言(如val.(string))是常见的取值方式; - 跨槽自动拆分:
ClusterClient会对跨 hash slot 的MGET自动拆分并按序聚合,用户无需关心节点分布; - hash tag 强制同槽:当业务上要求多个键必须在同一节点(例如配合 Lua 脚本、事务或单命令多键操作)时,用
{tag}前缀把键固定到同一槽位; - 环境端口要匹配:docker-compose 集群端口为 16600-16605,示例与 README 默认的 7000-7002 不同,运行前务必核对
Addrs。
延伸阅读
- 完整示例代码:example/cluster-mget/main.go
- 示例说明文档:example/cluster-mget/README.md
- MGET 实现源码:string_commands.go
- 集群路由与聚合:osscluster_router.go 与 internal/routing/aggregator.go
- hash slot 计算:internal/hashtag/hashtag.go
- 集群相关测试用例:osscluster_test.go(含多分片 MGET 的边界行为验证)
- 集群环境编排:docker-compose.yml
- 后端
- 数据库客户端
- 缓存
【免费下载链接】go-redis
Redis Go client
相关推荐
使用Redis实现高效限速的Rate Limiting——go-redis/redis_rate
使用Redis实现高效限速的Rate Limiting——go redis/redis_rate 在构建高并发和分布式系统中,对请求速率进行限制是保障服务稳定性
如何快速开发CamanJS自定义图像处理插件:完整指南
如何快速开发CamanJS自定义图像处理插件:完整指南 CamanJS是一个强大的Javascript HTML5 Canvas图像处理库,它允许开发者通过简单
PHP Redis批量命令性能优化:MGET与MSET终极指南
PHP Redis批量命令性能优化:MGET与MSET终极指南 Redis作为高性能内存数据库,在PHP应用中广泛使用。 phpredis扩展 提供了强大的Re
后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考