52、Redis集群操作:Redis Cluster、哈希槽、节点通信、C语言客户端集群支持
Redis Cluster,说白了就是一套自动分片、高可用的方案。我早年做缓存中间件选型时,单机Redis扛不住QPS,主从切换又得手动搞,后来上了Cluster,才算真正解放双手。今天咱们就聊聊Cluster的核心机制,以及怎么用C语言客户端去操作它。
Redis Cluster 的核心概念
Cluster不是简单的「多台Redis拼一起」。它有几个关键设计,我一个个说。
1. 哈希槽(Hash Slot)
整个Cluster有16384个哈希槽。每个key通过CRC16算法取模,映射到某个槽上。然后槽再分配给不同的节点。
举个例子:
// 计算key的哈希槽
unsigned int slot = crc16(key) % 16384;
为什么是16384?我记得Redis作者在邮件组里解释过:这个数字足够大,能均匀分布;又足够小,心跳包里的槽位信息用2KB就能传完。嗯,设计细节里全是权衡。
重要原则:客户端必须知道每个槽落在哪个节点上。否则你发请求给A节点,结果key在B节点,A会返回MOVED错误,让你重定向。
2. 节点通信(Gossip协议)
节点之间通过Gossip协议交换信息。每个节点每秒随机选几个节点,发PING/PONG包。包里包含自己知道的节点状态、槽位信息。
我曾在生产环境遇到过一个问题:网络抖动导致节点间Gossip延迟,集群误判节点下线。后来调大了cluster-node-timeout,从默认15秒改成30秒,才稳定下来。
C语言客户端如何支持集群
用C操作Redis Cluster,最常用的库是 hiredis 和 hiredis-cluster。hiredis本身不支持集群,需要自己处理槽位映射和重定向。hiredis-cluster是社区封装好的,直接支持集群操作。
安装 hiredis-cluster
git clone https://github.com/Nordix/hiredis-cluster.git
cd hiredis-cluster
mkdir build && cd build
cmake ..
make
sudo make install
注意:hiredis-cluster依赖libevent或libuv做异步IO。我建议用libevent,文档更全。
连接集群并执行命令
#include <hiredis_cluster/hiredis_cluster.h>
int main() {
redisClusterContext *cc = redisClusterContextInit();
redisClusterSetOptionAddNodes(cc, "127.0.0.1:7000,127.0.0.1:7001,127.0.0.1:7002");
redisClusterConnect2(cc);
if (cc->err) {
printf("连接失败: %s\n", cc->errstr);
return -1;
}
// 设置key
redisReply *reply = redisClusterCommand(cc, "SET user:1001 \"Alice\"");
if (reply) {
printf("SET 结果: %s\n", reply->str);
freeReplyObject(reply);
}
// 获取key
reply = redisClusterCommand(cc, "GET user:1001");
if (reply) {
printf("GET 结果: %s\n", reply->str);
freeReplyObject(reply);
}
redisClusterFree(cc);
return 0;
}
这段代码里,redisClusterSetOptionAddNodes 只传了三个节点。实际上你传一个就行,客户端会自动发现集群内所有节点。但我习惯多传几个,防止单点故障。
哈希槽的自动重定向
hiredis-cluster内部维护了一张槽位映射表。当你执行命令时,它会先计算key的槽,然后直接发给对应节点。如果集群发生了resharding(槽迁移),节点会返回 MOVED 或 ASK 错误,客户端会自动更新映射表并重试。
我的经验:resharding期间,客户端可能会收到多次重定向。hiredis-cluster默认重试3次。如果集群数据量大,建议把重试次数调高到5次,避免迁移时请求失败。
实战:批量写入与管道
Cluster模式下,管道(pipeline)不能跨节点。你得按槽分组,每个槽单独发管道。我写过一个工具,先收集所有key,按槽分组,再逐个节点发管道,性能提升很明显。
// 伪代码:按槽分组
for (int i = 0; i < key_count; i++) {
unsigned int slot = crc16(keys[i]) % 16384;
node = slot_to_node[slot];
append_to_batch(node, keys[i], values[i]);
}
// 对每个节点发管道
for (each node) {
redisClusterCommand(cc, "PIPELINE");
for (each cmd in node.batch) {
redisClusterAppendCommand(cc, "SET %s %s", cmd.key, cmd.val);
}
// 读取所有回复
}
注意:Cluster模式下,不支持多key操作(如MGET、MSET),除非所有key在同一个槽。你可以用哈希标签(hash tag)强制让相关key落到同一槽,比如 user:{1001}:name 和 user:{1001}:age。
故障转移与节点发现
Cluster的故障转移是自动的。当主节点挂了,从节点会选举成为新主。客户端需要感知这个变化。hiredis-cluster会定期发送 CLUSTER SLOTS 命令刷新槽位信息。
我曾经踩过一个坑:从节点晋升为主节点后,客户端缓存没更新,一直往旧节点发请求,导致大量超时。解决办法是开启自动刷新:
redisClusterSetOptionRefresh(cc, 1); // 开启自动刷新
redisClusterSetOptionRefreshInterval(cc, 1000); // 每1秒刷新一次
刷新间隔别设太短,否则集群节点多时,频繁发 CLUSTER SLOTS 会占用带宽。我个人建议5秒左右。
SVG:Redis Cluster 核心流程
总结几个要点
- 哈希槽:16384个槽,CRC16计算,客户端必须维护映射表
- 节点通信:Gossip协议,自动发现和故障检测
- C客户端:hiredis-cluster封装了槽位映射和重定向,开箱即用
- 避坑:多key操作需用哈希标签;resharding时增加重试次数;定期刷新槽位信息
Redis Cluster的设计其实很优雅。它把分片逻辑交给了客户端和节点共同维护,既保证了性能,又实现了自动故障转移。你在项目中用Cluster时,记住一点:客户端不是傻瓜,它需要知道数据在哪。把这个映射关系维护好,Cluster就能跑得很稳。
最后分享一个小技巧:调试Cluster时,可以用 redis-cli -c 连上去,它会自动处理重定向。配合 CLUSTER NODES 和 CLUSTER SLOTS 命令,能快速看清集群拓扑。