从零实现一个分布式缓存:Redis Cluster的核心设计

前言

你有没有想过:Redis Cluster是怎么做到高性能、高可用、可扩展的?数据是怎么分片的?节点宕机了怎么办?

Redis Cluster是Redis的分布式解决方案,今天我们从零实现核心设计:

· 一致性哈希(Consistent Hashing)

· 数据分片(Slot)

· 节点发现(Gossip协议)

· 故障检测(心跳 + 投票)

· 主从切换(Failover)

· 客户端重定向(MOVED/ASK)


一、Redis Cluster核心原理

  1. 架构图

```

┌─────────────────────────────────────────────────────────────┐

│ Redis Cluster │

│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │

│ │ Node1 │ │ Node2 │ │ Node3 │ │ Node4 │ │

│ │ Slot0- │ │ Slot- │ │ Slot- │ │ Slot- │ │

│ │ 16383 │ │ ... │ │ ... │ │ ... │ │

│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │

│ │ │ │ │ │

│ └─────────────┴─────────────┴─────────────┘ │

│ Gossip通信 │

└─────────────────────────────────────────────────────────────┘

```

  1. 核心概念

概念 说明

Slot 16384个槽位,映射到不同节点

Gossip 节点间通信协议(MEET/PING/PONG)

Failover 主节点宕机,从节点升主

MOVED 客户端重定向到正确节点

ASK 临时重定向(迁移中)


二、完整代码实现

  1. 基础数据结构

```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;

```

  1. 槽位管理

```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;

}

```

  1. 节点发现(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;

}

```

  1. 故障检测

```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;

}

```

  1. 数据操作

```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;

}

```

  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);

// 种子节点会广播到其他节点

}

```

  1. 测试代码

```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的理解~