一致性哈希概念与Python的简单实现

2023-01-31 02:01:44 简单 概念 一致性哈希

好像从开始接触ZooKeeper的时候就知道了有一致性哈希这东西。。。。不过倒是一直都没有去了解这到底是个啥东西。。。只是知道它在分布式系统设计中有十分重要的作用。。。。


好了,接下来用举例子的方式来说一下一致性哈希到底有啥用吧。。。场景如下:

当前有4台服务器组成的分布式缓存系统,这里对于一个Key,通过哈希的方式,将其保存到某一台服务器上。。。


对于一般的哈希算法,我们首先会想到用一些哈希算法来获取Key的哈希值,例如MD5啥的。。。接着进行一次取模的操作,通过余数来确定将这个Key保存到哪一台服务器上。。。

嗯,这种方法确实很简单。。。不够缺存在很大的问题:

当系统中某一台服务器退出了之后,那么对于所有Key的哈希分布都得重新来过,以前的缓存也就基本失效了。。。

同时:

如果在系统中加入新的服务器,所有的Key的哈希分布也都失效了。。。


接下来就可以引入一致性哈希了。。。它就是用于解决这种问题的了。。。

这里无耻的盗一张图:



这里的关键就是,不光是要对缓存的key进行哈希,同时也需要服务器也提供一个key进行哈希,然后将其分布在一个闭圆上,在决定一个key的分布的时候,这里通过找到第一个大于这个key的哈希值的服务器就行了。。。

这样在当一台服务器退出之后,或者有新的服务器加入进来之后,只会影响一部分的key的哈希分布,不至于导致所有的key的哈希分布都失效。。。。


另外,如果服务器比较少的情况下,一般都会引入虚拟节点的概念。。也就是一个服务器在圆上对应多个节点。。。


当然这个只是对一致性哈希比较皮毛的理解。。。对于其在分布式系统中其他的高端用法也不太清楚。。不过好在以后不用怕再遇到这概念了。。

这里还简单的实现了一个简单的一致性哈希算法。。。用python写的:

# -*- coding: utf-8 -*-
import hashlib

class YHash(object):
    def __init__(self, nodes=None, n_number=3):
        """
        :param nodes:           所有的节点
        :param n_number:        一个节点对应多少个虚拟节点
        :return:
        """
        self._n_number = n_number   #每一个节点对应多少个虚拟节点,这里默认是3个
        self._node_dict = dict()    #用于将虚拟节点的hash值与node的对应关系
        self._sort_list = []        #用于存放所有的虚拟节点的hash值,这里需要保持排序
        if nodes:
            for node in nodes:
                self.add_node(node)

    def add_node(self, node):
        """
        添加node,首先要根据虚拟节点的数目,创建所有的虚拟节点,并将其与对应的node对应起来
        当然还需要将虚拟节点的hash值放到排序的里面
        这里在添加了节点之后,需要保持虚拟节点hash值的顺序
        :param node:
        :return:
        """
        for i in xrange(self._n_number):
            node_str = "%s%s" % (node, i)
            key = self._gen_key(node_str)
            self._node_dict[key] = node
            self._sort_list.append(key)
        self._sort_list.sort()

    def remove_node(self, node):
        """
        这里一个节点的退出,需要将这个节点的所有的虚拟节点都删除
        :param node:
        :return:
        """
        for i in xrange(self._n_number):
            node_str = "%s%s" % (node, i)
            key = self._gen_key(node_str)
            del self._node_dict[key]
            self._sort_list.remove(key)

    def get_node(self, key_str):
        """
        返回这个字符串应该对应的node,这里先求出字符串的hash值,然后找到第一个小于等于的虚拟节点,然后返回node
        如果hash值大于所有的节点,那么用第一个虚拟节点
        :param :
        :return:
        """
        if self._sort_list:
            key = self._gen_key(key_str)
            for node_key in self._sort_list:
                if key <= node_key:
                    return self._node_dict[node_key]
            return self._node_dict[self._sort_list[0]]
        else:
            return None

    @staticmethod
    def _gen_key(key_str):
        """
        通过key,返回当前key的hash值,这里采用md5
        :param key:
        :return:
        """
        md5_str = hashlib.md5(key_str).hexdigest()
        return long(md5_str, 16)


fjs = YHash(["127.0.0.1", "192.168.1.1"])
print fjs.get_node("fjs32121")

相关文章