一致性哈希算法原理及代码实现「建议收藏」

一致性哈希算法原理及代码实现「建议收藏」一致性哈希安装goget-ugithub.com/junhaideng/consistent使用c:=consistent.New()ips:=[]string{“192.168.0.1″,”192.168.0.2″,”192.168.0.3″,”192.168.0.4”}for_,ip:=rangeips{c.Add(ip)}fmt.Println(“ip:”,c.Get(“/hello.txt”))背景在介绍一致性哈希之前,首先来看

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

一致性哈希

安装

go get -u github.com/junhaideng/consistent

使用

c := consistent.New()
ips := []string{ 
   "192.168.0.1", "192.168.0.2", "192.168.0.3", "192.168.0.4"}

for _, ip := range ips { 
   
  c.Add(ip)
}

fmt.Println("ip: ", c.Get("/hello.txt"))

背景

在介绍一致性哈希之前,首先来看看集群部署可能发生的问题:比如说我现在有5台 Redis 服务器,正常运行了很久,很不巧有一天A服务器崩溃了,这个时候还有4台服务器,系统还可以正常运行,原来发送到A服务器的请求我们肯定要想办法进行重定向吧,如果说我们使用一般的哈希函数进行分配,无疑是 hash(key) % num,不过因为 num 现在变成了 num-1,那么很有可能所有的请求都会发生改变打到不同的服务器上,原来发送到B的请求重新处理之后可能发送到了C服务器了。
一致性哈希算法原理及代码实现「建议收藏」

为了规避这种大迁移情况,我们可以使用一致性哈希。

算法原理

一致性哈希算法使用的也是取模,但是不同于普通的哈希,我们不是对服务器的数量进行取模,而是对 2^32 取模,也就是相当于对一个固定的数进行取模,我们可以这样认为:所有的哈希值空间构成了一个圆环,如下图:

一致性哈希算法原理及代码实现「建议收藏」

如果我们有多个服务器的,根据某个属性计算哈希值,比如说IP地址,映射到圆环上对应节点,假设说现在我有三个节点,映射之后的结果如下:

一致性哈希算法原理及代码实现「建议收藏」

当某个请求发送过来的时候,根据其中的某个属性计算哈希值,比如说HTTP请求的路径,头部信息等,假设映射到如下的位置

一致性哈希算法原理及代码实现「建议收藏」

根据映射到的位置,我们选择顺时针旋转遇到的第一个服务器节点作为该请求所选择的服务器节点,也就是 B 服务器。

当我们的某台服务器崩溃的时候,比如 B 崩溃了,那么 A 请求根据一致性哈希原理,会分配到服务器节点 C,更一般的,(A, B] 范围中的请求都会重新分配到服务器节点 C,而其他的请求都不会发生改变,这和之前所述的普通哈希函数不同。

但是上述的算法在某些情况下表现不好,比如说下面的这种服务器节点分布:

一致性哈希算法原理及代码实现「建议收藏」

C 一个节点就占用了大概2/3的取值空间,导致请求分配非常不均匀,针对这种情况,我们可以通过设置多个节点副本,如下:

一致性哈希算法原理及代码实现「建议收藏」

这样处理之后,比之前而言要均匀了很多,理论来说,副本的数量越多,分配的越均匀,但是同时也会增加管理的难度,所以不宜设置过大。

算法实现

先简单说一下思路吧,按照上面的原理,我们需要:

  • hash 函数:能够支持多副本
  • 服务器节点映射:哈希值->服务器节点
  • 映射的值范围:类似上面的取值圆环
type consistent struct { 
   
	// 副本数量
	replicas int
	// 所有的server 节点
	nodes map[string]struct{ 
   }
	// 节点所对应的server
	servers map[uint32]string
	// 保存所有的索引,也就是在hash圆环上的节点
	circle uints // type uints []uint32
	// 采用的hash算法
	// hash 方法可能直接决定节点的分布情况
	hash Hash
	sync.RWMutex
}

当一个服务器节点进行映射的时候,会得到多个哈希值(多个副本),这些哈希值在圆环上对应一个位置

一致性哈希算法原理及代码实现「建议收藏」

但是我们没必要保存一个 2^31-1 大的数组,我们只需要维护一个有序数组就好了,当一个请求过来的时候,我们只需要获取到它的哈希值顺时针的下一个节点哈希值对应的服务器节点就好了

哈希函数

哈希函数应该能够支持多副本,方式有很多种,这里采用一个简单的方式,也可以自定义

import (
	"hash/fnv"
	"strconv"
)

// replica
func hash(key string, num int) uint32 { 
   
	h := fnv.New32()
	h.Write([]byte(key + '-'+ strconv.Itoa(num)))
	return h.Sum32()
}

服务器节点映射

服务器节点进行哈希之后,需要保存对应的哈希值,这样可以通过哈希值获取到对应的服务器

servers := make(map[uint32]string)

映射值的范围

circle := make([]uint32, 0)

每一次添加节点的时候,都需要进行重新排序:

for i := 0; i < replicas; i++ { 
   
  key := hash(node, i)
  circle = append(c.circle, key)
  servers[key] = node
}
// 重新进行排序
sort.Sort(c.circle)

而在删除节点的时候,我们只需要删除对应节点的哈希值,顺序维持不变即可。

支持的方法

主要支持的方法不是很多,基本的有:

type ConsistentHasher interface { 
   
	// 添加节点
	Add(slot string)
	// 删除节点
	Delete(slot string)
	// 数据对应的节点
	Get(key string) string
}

添加服务器节点上面已经说了,删除节点的主要代码如下:

func (c *consistent) Delete(node string) { 
   
	c.Lock()
	defer c.Unlock()
	// 删除节点
	delete(c.nodes, node)

	// 因为在数组中删除元素不方便,这里先记录一下需要删除的数据
	// 然后如果在这里面的数据就不再添加到新的记录中
	memo := make(map[uint32]struct{ 
   })

	// 删除hash圆环中的值
	for i := 0; i < c.replicas; i++ { 
   
		key := c.hashKey(node, i)
		memo[key] = struct{ 
   }{ 
   }
		delete(c.servers, key)
	}

	// 创建一个新的保存
	newCircle := make(uints, 0, c.circle.Len()-c.replicas)
	for i := 0; i < c.circle.Len(); i++ { 
   
		if _, ok := memo[c.circle[i]]; !ok { 
   
			newCircle = append(newCircle, c.circle[i])
		}
	}
	c.circle = newCircle
}

当我们需要获取一个请求对应的服务器节点的时候,我们只要搜索到顺序针第一个服务器节点即可,因为哈希值有序,这里可以采用二分查找的方式

// Get 获取到属于的server节点
func (c *consistent) Get(name string) string { 
   
	c.RLock()
	defer c.RUnlock()
	// 首先将hash找到
	key := c.hash(name)
	// 然后在Hash圆环上找到对应的节点
	i := sort.Search(len(c.circle), func(i int) bool { 
    return c.circle[i] >= key })
	
  // 顺时针计算应该就是第一个节点了
  if i >= c.circle.Len() { 
   
		i = 0
	}
	return c.servers[c.circle[i]]
}

总结

一致性哈希在加入和删除节点的时候只会影响相邻的节点,对其他的节点无影响。当节点数量发生变化,我们不希望映射关系全部被打乱的时候,可以采用该算法。

具体的实现代码可从微信公众号推文中获取~

一致性哈希算法原理及代码实现「建议收藏」

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/164582.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • python爬虫滑动验证码_python爬虫爬取京东优惠线报

    python爬虫滑动验证码_python爬虫爬取京东优惠线报如何自动登陆京东?我们先来看一下京东的登陆页面,如下图所示:【插入图片,登陆页面】登陆框就是右面这一个框框了,但是目前我们遇到一个困呐,默认的登陆方式是扫码登陆,如果我们想要以用户民个、密码的形式登陆,就要切换一下。我们看一下这两种登陆方式是如何切换的,通过浏览器的元素检查,我们看一下两个标签。【插入图片,两种登陆方式】扫码登陆和用户登陆分别在一个div标签里面,我们可以通过css选择器选定用户登…

    2022年9月18日
    2
  • vmware15.5.7激活码-激活码分享

    (vmware15.5.7激活码)2021最新分享一个能用的的激活码出来,希望能帮到需要激活的朋友。目前这个是能用的,但是用的人多了之后也会失效,会不定时更新的,大家持续关注此网站~IntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,下面是详细链接哦~https://javaforall.net/100143.html…

    2022年3月20日
    444
  • pycharm激活码提示Key is invalid.(注册激活)2022.02.14

    (pycharm激活码提示Key is invalid.)这是一篇idea技术相关文章,由全栈君为大家提供,主要知识点是关于2021JetBrains全家桶永久激活码的内容https://javaforall.net/100143.htmlIntelliJ2021最新激活注册码,破解教程可免费永久激活,亲测有效,上面是详细链接哦~9AAG1RZ8NI-eyJsaWNlb…

    2022年4月1日
    1.4K
  • eclipse如何导入java文件_xml表格

    eclipse如何导入java文件_xml表格代码快速实现xml转换为Excel(xml转excel通用类-java-完成代码可作工具使用)用代码实现xml文件/数据转换为excel文件。(java)—-何潮背景:最近项目要做导出功能,但导出的数据对象类型实在太多了,一个个去实现;实在是没心情去做。于是———-意义:快速实现数据导出为什么是xmltoexcel?因为项目中可以直接使用xml数据。所以就选择xm…

    2022年8月22日
    9
  • XOR加密初识

    XOR加密初识XOR加密利用了两次异或操作仍为原值的特性。通过一个密钥,将明文与密钥进行异或操作,从而对明文加密,解密时再将密文与密钥进行一次异或操作就能恢复出明文。下面是C语言简单模拟:#include#include#defineKEY’K’//密钥intmain(){intorig_char,new_char;while((orig_char=getc

    2022年7月16日
    17
  • git push到远程指定分支_gitcheckout远程分支

    git push到远程指定分支_gitcheckout远程分支一、pull操作1、将远程指定分支拉取到本地指定分支上:gitpull&lt;远程仓库名&gt;&lt;远程分支名&gt;:&lt;本地分支名&gt;2、将远程指定分支拉取到本地当前分支上:gitpull&lt;远程仓库名&gt;&lt;远程分支名&gt;3、将与本地当前分支同名的远程分支拉取到本地当前分支上gitpull&lt;远程仓库名&…

    2022年8月22日
    11

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号