手写一个一致性哈希:从原理到分布式缓存实战

news2026/4/29 10:46:00
前言你有没有想过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等系统的核心设计。下一篇预告《从布隆过滤器到布谷鸟过滤器更优的判重方案》---评论区分享一下你在哪用到了一致性哈希

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2565314.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…