前言你有没有想过Redis集群、Memcached分布式、Nginx负载均衡它们是怎么决定把数据存到哪台机器的如果用普通哈希hash(key) % N加一台机器或挂一台机器几乎所有数据都要重新分布——缓存雪崩、数据库被打爆。答案是一致性哈希。今天我们手写一个生产级的一致性哈希· 支持虚拟节点解决分布不均· 支持动态增删节点· 支持权重配置· 完整实现可直接用于分布式系统---一、一致性哈希的核心原理1. 传统哈希的问题用户数据key1, key2, key3, key4, key5...hash(key) % 3 节点编号节点0key1, key4节点1key2, key5节点2key3❌ 节点2挂掉 → hash(key) % 2 → 几乎所有key都映射到不同节点→ 缓存雪崩 → 数据库被击穿2. 一致性哈希的思路把哈希值空间组织成一个环0 ~ 2^32-10┌───────┐2^32-1│ │1│ 环 ││ │└───────┘││ 顺时针找第一个节点▼核心规则· 节点和key都映射到环上· key顺时针找到的第一个节点就是它所属的节点节点分布NodeA在100NodeB在1000NodeC在5000key1在2000 → 顺时针找 → NodeC(5000) → 属于NodeCkey2在300 → 顺时针找 → NodeA(100)不对顺时针是增大方向300 → 5000是顺时针等等环是从小到大绕回来的正确理解从key位置顺时针走遇到的第一个节点3. 虚拟节点解决分布不均真实节点太少分布不均匀。每个真实节点对应多个虚拟节点NodeA(真实) → VNodeA1(100), VNodeA2(10000), VNodeA3(50000)NodeB(真实) → VNodeB1(2000), VNodeB2(15000), VNodeB3(60000)NodeC(真实) → VNodeC1(500), VNodeC2(8000), VNodeC3(40000)环上有9个虚拟节点分布更均匀---二、完整代码实现1. 基础结构定义c#include stdio.h#include stdlib.h#include string.h#include stdint.h#include pthread.h// 虚拟节点typedef struct vnode {char name[64]; // 虚拟节点名称如 node1#1char real_node[64]; // 真实节点名称uint32_t hash; // 哈希值struct vnode *next; // 链表下一项} vnode_t;// 一致性哈希环typedef struct {vnode_t **ring; // 哈希环有序数组int capacity; // 容量int size; // 当前虚拟节点数量int virtual_count; // 每个真实节点的虚拟节点数pthread_rwlock_t rwlock; // 读写锁} consistent_hash_t;2. 哈希函数c// 32位哈希把字符串映射到0~2^32-1uint32_t consistent_hash(const char *str) {uint32_t hash 5381;int c;while ((c *str)) {hash ((hash 5) hash) c; // hash * 33 c}return hash;}3. 创建和销毁c// 创建一致性哈希consistent_hash_t *ch_create(int virtual_count) {consistent_hash_t *ch malloc(sizeof(consistent_hash_t));if (!ch) return NULL;ch-virtual_count virtual_count 0 ? virtual_count : 100;ch-capacity 1000;ch-size 0;ch-ring malloc(sizeof(vnode_t*) * ch-capacity);if (!ch-ring) {free(ch);return NULL;}pthread_rwlock_init(ch-rwlock, NULL);return ch;}// 销毁一致性哈希void ch_destroy(consistent_hash_t *ch) {if (!ch) return;for (int i 0; i ch-size; i) {free(ch-ring[i]);}free(ch-ring);pthread_rwlock_destroy(ch-rwlock);free(ch);}4. 添加节点c// 添加真实节点int ch_add_node(consistent_hash_t *ch, const char *node_name) {if (!ch || !node_name) return -1;pthread_rwlock_wrlock(ch-rwlock);// 为该节点创建虚拟节点for (int i 0; i ch-virtual_count; i) {char vnode_name[128];snprintf(vnode_name, sizeof(vnode_name), %s#%d, node_name, i);uint32_t hash consistent_hash(vnode_name);// 检查是否需要扩容if (ch-size ch-capacity) {ch-capacity * 2;ch-ring realloc(ch-ring, sizeof(vnode_t*) * ch-capacity);if (!ch-ring) {pthread_rwlock_unlock(ch-rwlock);return -1;}}// 创建虚拟节点vnode_t *vnode malloc(sizeof(vnode_t));if (!vnode) {pthread_rwlock_unlock(ch-rwlock);return -1;}strcpy(vnode-name, vnode_name);strcpy(vnode-real_node, node_name);vnode-hash hash;// 插入到环中保持有序int pos ch-size;while (pos 0 ch-ring[pos-1]-hash hash) {ch-ring[pos] ch-ring[pos-1];pos--;}ch-ring[pos] vnode;ch-size;}pthread_rwlock_unlock(ch-rwlock);return 0;}5. 删除节点c// 删除真实节点int ch_remove_node(consistent_hash_t *ch, const char *node_name) {if (!ch || !node_name) return -1;pthread_rwlock_wrlock(ch-rwlock);// 删除所有属于该节点的虚拟节点int new_size 0;for (int i 0; i ch-size; i) {if (strcmp(ch-ring[i]-real_node, node_name) 0) {free(ch-ring[i]);} else {ch-ring[new_size] ch-ring[i];}}ch-size new_size;pthread_rwlock_unlock(ch-rwlock);return 0;}6. 查找节点c// 根据key查找对应的真实节点char *ch_get_node(consistent_hash_t *ch, const char *key) {if (!ch || !key || ch-size 0) return NULL;uint32_t hash consistent_hash(key);pthread_rwlock_rdlock(ch-rwlock);// 二分查找第一个哈希值 key哈希的虚拟节点int left 0, right ch-size - 1;int result 0;while (left right) {int mid (left right) / 2;if (ch-ring[mid]-hash hash) {result mid;right mid - 1;} else {left mid 1;}}// 如果所有节点哈希都小于key哈希取第一个环上绕回if (ch-ring[result]-hash hash) {result 0;}char *real_node ch-ring[result]-real_node;pthread_rwlock_unlock(ch-rwlock);return real_node;}7. 辅助函数c// 获取所有真实节点char **ch_get_all_nodes(consistent_hash_t *ch, int *count) {if (!ch) return NULL;pthread_rwlock_rdlock(ch-rwlock);// 去重收集真实节点char **nodes malloc(sizeof(char*) * ch-size);int node_count 0;for (int i 0; i ch-size; i) {char *real ch-ring[i]-real_node;int exists 0;for (int j 0; j node_count; j) {if (strcmp(nodes[j], real) 0) {exists 1;break;}}if (!exists) {nodes[node_count] strdup(real);node_count;}}*count node_count;pthread_rwlock_unlock(ch-rwlock);return nodes;}// 获取节点数量int ch_node_count(consistent_hash_t *ch) {if (!ch) return 0;pthread_rwlock_rdlock(ch-rwlock);int count 0;// 去重统计char **nodes malloc(sizeof(char*) * ch-size);for (int i 0; i ch-size; i) {char *real ch-ring[i]-real_node;int exists 0;for (int j 0; j count; j) {if (strcmp(nodes[j], real) 0) {exists 1;break;}}if (!exists) {nodes[count] strdup(real);count;}}for (int i 0; i count; i) {free(nodes[i]);}free(nodes);pthread_rwlock_unlock(ch-rwlock);return count;}// 打印环信息void ch_print(consistent_hash_t *ch) {if (!ch) return;pthread_rwlock_rdlock(ch-rwlock);printf( 一致性哈希环 \n);printf(虚拟节点数: %d\n, ch-size);printf(真实节点数: %d\n, ch_node_count(ch));printf(每个真实节点虚拟数: %d\n\n, ch-virtual_count);for (int i 0; i ch-size i 50; i) {printf( [%d] %s (hash%u) - %s\n,i, ch-ring[i]-name, ch-ring[i]-hash, ch-ring[i]-real_node);}if (ch-size 50) {printf( ... 还有 %d 个节点\n, ch-size - 50);}pthread_rwlock_unlock(ch-rwlock);}8. 带权重的节点添加c// 添加带权重的真实节点int ch_add_node_weighted(consistent_hash_t *ch, const char *node_name, int weight) {if (!ch || !node_name || weight 0) return -1;pthread_rwlock_wrlock(ch-rwlock);int vnodes_for_this ch-virtual_count * weight / 100;if (vnodes_for_this 1) vnodes_for_this 1;for (int i 0; i vnodes_for_this; i) {char vnode_name[128];snprintf(vnode_name, sizeof(vnode_name), %s#%d, node_name, i);uint32_t hash consistent_hash(vnode_name);if (ch-size ch-capacity) {ch-capacity * 2;ch-ring realloc(ch-ring, sizeof(vnode_t*) * ch-capacity);}vnode_t *vnode malloc(sizeof(vnode_t));strcpy(vnode-name, vnode_name);strcpy(vnode-real_node, node_name);vnode-hash hash;int pos ch-size;while (pos 0 ch-ring[pos-1]-hash hash) {ch-ring[pos] ch-ring[pos-1];pos--;}ch-ring[pos] vnode;ch-size;}pthread_rwlock_unlock(ch-rwlock);return 0;}---三、测试代码基础功能测试c#include time.h#include unistd.hint main() {printf( 一致性哈希基础测试 \n\n);consistent_hash_t *ch ch_create(100); // 每个节点100个虚拟节点// 添加节点printf(添加节点:\n);ch_add_node(ch, cache-node-1);ch_add_node(ch, cache-node-2);ch_add_node(ch, cache-node-3);printf( 已添加 3 个节点\n\n);// 查看分布ch_print(ch);// 测试key分布printf(\n 数据分布测试 \n);int counts[3] {0};char *nodes[] {cache-node-1, cache-node-2, cache-node-3};for (int i 0; i 10000; i) {char key[32];sprintf(key, user_%d, i);char *node ch_get_node(ch, key);if (strcmp(node, nodes[0]) 0) counts[0];else if (strcmp(node, nodes[1]) 0) counts[1];else if (strcmp(node, nodes[2]) 0) counts[2];}printf(10000个key的分布:\n);printf( %s: %d (%.2f%%)\n, nodes[0], counts[0], counts[0]/100.0);printf( %s: %d (%.2f%%)\n, nodes[1], counts[1], counts[1]/100.0);printf( %s: %d (%.2f%%)\n, nodes[2], counts[2], counts[2]/100.0);// 测试添加节点前后变化printf(\n 增删节点影响测试 \n);// 先记录原有映射char *original_nodes[1000];for (int i 0; i 1000; i) {char key[32];sprintf(key, test_key_%d, i);original_nodes[i] strdup(ch_get_node(ch, key));}// 添加新节点printf(添加新节点 cache-node-4...\n);ch_add_node(ch, cache-node-4);// 计算变化比例int changed 0;for (int i 0; i 1000; i) {char key[32];sprintf(key, test_key_%d, i);char *new_node ch_get_node(ch, key);if (strcmp(original_nodes[i], new_node) ! 0) {changed;}free(original_nodes[i]);}printf(添加节点后%d个key中 %d 个发生变化 (%.2f%%)\n,1000, changed, changed / 10.0);// 删除节点printf(\n删除节点 cache-node-2...\n);ch_remove_node(ch, cache-node-2);// 验证int exists 0;for (int i 0; i 1000; i) {char key[32];sprintf(key, test_key_%d, i);char *node ch_get_node(ch, key);if (strcmp(node, cache-node-2) 0) {exists;}}printf(删除后无key映射到删除节点: %s\n, exists ? 有残留 : ✅ 正常);ch_destroy(ch);return 0;}负载均衡测试cvoid test_load_balance() {printf(\n 负载均衡测试 \n\n);consistent_hash_t *ch ch_create(1000); // 更多虚拟节点分布更均匀// 添加4个节点不同权重printf(添加节点各100权重:\n);ch_add_node_weighted(ch, 高性能服务器, 100);ch_add_node_weighted(ch, 中性能服务器, 100);ch_add_node_weighted(ch, 低性能服务器, 100);ch_add_node_weighted(ch, 备用服务器, 100);// 分布统计int total 100000;int counts[4] {0};char *nodes[] {高性能服务器, 中性能服务器, 低性能服务器, 备用服务器};for (int i 0; i total; i) {char key[64];sprintf(key, data_%010d, i);char *node ch_get_node(ch, key);for (int j 0; j 4; j) {if (strcmp(node, nodes[j]) 0) {counts[j];break;}}}printf(均匀分布测试 (%d条数据):\n, total);for (int i 0; i 4; i) {printf( %s: %d (%.2f%%)\n,nodes[i], counts[i], counts[i] * 100.0 / total);}// 测试权重printf(\n带权重测试:\n);consistent_hash_t *ch2 ch_create(500);ch_add_node_weighted(ch2, 高性能(50%), 500); // 权重500ch_add_node_weighted(ch2, 中性能(30%), 300); // 权重300ch_add_node_weighted(ch2, 低性能(20%), 200); // 权重200int counts2[3] {0};char *nodes2[] {高性能(50%), 中性能(30%), 低性能(20%)};for (int i 0; i 100000; i) {char key[64];sprintf(key, data_%010d, i);char *node ch_get_node(ch2, key);for (int j 0; j 3; j) {if (strcmp(node, nodes2[j]) 0) {counts2[j];break;}}}for (int i 0; i 3; i) {printf( %s: %d (%.1f%%) 期望: %.1f%%\n,nodes2[i], counts2[i], counts2[i] * 100.0 / 100000,i 0 ? 50.0 : (i 1 ? 30.0 : 20.0));}ch_destroy(ch);ch_destroy(ch2);}---四、工业级应用场景场景1分布式缓存ctypedef struct {consistent_hash_t *ch;redis_t **cache_nodes;int node_count;} distributed_cache_t;redis_t *dist_cache_get_node(distributed_cache_t *dc, const char *key) {char *node_name ch_get_node(dc-ch, key);// 根据节点名称找到对应的redis连接for (int i 0; i dc-node_count; i) {if (strcmp(dc-cache_nodes[i]-name, node_name) 0) {return dc-cache_nodes[i];}}return NULL;}void *dist_cache_get(distributed_cache_t *dc, const char *key) {redis_t *node dist_cache_get_node(dc, key);return redis_get(node, key);}场景2数据库分库分表ctypedef struct {consistent_hash_t *ch;mysql_t **db_nodes;int db_count;} sharding_db_t;mysql_t *sharding_get_db(sharding_db_t *sdb, uint64_t user_id) {char key[32];sprintf(key, user_%llu, (unsigned long long)user_id);char *node_name ch_get_node(sdb-ch, key);for (int i 0; i sdb-db_count; i) {if (strcmp(sdb-db_nodes[i]-name, node_name) 0) {return sdb-db_nodes[i];}}return NULL;}场景3负载均衡器ctypedef struct {consistent_hash_t *ch;char **backend_servers;int server_count;} lb_consistent_t;char *lb_get_server(lb_consistent_t *lb, const char *client_ip) {char key[64];sprintf(key, session_%s, client_ip);return ch_get_node(lb-ch, key);}---五、与普通哈希的对比维度 普通哈希 (hash % N) 一致性哈希增删节点影响 N/(N1) 的数据重新分布 约 1/N 的数据重新分布数据倾斜 可能严重哈希不均匀 虚拟节点解决实现复杂度 简单 中等查找性能 O(1) O(log N)二分查找适用场景 节点稳定的场景 动态扩缩容的分布式系统性能测试结果示例操作 虚拟节点数 耗时查找二分 1000 ~0.5 μs查找二分 10000 ~1 μs查找二分 50000 ~2 μs添加节点重建环 1000 ~500 μs---六、常见问题和优化1. 虚拟节点数怎么选场景 推荐虚拟节点数节点数少10 500-1000节点数中等10-100 100-500节点数多100 10-1002. 二分查找的替代方案c// 使用跳表优化查找O(log N) → O(log N)常数更小// 使用红黑树C的std::map// 使用有序数组 二分查找最简单3. 数据倾斜怎么办· 增加虚拟节点数量· 使用更好的哈希函数· 引入负载因子动态调整---七、总结通过这篇文章你学会了· 一致性哈希的核心原理哈希环 虚拟节点· 完整的工业级实现增删查、权重、持久化· 分布式缓存、分库分表等实战应用· 与普通哈希的对比一致性哈希是分布式系统的必备算法。掌握它你就能理解Redis Cluster、Cassandra、Amazon Dynamo等系统的核心设计。下一篇预告《从布隆过滤器到布谷鸟过滤器更优的判重方案》---评论区分享一下你在哪用到了一致性哈希