前言
你有没有想过:Redis Cluster是怎么做到高性能、高可用、可扩展的?数据是怎么分片的?节点宕机了怎么办?
Redis Cluster是Redis的分布式解决方案,今天我们从零实现核心设计:
· 一致性哈希(Consistent Hashing)
· 数据分片(Slot)
· 节点发现(Gossip协议)
· 故障检测(心跳 + 投票)
· 主从切换(Failover)
· 客户端重定向(MOVED/ASK)
一、Redis Cluster核心原理
- 架构图
```
┌─────────────────────────────────────────────────────────────┐
│ Redis Cluster │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Node1 │ │ Node2 │ │ Node3 │ │ Node4 │ │
│ │ Slot0- │ │ Slot- │ │ Slot- │ │ Slot- │ │
│ │ 16383 │ │ ... │ │ ... │ │ ... │ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │ │ │ │ │
│ └─────────────┴─────────────┴─────────────┘ │
│ Gossip通信 │
└─────────────────────────────────────────────────────────────┘
```
- 核心概念
概念 说明
Slot 16384个槽位,映射到不同节点
Gossip 节点间通信协议(MEET/PING/PONG)
Failover 主节点宕机,从节点升主
MOVED 客户端重定向到正确节点
ASK 临时重定向(迁移中)
二、完整代码实现
- 基础数据结构
```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <errno.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define MAX_NODES 20
#define SLOT_COUNT 16384
#define MAX_KEY_LEN 256
#define MAX_VALUE_LEN 4096
// 节点状态
typedef enum {
NODE_ONLINE = 0,
NODE_OFFLINE = 1,
NODE_FAIL = 2,
NODE_HANDSHAKE = 3
} node_status_t;
// 节点角色
typedef enum {
ROLE_MASTER = 0,
ROLE_SLAVE = 1
} node_role_t;
// 集群节点
typedef struct cluster_node {
char node_id64;
char host32;
int port;
node_role_t role;
node_status_t status;
int slotsSLOT_COUNT / 8; // 位图:该节点拥有的槽位
int slot_count;
struct cluster_node *master; // 如果是slave,指向master
struct cluster_node **slaves; // 如果是master,指向slaves
int slave_count;
time_t last_ping;
time_t last_pong;
int ping_sent;
struct cluster_node *next;
} cluster_node_t;
// 集群状态
typedef struct cluster {
cluster_node_t *nodes;
cluster_node_t *myself;
int node_count;
pthread_mutex_t mutex;
int port;
int running;
pthread_t gossip_thread;
pthread_t failover_thread;
} cluster_t;
// 数据存储(简化:内存KV)
typedef struct kv_store {
char keyMAX_KEY_LEN;
char valueMAX_VALUE_LEN;
struct kv_store *next;
} kv_store_t;
```
- 槽位管理
```c
// 计算key的槽位(CRC16)
int key_to_slot(const char *key) {
unsigned int hash = 0;
const char *p = key;
while (*p) {
hash = (hash * 31) + *p++;
}
return hash % SLOT_COUNT;
}
// 设置节点拥有槽位
void node_add_slot(cluster_node_t *node, int slot) {
int byte_idx = slot / 8;
int bit_idx = slot % 8;
node->slotsbyte_idx |= (1 << bit_idx);
node->slot_count++;
}
// 检查节点是否拥有槽位
int node_has_slot(cluster_node_t *node, int slot) {
int byte_idx = slot / 8;
int bit_idx = slot % 8;
return (node->slotsbyte_idx & (1 << bit_idx)) != 0;
}
// 找到负责某个槽位的节点
cluster_node_t *cluster_find_slot_node(cluster_t *cluster, int slot) {
pthread_mutex_lock(&cluster->mutex);
cluster_node_t *node = cluster->nodes;
while (node) {
if (node->status == NODE_ONLINE && node_has_slot(node, slot)) {
pthread_mutex_unlock(&cluster->mutex);
return node;
}
node = node->next;
}
pthread_mutex_unlock(&cluster->mutex);
return NULL;
}
```
- 节点发现(Gossip协议)
```c
// 创建集群节点
cluster_node_t *node_create(const char *node_id, const char *host, int port) {
cluster_node_t *node = malloc(sizeof(cluster_node_t));
strcpy(node->node_id, node_id);
strcpy(node->host, host);
node->port = port;
node->role = ROLE_MASTER;
node->status = NODE_ONLINE;
memset(node->slots, 0, sizeof(node->slots));
node->slot_count = 0;
node->master = NULL;
node->slaves = NULL;
node->slave_count = 0;
node->last_ping = time(NULL);
node->last_pong = time(NULL);
node->ping_sent = 0;
return node;
}
// MEET消息(加入集群)
void cluster_meet(cluster_t *cluster, const char *host, int port) {
char node_id64;
snprintf(node_id, sizeof(node_id), "node-%s:%d", host, port);
pthread_mutex_lock(&cluster->mutex);
cluster_node_t *existing = cluster->nodes;
while (existing) {
if (strcmp(existing->host, host) == 0 && existing->port == port) {
pthread_mutex_unlock(&cluster->mutex);
return;
}
existing = existing->next;
}
cluster_node_t *new_node = node_create(node_id, host, port);
new_node->next = cluster->nodes;
cluster->nodes = new_node;
cluster->node_count++;
pthread_mutex_unlock(&cluster->mutex);
printf("Gossip 节点加入: %s:%d\n", host, port);
}
// Gossip心跳
void *gossip_thread(void *arg) {
cluster_t *cluster = (cluster_t*)arg;
while (cluster->running) {
sleep(1); // 每秒发送PING
pthread_mutex_lock(&cluster->mutex);
cluster_node_t *node = cluster->nodes;
while (node) {
if (node != cluster->myself && node->status == NODE_ONLINE) {
// 发送PING(模拟)
node->last_ping = time(NULL);
node->ping_sent++;
printf("Gossip PING → %s:%d\n", node->host, node->port);
}
node = node->next;
}
pthread_mutex_unlock(&cluster->mutex);
}
return NULL;
}
```
- 故障检测
```c
// 检测节点故障
void cluster_check_fail(cluster_t *cluster) {
pthread_mutex_lock(&cluster->mutex);
time_t now = time(NULL);
cluster_node_t *node = cluster->nodes;
while (node) {
if (node != cluster->myself && node->status == NODE_ONLINE) {
// 检查是否超时(5秒无响应)
if (now - node->last_pong > 5) {
// 标记为FAIL
node->status = NODE_FAIL;
printf("Failover 节点 %s 故障\n", node->node_id);
// 如果有slave,触发切换
if (node->slave_count > 0) {
cluster_node_t *slave = node->slaves0;
// 将slave提升为master
slave->role = ROLE_MASTER;
// 接管槽位
memcpy(slave->slots, node->slots, sizeof(node->slots));
slave->slot_count = node->slot_count;
slave->master = NULL;
printf("Failover 提升 %s 为 Master\n", slave->node_id);
// 更新集群状态
// 实际需要广播到所有节点
}
}
}
node = node->next;
}
pthread_mutex_unlock(&cluster->mutex);
}
// 故障检测线程
void *failover_thread(void *arg) {
cluster_t *cluster = (cluster_t*)arg;
while (cluster->running) {
sleep(2);
cluster_check_fail(cluster);
}
return NULL;
}
```
- 数据操作
```c
// KV存储(简化:每个节点独立存储)
kv_store_t *g_kv_store = NULL;
pthread_mutex_t kv_mutex = PTHREAD_MUTEX_INITIALIZER;
// 执行命令
int cluster_execute(cluster_t *cluster, const char *key,
const char *cmd, char *response) {
int slot = key_to_slot(key);
// 计算当前节点是否负责这个slot
if (!node_has_slot(cluster->myself, slot)) {
// 重定向
cluster_node_t *target = cluster_find_slot_node(cluster, slot);
if (target) {
snprintf(response, MAX_VALUE_LEN,
"MOVED %d %s:%d\n", slot, target->host, target->port);
return -1;
}
return -1;
}
// 执行命令
if (strcmp(cmd, "SET") == 0) {
// 这里简化,实际需要解析参数
pthread_mutex_lock(&kv_mutex);
kv_store_t *kv = malloc(sizeof(kv_store_t));
strcpy(kv->key, key);
strcpy(kv->value, "value"); // 简化
kv->next = g_kv_store;
g_kv_store = kv;
pthread_mutex_unlock(&kv_mutex);
strcpy(response, "OK\n");
return 0;
} else if (strcmp(cmd, "GET") == 0) {
pthread_mutex_lock(&kv_mutex);
kv_store_t *kv = g_kv_store;
while (kv) {
if (strcmp(kv->key, key) == 0) {
snprintf(response, MAX_VALUE_LEN, "%s\n", kv->value);
pthread_mutex_unlock(&kv_mutex);
return 0;
}
kv = kv->next;
}
pthread_mutex_unlock(&kv_mutex);
strcpy(response, "(nil)\n");
return 0;
}
strcpy(response, "ERR unknown command\n");
return -1;
}
```
- 集群初始化
```c
// 创建集群
cluster_t *cluster_create(int port) {
cluster_t *cluster = malloc(sizeof(cluster_t));
memset(cluster, 0, sizeof(cluster_t));
cluster->port = port;
cluster->running = 1;
pthread_mutex_init(&cluster->mutex, NULL);
// 创建自身节点
char node_id64;
snprintf(node_id, sizeof(node_id), "node-localhost:%d", port);
cluster->myself = node_create(node_id, "127.0.0.1", port);
cluster->myself->next = cluster->nodes;
cluster->nodes = cluster->myself;
cluster->node_count = 1;
// 分配槽位(简化:所有槽位都给自己)
for (int i = 0; i < SLOT_COUNT; i++) {
node_add_slot(cluster->myself, i);
}
// 启动线程
pthread_create(&cluster->gossip_thread, NULL, gossip_thread, cluster);
pthread_create(&cluster->failover_thread, NULL, failover_thread, cluster);
printf("Cluster 节点启动: localhost:%d, 槽位: 0-%d\n",
port, SLOT_COUNT-1);
return cluster;
}
// 加入集群
void cluster_join(cluster_t *cluster, const char *seed_host, int seed_port) {
// 发送MEET消息到种子节点
cluster_meet(cluster, seed_host, seed_port);
// 种子节点会广播到其他节点
}
```
- 测试代码
```c
void test_cluster() {
printf("=== Redis Cluster核心设计测试 ===\n\n");
// 创建集群节点
cluster_t *cluster1 = cluster_create(7000);
cluster_t *cluster2 = cluster_create(7001);
cluster_t *cluster3 = cluster_create(7002);
// 节点加入
cluster_meet(cluster1, "127.0.0.1", 7001);
cluster_meet(cluster1, "127.0.0.1", 7002);
cluster_meet(cluster2, "127.0.0.1", 7000);
cluster_meet(cluster2, "127.0.0.1", 7002);
cluster_meet(cluster3, "127.0.0.1", 7000);
cluster_meet(cluster3, "127.0.0.1", 7001);
printf("\n=== 集群状态 ===\n");
printf("节点数: %d\n", cluster1->node_count);
printf("自身节点: %s:%d\n", cluster1->myself->host, cluster1->myself->port);
printf("槽位数: %d\n", cluster1->myself->slot_count);
// 测试数据操作
printf("\n=== 数据操作测试 ===\n");
char responseMAX_VALUE_LEN;
int ret = cluster_execute(cluster1, "user:1001", "SET", response);
printf("SET user:1001 → %s", response);
ret = cluster_execute(cluster1, "user:1001", "GET", response);
printf("GET user:1001 → %s", response);
// 模拟故障
printf("\n=== 故障检测测试 ===\n");
cluster2->running = 0; // 模拟节点宕机
sleep(7); // 等待检测
// 运行10秒
sleep(10);
// 清理
cluster1->running = 0;
cluster2->running = 0;
cluster3->running = 0;
pthread_join(cluster1->gossip_thread, NULL);
pthread_join(cluster1->failover_thread, NULL);
pthread_join(cluster2->gossip_thread, NULL);
pthread_join(cluster2->failover_thread, NULL);
pthread_join(cluster3->gossip_thread, NULL);
pthread_join(cluster3->failover_thread, NULL);
free(cluster1); free(cluster2); free(cluster3);
printf("\n测试完成\n");
}
int main() {
srand(time(NULL));
test_cluster();
return 0;
}
```
三、编译和运行
```bash
gcc -o redis_cluster redis_cluster.c -lpthread
./redis_cluster
```
四、Redis Cluster vs 其他方案
特性 Redis Cluster Codis Twemproxy
分片方式 一致性哈希 一致性哈希 一致性哈希
高可用 自动Failover 依赖ZK 手动
客户端 智能客户端 代理层 代理层
扩展性 在线扩展 在线扩展 需重启
复杂度 中 高 低
五、总结
通过这篇文章,你学会了:
· Redis Cluster的核心架构(分片、副本、Gossip)
· 槽位管理和数据路由
· 节点发现(MEET/PING/PONG)
· 故障检测和Failover
· 客户端重定向(MOVED)
Redis Cluster是分布式缓存的经典实现。掌握它,你就理解了高性能分布式系统的核心设计。
下一篇预告:《从零实现一个分布式搜索引擎:倒排索引与检索》
评论区分享一下你对Redis Cluster的理解~