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,最常用的库是 hiredishiredis-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(槽迁移),节点会返回 MOVEDASK 错误,客户端会自动更新映射表并重试。

我的经验: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}:nameuser:{1001}:age

故障转移与节点发现

Cluster的故障转移是自动的。当主节点挂了,从节点会选举成为新主。客户端需要感知这个变化。hiredis-cluster会定期发送 CLUSTER SLOTS 命令刷新槽位信息。

我曾经踩过一个坑:从节点晋升为主节点后,客户端缓存没更新,一直往旧节点发请求,导致大量超时。解决办法是开启自动刷新:

redisClusterSetOptionRefresh(cc, 1);  // 开启自动刷新
redisClusterSetOptionRefreshInterval(cc, 1000);  // 每1秒刷新一次

刷新间隔别设太短,否则集群节点多时,频繁发 CLUSTER SLOTS 会占用带宽。我个人建议5秒左右。

SVG:Redis Cluster 核心流程

Redis Cluster 核心流程 C语言客户端 计算CRC16(key) % 16384 查找槽位 → 节点映射表 节点A (槽0-5460) 节点B (槽5461-10922) 节点C (槽10923-16383) 从节点A1 从节点B1 从节点C1 Gossip协议:节点间交换槽位信息、心跳检测

总结几个要点

  • 哈希槽:16384个槽,CRC16计算,客户端必须维护映射表
  • 节点通信:Gossip协议,自动发现和故障检测
  • C客户端:hiredis-cluster封装了槽位映射和重定向,开箱即用
  • 避坑:多key操作需用哈希标签;resharding时增加重试次数;定期刷新槽位信息

Redis Cluster的设计其实很优雅。它把分片逻辑交给了客户端和节点共同维护,既保证了性能,又实现了自动故障转移。你在项目中用Cluster时,记住一点:客户端不是傻瓜,它需要知道数据在哪。把这个映射关系维护好,Cluster就能跑得很稳。

最后分享一个小技巧:调试Cluster时,可以用 redis-cli -c 连上去,它会自动处理重定向。配合 CLUSTER NODESCLUSTER SLOTS 命令,能快速看清集群拓扑。

公众号:蓝海资料掘金营,微信deep3321