cache教程 4.一致性哈希(hash)

 本章节是单节点走向分布式节点的一个重要部分。

  • 一致性哈希(consistent hashing)的原理以及为什么要使用一致性哈希。
  • 实现一致性哈希代码,添加相应的测试用例 

 1.多节点部署遇到的问题

上一章节完成了一个单节点的缓存服务器。那对于一个单节点来说,读写缓存都是针对同一个节点的,那应该是不会出错的。

该对哪个节点进行访问?

而当我们的缓存服务器节点变多后,需要访问那个节点,需要写缓存到哪个节点?

比如:我们插入一条缓存数据groupName=scores,key=tom时,可以随机找一个结点A来插入,但当想要访问该缓存数据时候,我们要怎样才能定位到节点A

这就引出了哈希算法。

比如当前有10个节点,给每个节点进行编号,0,1,2,3....9。对存入的key进行hash运算再%10,拿到一个值,假如是2,那就把该数据存储到编号是2的节点上。而当需要方位该key时候,也进行hash运算再%10,就可以得到编号是2,就去访问编号为2的结点即可。

哈希算法的缺陷--分布式节点数量变更

简单求取 Hash 值解决了缓存性能的问题,但是没有考虑节点数量变化的场景。假设,移除了其中一台节点,只剩下 9 个,那么之前 hash(key) % 10 变成了 hash(key) % 9,也就意味着几乎缓存值对应的节点都发生了改变。即几乎所有的缓存值都失效了。节点在接收到对应的请求时,均需要重新去数据源获取数据,容易出大问题。

这时候,就要出动一致性哈希算法。

2.一致性哈希算法

一致性哈希是将整个哈希值空间组织成一个虚拟的圆环。其将 key 映射到 2^32 的空间中,将这个数字首尾相连,形成一个环。整个空间按顺时针方向组织,0和2^32-1在零点中方向重合。

该算法的两个步骤:

  1. 把服务器按照IP或主机名作为关键字进行哈希,这样就能确定其在哈希环的位置。
  2. 然后计算 key 的哈希值,放置在环上,顺时针寻找到的第一个节点,就是应选取的节点/机器。

 图片来自极客兔兔

 环上有 peer2,peer4,peer6 三个节点,按照顺时针,key11key2key27 均映射到 peer2,key23 映射到 peer4。

节点数量减少或扩张的情况分析 

如果新增节点 peer8,假设它新增位置如图所示,那么只有 key27 从 peer2 调整到 peer8,其余的映射均没有发生改变。

也就是说,一致性哈希算法,在新增/删除节点时,只需要重新定位该节点附近的一小部分数据,而不需要重新定位所有的节点,具有较好的容错性和可扩展性,可以较好解决分布式节点数量的变更问题。

数据倾斜问题,可用虚拟节点

如果节点较少容易出现节点分布不均衡造成数据倾斜问题。例如下图左边中的 节点1和节点2的例子。大部分的 key 都会被分配给 节点2,key 过度向 节点2 倾斜,缓存节点间负载不均。

为了解决数据存储不平衡的问题,一致性哈希算法引入了虚拟节点机制,即对每个节点计算多个哈希值,每个计算结果位置都放置在对应节点中,这些节点称为虚拟节点

具体做法可以在服务器IP或主机名的后面增加编号来实现,例如下图的情况,一共两个节点,缓存节点间负载不均。可以为每个服务节点增加三个虚拟节点,于是可以分为节点1#A、节点1#B、节点1#B,具体位置如下图所示:

 具体的步骤:

  • 第一步,计算虚拟节点的 Hash 值,放置在环上。
  • 第二步,计算 key 的 Hash 值,在环上顺时针寻找到应选取的虚拟节点,例如是 节点2#B,那么就对应真实的节点2。

虚拟节点扩充了节点的数量,解决了节点较少的情况下数据容易倾斜的问题。而且代价很小,只需要增加一个字典(map)维护真实节点与虚拟节点的映射关系即可。

3.一致性哈希代码实现

type HashFunc func(data []byte) uint32

// 定义哈希环
type HashRing struct {
	hashFunc HashFunc       //定义的哈希算法
	replicas int            //虚拟节点的数量
	keys     []int          //排序好的哈希环
	hashMap  map[int]string //虚拟节点与真实节点的映射关系,key是虚拟节点的哈希值,value是真实节点名称
}

func NewHash(replicas int, fn HashFunc) *HashRing {
	h := &HashRing{
		replicas: replicas,
		hashFunc: fn,
		hashMap:  make(map[int]string),
	}
	if h.hashFunc == nil {
		h.hashFunc = crc32.ChecksumIEEE
	}
	return h
}
  • 定义了函数类型 HashRing,采取依赖注入的方式,允许用于替换成自定义的 Hash 函数,也方便测试时替换,默认为 crc32.ChecksumIEEE 算法。
  • Map 是一致性哈希算法的主数据结构,包含 4 个成员变量:Hash 函数 hashFunc;虚拟节点倍数 replicas;哈希环 keys;虚拟节点与真实节点的映射表 hashMap,键是虚拟节点的哈希值,值是真实节点的名称。

 添加真实节点/机器的 Add() 方法

func (h *HashRing) Add(realNodeName ...string) {
	for _, name := range realNodeName {
		for i := 0; i < h.replicas; i++ {
			hash := int(h.hashFunc([]byte(strconv.Itoa(i) + name)))
			h.keys = append(h.keys, hash)
			h.hashMap[hash] = name
		}
	}
	sort.Ints(h.keys)
}
  • Add 函数允许传入 0 或 多个真实节点的名称。
  • 对每一个真实节点 name,对应创建 h.replicas 个虚拟节点,虚拟节点的名称是:strconv.Itoa(i) + name,即通过添加编号的方式区分不同虚拟节点。比如当前i是1,name是"2",那其编号就是"12"。
  • 使用 h.hash() 计算虚拟节点的哈希值,之后添加到环h.keys上。
  • 在 hashMap 中增加虚拟节点和真实节点的映射关系。
  • 最后一步,环上的哈希值排序。

 选择节点的 Get() 方法

// 选择节点
func (h *HashRing) Get(key string) string {
	if len(h.keys) == 0 {
		return ""
	}

	hash := int(h.hashFunc([]byte(key)))

	idx := sort.Search(len(h.keys), func(i int) bool {
		return h.keys[i] >= hash
	})

	return h.hashMap[h.keys[idx%len(h.keys)]]
}
  • 第一,计算 key 的哈希值。
  • 第二步,顺时针找到第一个匹配的虚拟节点的下标 idx,从 h.keys 中获取到对应的哈希值。如果 idx == len(m.keys),说明应选择 m.keys[0],因为 m.keys 是一个环状结构,所以用取余数的方式来处理这种情况。
  • 第三步,通过 hashMap 映射得到真实的节点。

4. 测试

在consistenthash_test.go文件中。

如果要进行测试,那么我们需要明确地知道每一个传入的 key 的哈希值,那使用默认的 crc32.ChecksumIEEE 算法显然达不到目的。所以在这里使用了自定义的 Hash 算法。自定义的 Hash 算法只处理数字,传入字符串表示的数字,返回对应的数字即可。

func TestHashing(t *testing.T) {
	//创建哈希环,每个真实节点有三个虚拟节点
	hash := NewHash(2, func(key []byte) uint32 {
		i, _ := strconv.Atoi(string(key))
		return uint32(i)
	})

	//添加3个真实节点,哈希函数后,
	//"2"对应的虚拟节点是2/12/22,4的是/4/14/24
	hash.Add("2", "4")

	//map的key是缓存数据key,value是真实节点
	testCases := map[string]string{
		"4":  "4",
		"11": "2",
		"16": "2",
		"27": "2",
	}

	for k, v := range testCases {
		if hash.Get(k) != v {
			t.Errorf("Asking for %s, should have yielded %s", k, v)
		}
	}
	//添加真实节点"8",其对应的虚拟节点是8/18
	hash.Add("8")

	testCases["16"] = "8"
	for k, v := range testCases {
		if hash.Get(k) != v {
			t.Errorf("Asking for %s, should have yielded %s", k, v)
		}
	}
}

测试代码中对应的哈希环如图

添加了真实节点"2","4"后,虚拟节点如图左边所示,要访问的key经过哈希运算后,为了简单就还是原来的值,那key的分布也就如图左边所示啦。

测试用例就是通过key去找到真实的节点。在添加真实节点"8"前,key为4对应的虚拟节点是4,那真实节点是4,依次类推,可自行测试。

完整代码:https://github.com/liwook/Go-projects/tree/main/go-cache/4-consistent-hash

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

L1-031:到底是不是太胖了

题目描述 据说一个人的标准体重应该是其身高&#xff08;单位&#xff1a;厘米&#xff09;减去100、再乘以0.9所得到的公斤数。真实体重与标准体重误差在10%以内都是完美身材&#xff08;即 | 真实体重 − 标准体重 | < 标准体重10%&#xff09;。已知 1 公斤等于 2 市斤。…

CSS入门(样式表|class类|选择器|背景|!important|文本颜色|字体|注释)

为什么学习CSS&#xff0c;因为QSS vs CSS 相似度极高&#xff0c;学好CSS对于QSS和QML都有潜移默化的作用。记住不管学习什么&#xff0c;我们都在围绕Qt集成。 入门 介绍 CSS 功能丰富&#xff0c;不仅仅是布局页面 外部样式表 <link> 在给定的HTML代码中&#xff…

文章解读与仿真程序复现思路——电力系统自动化EI\CSCD\北大核心《考虑移动式储能调度的配电网灾后多源协同孤岛运行策略》

这篇文章的标题表明研究的主题是在配电网发生灾害后&#xff0c;采用一种策略来实现多源协同孤岛运行&#xff0c;并在这个过程中特别考虑了移动式储能的调度。 让我们逐步解读标题的关键词&#xff1a; 考虑移动式储能调度&#xff1a; 文章关注的焦点之一是移动式储能系统的…

neuq-acm预备队训练week 8 P4779 【模板】单源最短路径(标准版)

题目背景 题目限制 题目描述 给定一个 n 个点&#xff0c;m 条有向边的带非负权图&#xff0c;请你计算从 s 出发&#xff0c;到每个点的距离。 数据保证你能从 s 出发到任意点。 输入格式 第一行为三个正整数n,m,s。 第二行起 m 行&#xff0c;每行三个非负整数 ui​,vi​…

2023年【广东省安全员C证第四批(专职安全生产管理人员)】考试总结及广东省安全员C证第四批(专职安全生产管理人员)复审考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年【广东省安全员C证第四批&#xff08;专职安全生产管理人员&#xff09;】考试总结及广东省安全员C证第四批&#xff08;专职安全生产管理人员&#xff09;复审考试&#xff0c;包含广东省安全员C证第四批&…

File has been changed outside the editor, reload?

编译keil工程&#xff0c;一直提示&#xff1a;该文件在编译器之外被修改&#xff0c;是否重新加载。 解决办法&#xff1a; 关闭.map后缀的文件即可&#xff0c;然后重新build/rebulid可以发现不会重新弹出该错误。

完整方案开放下载!详解中国移动《通信网络中量子计算应用研究报告》

8月30日&#xff0c;中国移动在第四届科技周暨战略性新兴产业共创发展大会上重磅发布了《通信网络中量子计算应用研究报告》。 玻色量子作为中国移动在光量子计算领域的唯一一家合作企业兼战投企业&#xff0c;在量子计算应用于通信行业达成了深入合作&#xff0c;并在5G天线多…

Docker部署redis单节点

【百炼成魔】docker部署redis单节点 环境准备 关闭防火墙 systemctl stop firewalld && systemctl disable firewalldsetenforce 0 && sed -i s/SELINUX.*/SELINUXdisabled/g /etc/selinux/config安装常用软件 yum install -y wget net-tools bash-compl…

[Linux] 用LNMP网站框架搭建论坛

一、nginx在其中工作原理 原理&#xff1a; php-fpm.conf是控制php-fpm守护进程 它是php.ini是一个php解析器 工作过程&#xff1a; 1.当客户端通过域名请求访问时&#xff0c;Nginx会找到对应的虚拟主机 2. Nginx将确定请求。 对于静态请求&#xff0c;Nginx会自行处理…

C++-引用和指针区别

文章目录 1.变量的组成2.指针2.1 定义2.2 使用指针操作变量2.3 为什么使用指针 3.引用3.1 定义3.2 引用注意事项 4.引用和指针的区别 1.变量的组成 变量的组成&#xff1a;变量地址&#xff0c;变量名&#xff0c;变量值 例&#xff1a; int i 12;2.指针 2.1 定义 指针用于存…

高效利用内存资源之动态内存管理详解

目录 一、为什么存在动态内存分配 二、动态内存函数的介绍 2.1malloc 2.2free 2.3calloc 2.4realloc 三、常见的动态内存错误 3.1对NULL指针的解引用操作 3.2对动态开辟空间的越界访问 3.3对非动态开辟内存使用free释放 3.4使用free释放一块动态开辟内存的一部分 3.…

普冉(PUYA)单片机开发笔记(7): ADC-轮询式多路采样

概述 应用中经常会有使用单片机进行模数转换的需求。PY32F003 具有 1 个 12 位的模拟数字转换器&#xff08;ADC&#xff09;&#xff0c;今天我们一起来使用一下这个 ADC。 数据手册中对 ADC 简介如下。 SAR ADC&#xff1a;逐次逼近式 ADC&#xff0c;原理参见“参考链接&a…

Weblogic-wls-wsat-unserialize_CVE-2017-10271

文章目录 Weblogic < 10.3.6 wls-wsat XMLDecoder 反序列化漏洞1. 漏洞描述2. 漏洞复现2.1 环境启动2.2 漏洞扫描2.3 漏洞验证 3. 修复建议 Weblogic < 10.3.6 ‘wls-wsat’ XMLDecoder 反序列化漏洞 1. 漏洞描述 说明内容漏洞编号CVE-2017-10271漏洞名称Weblogic <…

手机搭建kali

kali是著名的黑客专用系统&#xff0c;一般都是直接装在物理机或者虚拟机上&#xff0c;我们可以尝试把kali安装在手机上&#xff0c;把手机打造成一个便携式渗透神器。 我们需要下载以下3款软件&#xff1a; (1).Termux(终端模拟器) (2).AnLinux(里边有各种安装liunx的命令…

[架构之路-261]:目标系统 - 设计方法 - 软件工程 - 软件设计 - 架构设计 - 网络数据交换格式

一、网络数据交换格式 1.1 什么是网络数据交换格式 网络数据交换格式指的是在计算机网络中传输和存储数据时所采用的特定格式。 它定义了数据的组织方式、结构和编码规则&#xff0c;以便不同系统和应用程序之间能够准确地解析和处理数据。 网络数据交换格式的主要目的是&a…

内存映射机制

什么是内存映射 Linux通过将一个虚拟内存区域与一个磁盘上的对象关联起来&#xff0c;以初始化这个虚拟区域的内如&#xff0c;这个过程称为内存映射。 代码示例&#xff1a; /******************************************************************** > File Name: mmap…

java--StringBuilder、StringBuffer、StringJoiner

1.StringBuilder ①StringBuilder代表可变字符串对象&#xff0c;相当于是一个容器&#xff0c;它里面装的字符串是可以改变的&#xff0c;就是用来操作字符串的。 ②好处&#xff1a;StringBuilder比String更适合做字符串的修改操作&#xff0c;效率会比更高&#xff0c;代码…

Vue prop 子组件 给 父组件 使用sync传值 双向数据绑定

父传子 Vue prop组件间通信&#xff08;父传子&#xff09; 父组件 <User :name"userName" />data() {return {userName: 生产队的驴}}子组件 <span>用户名&#xff1a;{{name}}</span><button click"alter">点击给父组件传值&…

IDEA启动应用时报错:错误: 找不到或无法加载主类 @C:\Users\xxx\AppData\Local\Temp\idea_arg_filexxx

IDEA启动应用时报错&#xff0c;详细错误消息如下&#xff1a; C:\devel\jdk1.8.0_201\bin\java.exe -agentlib:jdwptransportdt_socket,address127.0.0.1:65267,suspendy,servern -XX:TieredStopAtLevel1 -noverify -Dspring.output.ansi.enabledalways -Dcom.sun.management…

【3DsMax】制作简单的骨骼动画

效果 步骤 首先准备4个板子模型展开放置好 添加一个4段的骨骼 选中其中的一块板子添加蒙皮命令 在蒙皮的参数面板中&#xff0c;设置每块板子对应哪块骨骼 设置好后你可以发现此时就已经可以通过骨骼来控制模型了 接下来就可以制作动画 点击左下角“时间配置”按钮 设置一下动…