golang分布式缓存项目 Day4 一致性哈希
注:该项目原作者:https://geektutu.com/post/geecache-day1.html。本文旨在记录本人做该项目时的一些疑惑解答以及部分的测试样例以便于本人复习
为什么使用一致性哈希
我该访问谁
对于分布式缓存来说,当一个节点接收到请求,如果该节点并没有存储缓存值,那么它面临的难题是,从谁那获取数据?自己,还是节点1, 2, 3, 4… 。假设包括自己在内一共有 10 个节点,当一个节点接收到请求时,随机选择一个节点,由该节点从数据源获取数据。
假设第一次随机选取了节点 1 ,节点 1 从数据源获取到数据的同时缓存该数据;那第二次,只有 1/10 的可能性再次选择节点 1, 有 9/10 的概率选择了其他节点,如果选择了其他节点,就意味着需要再一次从数据源获取数据,一般来说,这个操作是很耗时的。这样做,一是缓存效率低,二是各个节点上存储着相同的数据,浪费了大量的存储空间。
那有什么办法,对于给定的 key,每一次都选择同一个节点呢?使用 hash 算法也能够做到这一点。那把 key 的每一个字符的 ASCII 码加起来,再除以 10 取余数可以吗?当然可以,这可以认为是自定义的 hash 算法。
在这里插入图片描述
从上面的图可以看到,任意一个节点任意时刻请求查找键 Tom 对应的值,都会分配给节点 2,有效地解决了上述的问题。
节点数量变化了怎么办?
简单求取 Hash 值解决了缓存性能的问题,但是没有考虑节点数量变化的场景。假设,移除了其中一台节点,只剩下 9 个,那么之前 hash(key) % 10 变成了 hash(key) % 9,也就意味着几乎缓存值对应的节点都发生了改变。即几乎所有的缓存值都失效了。节点在接收到对应的请求时,均需要重新去数据源获取数据,容易引起 缓存雪崩。
那如何解决这个问题呢?一致性哈希算法可以。
算法原理
步骤
一致性哈希算法将 key 映射到 2^32 的空间中,将这个数字首尾相连,形成一个环。
- 计算节点/机器(通常使用节点的名称、编号和 IP 地址)的哈希值,放置在环上。
- 计算 key 的哈希值,放置在环上,顺时针寻找到的第一个节点,就是应选取的节点/机器。
PS:下面的图片是哈希环。其中key11, key2…在环上的位置是由其哈希值(由一个特殊函数算法)决定的,每一个key所用的节点是在环上顺时间取到的第一个节点peer
环上有 peer2,peer4,peer6 三个节点,key11,key2,key27 均映射到 peer2,key23 映射到 peer4。此时,如果新增节点/机器 peer8,假设它新增位置如图所示,那么只有 key27 从 peer2 调整到 peer8,其余的映射均没有发生改变。
也就是说,一致性哈希算法,在新增/删除节点时,只需要重新定位该节点附近的一小部分数据,而不需要重新定位所有的节点,这就解决了上述的问题
数据倾斜问题
如果服务器的节点过少,容易引起 key 的倾斜。例如上面例子中的 peer2,peer4,peer6 分布在环的上半部分,下半部分是空的。那么映射到环下半部分的 key 都会被分配给 peer2,key 过度向 peer2 倾斜,缓存节点间负载不均。
为了解决这个问题,引入了虚拟节点的概念,一个真实节点对应多个虚拟节点。
假设 1 个真实节点对应 3 个虚拟节点,那么 peer1 对应的虚拟节点是 peer1-1、 peer1-2、 peer1-3(通常以添加编号的方式实现),其余节点也以相同的方式操作。
- 第一步,计算虚拟节点的 Hash 值,放置在环上。
- 第二步,计算 key 的 Hash 值,在环上顺时针寻找到应选取的虚拟节点,例如是 peer2-1,那么就对应真实节点 peer2。
虚拟节点扩充了节点的数量,解决了节点较少的情况下数据容易倾斜的问题。而且代价非常小,只需要增加一个字典(map)维护真实节点与虚拟节点的映射关系即可。
Go语言实现
我们在 geecache 目录下新建 package consistenthash,用来实现一致性哈希算法。
package consistenthashimport ("hash/crc32""sort""strconv"
)// Hash maps bytes to uint32
type HashFunc func(data []byte) uint32// Map constains all hashed keys
type Map struct {hashFunc HashFuncreplicas intkeys []int // SortedhashMap map[int]string
}// New creates a Map instance
func New(replicas int, fn HashFunc) *Map {m := &Map{replicas: replicas,hashFunc: fn,hashMap: make(map[int]string),}if m.hash == nil {m.hashFunc = crc32.ChecksumIEEE//如果没有输入哈希函数,则使用默认哈希函数crc32.ChecksumIEEE}return m
}
-
定义了函数类型 Hash,采取依赖注入的方式,允许用于替换成自定义的 Hash 函数,也方便测试时替换,默认为
crc32.ChecksumIEEE 算法。 -
Map 是一致性哈希算法的主数据结构,包含 4 个成员变量:Hash 函数 hash;虚拟节点倍数 replicas;哈希环
keys;虚拟节点与真实节点的映射表 hashMap,键是虚拟节点的哈希值,值是真实节点的名称。 -
构造函数 New() 允许自定义虚拟节点倍数和 Hash 函数。
接下来,实现添加真实节点/机器的 Add() 方法。
// Add adds some keys to the hash.
func (m *Map) Add(keys ...string) {for _, key := range keys {for i := 0; i < m.replicas; i++ {hash := int(m.hashFunc([]byte(strconv.Itoa(i) + key)))//strconv.Itoa()函数用于将整数转换为字符串m.keys = append(m.keys, hash)m.hashMap[hash] = key}}sort.Ints(m.keys)
}
- Add 函数允许传入 0 或 多个真实节点的名称。
- 对每一个真实节点 key,对应创建 m.replicas 个虚拟节点,虚拟节点的名称是:strconv.Itoa(i) + key,即通过添加编号的方式区分不同虚拟节点.
- 使用 m.hash() 计算虚拟节点的哈希值,使用 append(m.keys, hash) 添加到环上。
- 在 hashMap 中增加虚拟节点和真实节点的映射关系。
- 最后一步,环上的哈希值排序。
最后一步,实现选择节点的 Get() 方法。
// Get gets the closest item in the hash to the provided key.
func (m *Map) Get(key string) string {if len(m.keys) == 0 {return ""}hash := int(m.hashFunc([]byte(key)))// Binary search for appropriate replica.idx := sort.Search(len(m.keys), func(i int) bool {return m.keys[i] >= hash})return m.hashMap[m.keys[idx%len(m.keys)]]
}
- 选择节点就非常简单了,第一步,计算 key 的哈希值。
- 第二步,顺时针找到第一个匹配的虚拟节点的下标 idx,从 m.keys 中获取到对应的哈希值。如果 idx == len(m.keys),说明应选择 m.keys[0],因为 m.keys 是一个环状结构,所以用取余数的方式来处理这种情况。
- 第三步,通过 hashMap 映射得到真实的节点。
sort.Search函数的用法:
函数签名
func Search(n int, f func(int) bool) int
参数
n:表示要搜索的集合的大小,通常是切片的长度。
f:是一个函数,它接受一个整数参数 i,并返回一个布尔值。这个函数定义了搜索的条件。
工作原理
sort.Search 会找到并返回 [0, n) 范围内,使得 f(i) 为 true 的最小索引 i。如果不存在这样的索引,Search 返回 n。Search 要求 f 对于输入范围 [0, n) 的某些(可能为空)前缀为 false,然后对(可能为空)余数为 true;Search 返回第一个 true 的索引。
常见用途
sort.Search 常用于在排序的、可索引的数据结构(例如数组或切片)中查找值 x 的索引 i。在这种情况下,参数 f(通常是闭包)捕获要搜索的值,以及数据结构的索引和排序方式。
示例代码
package mainimport ("fmt""sort"
)func main() {data := []int{1, 2, 3, 5, 10}target := 4index := sort.Search(len(data), func(i int) bool {return data[i] >= target})if index < len(data) && data[index] == target {// target is present at data[index]fmt.Printf("找到了不小于 %d 的元素,索引为 %d,值为 %d\n", target, index, data[index])} else {// target is not present in data,// but index is the index where it would be inserted.fmt.Printf("没有找到不小于 %d 的元素,索引为 %d\n", target, index)}
}
至此,整个一致性哈希算法就实现完成了。
测试
package consistenthashimport ("strconv""testing"
)func TestHashing(t *testing.T) {hash := New(3, func(key []byte) uint32 {i, _ := strconv.Atoi(string(key))return uint32(i)})// Given the above hash function, this will give replicas with "hashes":// 2, 4, 6, 12, 14, 16, 22, 24, 26hash.Add("6", "4", "2")testCases := map[string]string{"2": "2","11": "2","23": "4","27": "2",}for k, v := range testCases {if hash.Get(k) != v {t.Errorf("Asking for %s, should have yielded %s", k, v)}}// Adds 8, 18, 28hash.Add("8")// 27 should now map to 8.testCases["27"] = "8"for k, v := range testCases {if hash.Get(k) != v {t.Errorf("Asking for %s, should have yielded %s", k, v)}}}
如果要进行测试,那么我们需要明确地知道每一个传入的 key 的哈希值,那使用默认的 crc32.ChecksumIEEE 算法显然达不到目的。所以在这里使用了自定义的 Hash 算法。自定义的 Hash 算法只处理数字,传入字符串表示的数字,返回对应的数字即可。
- 一开始,有 2/4/6 三个真实节点,对应的虚拟节点的哈希值是 02/12/22、04/14/24、06/16/26。
- 那么用例 2/11/23/27 选择的虚拟节点分别是 02/12/24/02,也就是真实节点 2/2/4/2。
- 添加一个真实节点 8,对应虚拟节点的哈希值是 08/18/28,此时,用例 27 对应的虚拟节点从 02 变更为 28,即真实节点 8。
PS:为什么2/4/6 三个真实节点,对应的虚拟节点的哈希值是 02/12/22、04/14/24、06/16/26?
:因为我们将节点值先转换为string类型, 再转换成[]byte类型。在转换成string类型后加上i = 0, 1, 2后,是直接在数据前相加的,也就是"0" + “2” = “02”, “1” + “2” = “12”, “2” + “2” = “22”…
相关文章:

golang分布式缓存项目 Day4 一致性哈希
注:该项目原作者:https://geektutu.com/post/geecache-day1.html。本文旨在记录本人做该项目时的一些疑惑解答以及部分的测试样例以便于本人复习 为什么使用一致性哈希 我该访问谁 对于分布式缓存来说,当一个节点接收到请求,如…...

ARM 汇编指令
blr指令的基本概念和用途 在 ARM64 汇编中,blr是 “Branch with Link to Register” 的缩写。它是一种分支指令,主要用于跳转到一个由寄存器指定的地址,并将返回地址保存到链接寄存器(Link Register,LR)中。…...

打造个性化体验:在Axure中创建你的专属组件库
打造个性化体验:在Axure中创建你的专属组件库 在数字产品设计的浪潮中,效率和一致性是设计团队追求的两大圣杯。 随着项目的不断扩展,重复性的工作逐渐增多,设计师们开始寻找能够提高工作效率、保持设计一致性的解决方案。 而 …...

如何用WordPress和Shopify提升SEO表现?
选择合适的建站程序对于SEO优化非常重要。目前,WordPress和Shopify是两种备受推崇的建站平台,各有优势。 WordPress最大的优点是灵活性。它支持大量SEO插件,帮助你调整元标签、生成站点地图、优化内容结构等。这些功能让你能够轻松地提升网站…...

不泄密的安全远程控制软件需要哪些技术
在数字化浪潮中,远程控制软件已不再是简单的辅助工具,而是成为企业运作和日常工作中不可或缺的一部分。随着远程办公模式的广泛采纳,这些软件提供了一种既安全又高效的途径来管理和访问远端系统。无论是在家办公、技术支持还是远程教育&#…...

rust高级特征
文章目录 不安全的rust解引用裸指针裸指针与引用和智能指针的区别裸指针使用解引用运算符 *,这需要一个 unsafe 块调用不安全函数或方法在不安全的代码之上构建一个安全的抽象层 使用 extern 函数调用外部代码rust调用C语言函数rust接口被C语言程序调用 访问或修改可…...

STM32F407简单驱动步进电机(标准库)
配置 单片机型号:STM32F104ZGT6 步进电机:YK28HB40-01A 驱动器:YKD2204M-Plus 接线方式: pu:接对应的产生PWM的引脚,这里接PF9,对应TIM14_CH1通道! pu-:接单片机的G…...

使用热冻结数据层生命周期优化在 Elastic Cloud 中存储日志的成本
作者:来自 Elastic Jonathan Simon 收集数据对于可观察性和安全性至关重要,而确保数据能够快速搜索且获得低延迟结果对于有效管理和保护应用程序和基础设施至关重要。但是,存储所有这些数据会产生持续的存储成本,这为节省成本创造…...
LeetCode131. 分割回文串(2024冬季每日一题 4)
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。 示例 1: 输入:s “aab” 输出:[[“a”,“a”,“b”],[“aa”,“b”]] 示例 2: 输入:s “a…...

万字长文解读深度学习——训练(DeepSpeed、Accelerate)、优化(蒸馏、剪枝、量化)、部署细节
🌺历史文章列表🌺 深度学习——优化算法、激活函数、归一化、正则化深度学习——权重初始化、评估指标、梯度消失和梯度爆炸深度学习——前向传播与反向传播、神经网络(前馈神经网络与反馈神经网络)、常见算法概要汇总万字长文解读…...

STM32—独立看门狗(IWDG)和窗口看门狗(WWDG)
概述: WDG(Watchdog) 看门狗,看门狗可以监控程序的运行状态,当程序因为设计漏洞、硬件故障、电磁干扰等原因,出现卡死或跑飞现象时,看门狗能计时复位程序,避免程序陷入长时间的罢工状态,保证系…...

ks8 本地化部署 F5-TTS
huggingface上有一个demo可以打开就能玩 https://huggingface.co/spaces/mrfakename/E2-F5-TTS 上传了一段懂王的演讲片段,然后在 generate text框内填了点古诗词,生成后这语气这效果,离真懂王就差一个手风琴了。 F5-TTS 项目地址…...

Web组态大屏可视化编辑器
1、零代码、一键构建、一键下载 用户只需通过拖拉拽操作,即可在画布上添加、调整和排列各种设备组件、图表和控件。零代码拖拽方式让用户能够实时预览界面效果,直观地观察布局、样式和数据的变化。 2、实时展示,自动化连接数据,用…...

【comfyui教程】让模特换衣服,comfyui一键搞定!
前言 一键穿上别人的衣服?揭秘ComfyUI模特换装工作流! 你有没有想过,某天早晨你起床后,只需轻轻一点,就能穿上明星昨晚在红毯上的华丽礼服?这种听起来像是科幻电影的情节,如今通过ComfyUI模特…...
数据湖与数据仓库的区别
数据湖与数据仓库是两种不同的数据存储和管理方式,它们在多个方面存在显著的区别。以下是对数据湖与数据仓库区别的详细阐述: 一、数据存储方式 数据仓库 通常采用预定义的模式和结构来存储数据。数据在存储前通常经过清洗、转换和整合等处理࿰…...
golang分布式缓存项目 Day6 防止缓存击穿
该项目原作者:https://github.com/geektutu/7days-golang。本文旨在记录本人做该项目时的一些疑惑解答以及部分的测试样例以便于本人复习。 1 缓存雪崩、缓存击穿与缓存穿透 概念解析: 缓存雪崩:缓存在同一时刻全部失效,造成瞬…...

Redis高可用-主从复制
这里写目录标题 Redis主从复制主从复制过程环境搭建从节点配置常见问题主从模式缺点 Redis主从复制 虽然 Redis 可以实现单机的数据持久化,但无论是 RDB 也好或者 AOF 也好,都解决不了单点宕机问题,即一旦 redis 服务器本身出现系统故障、硬…...
Angular框架:构建现代Web应用的全面指南
文章目录 前言一、Angular简介二、Angular的核心特性三、Angular的应用场景四、Angular的发展趋势五、如何开始使用Angular结语 前言 在当今高度竞争的互联网环境中,构建高效、响应迅速且易于维护的Web应用成为企业成功的关键。Angular框架以其强大的功能、灵活的架…...

Golang | Leetcode Golang题解之第563题二叉树的坡度
题目: 题解: func findTilt(root *TreeNode) (ans int) {var dfs func(*TreeNode) intdfs func(node *TreeNode) int {if node nil {return 0}sumLeft : dfs(node.Left)sumRight : dfs(node.Right)ans abs(sumLeft - sumRight)return sumLeft sumRi…...

gdb编译教程(支持linux下X86和ARM架构)
1、下载源码 http://ftp.gnu.org/gnu/gdb/ 我下载的8.2版本。 2、下载完后拷贝到linux的x86系统。 3、解压,然后进入到目录下,打开当前目录的命令行窗口。 4、创建一个生成目录。 5、我们先开始x86版本,这个比较简单,不需要配置…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...
Qt Http Server模块功能及架构
Qt Http Server 是 Qt 6.0 中引入的一个新模块,它提供了一个轻量级的 HTTP 服务器实现,主要用于构建基于 HTTP 的应用程序和服务。 功能介绍: 主要功能 HTTP服务器功能: 支持 HTTP/1.1 协议 简单的请求/响应处理模型 支持 GET…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...

学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”
2025年#高考 将在近日拉开帷幕,#AI 监考一度冲上热搜。当AI深度融入高考,#时间同步 不再是辅助功能,而是决定AI监考系统成败的“生命线”。 AI亮相2025高考,40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕,江西、…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
全面解析数据库:从基础概念到前沿应用
在数字化时代,数据已成为企业和社会发展的核心资产,而数据库作为存储、管理和处理数据的关键工具,在各个领域发挥着举足轻重的作用。从电商平台的商品信息管理,到社交网络的用户数据存储,再到金融行业的交易记录处理&a…...

一些实用的chrome扩展0x01
简介 浏览器扩展程序有助于自动化任务、查找隐藏的漏洞、隐藏自身痕迹。以下列出了一些必备扩展程序,无论是测试应用程序、搜寻漏洞还是收集情报,它们都能提升工作流程。 FoxyProxy 代理管理工具,此扩展简化了使用代理(如 Burp…...
2.2.2 ASPICE的需求分析
ASPICE的需求分析是汽车软件开发过程中至关重要的一环,它涉及到对需求进行详细分析、验证和确认,以确保软件产品能够满足客户和用户的需求。在ASPICE中,需求分析的关键步骤包括: 需求细化:将从需求收集阶段获得的高层需…...

高端性能封装正在突破性能壁垒,其芯片集成技术助力人工智能革命。
2024 年,高端封装市场规模为 80 亿美元,预计到 2030 年将超过 280 亿美元,2024-2030 年复合年增长率为 23%。 细分到各个终端市场,最大的高端性能封装市场是“电信和基础设施”,2024 年该市场创造了超过 67% 的收入。…...
「Java基本语法」变量的使用
变量定义 变量是程序中存储数据的容器,用于保存可变的数据值。在Java中,变量必须先声明后使用,声明时需指定变量的数据类型和变量名。 语法 数据类型 变量名 [ 初始值]; 示例:声明与初始化 public class VariableDemo {publi…...