Python实现一致性hash(不设置虚拟节点与设置虚拟节点的对比)

一致性hash的原理介绍,前人已经做的很清楚了,可以参看下面链接:

一致性HASH算法详解

上文美中不足的是,数据结构的设计较复杂,hash环的实现,属性用简单的列表和字典实现即可。

一致性哈希(不设置虚拟节点)

首先放不设置虚拟节点的代码,可以看见删除掉某个节点时很容易引起雪崩效应,代码如下。

"""对一致性hash进行学习,构造没有vnode的hash,增加和删除节点以进行观察,会产生对应的雪崩效应"""
from zlib import crc32
import memcache

class conhashnorep(object):
    def __init__(self,nodes=None):
        """此处nodes代表需要初始化的node列表,为进行对比先提供虚拟节点副本数,ring是存储hash-node的字典,key是存储所有hash值的列表"""
        self.nodes=nodes
        self.ring={}
        self._sorted_keys=[]
        self.add_nodes(nodes)

    def _add_node(self,node):
        bnode=bytes(node,encoding="utf8")
        nodehash=abs(crc32(bnode))
        self.ring[nodehash]=node
        self._sorted_keys.append(nodehash)
        self._sorted_keys.sort()

    def add_nodes(self,nodes):
        if nodes:
            for node in nodes:
                self._add_node(node)

    def remove_nodes(self,nodes):
        """对于remove的操作,如果直接操作已经产生的ring字典,会比较麻烦,因为ring是{hashnode:node}的键值对形式,
        直接处理涉及到字典直接查找值ring.values()/ring.keys(),以及通过值来remove(key)的操作,太麻烦"""
        if nodes:
            for node in nodes:
                bnode = bytes(node, encoding="utf8")
                nodehash = abs(crc32(bnode))
                del self.ring[nodehash]
                self._sorted_keys.remove(nodehash)

    def get_node(self,key):
        bkey=bytes(key,encoding="utf8")
        keyhash=abs(crc32(bkey))
        i=0
        for nodehash in self._sorted_keys:
            i += 1
            if keyhash < nodehash:
                #print("%d Keyhash:%s Nodehash:%s" % (i,keyhash,nodehash))
                return self.ring[nodehash]
            else:
                continue
        if i==len(self._sorted_keys):
            #print("None Keyhash:%s Nodehash:%s" % (keyhash,self._sorted_keys[0]))
            return  self.ring[self._sorted_keys[0]]

    def count_server(self,number):
        '''server_list用于存放重复出现的server,server_counnt用于统计出现频数'''
        server_list = []
        server_count = {}
        for i in range(number):
            kei = "key_%s" % i
            server = self.get_node(kei)
            server_list.append(server)
        for m in set(server_list):
            server_count[m] = server_list.count(m)
        '''根据字典的键进行排序,如果需要逆序则添加 reverse=True'''
        server_count=dict(sorted(server_count.items(), key=lambda e: e[0]))
        print("ServerCount:", server_count)


ini_servers = [
    '127.0.0.1:1',
    '127.0.0.1:2',
    '127.0.0.1:3',
    '127.0.0.1:4',
    #'127.0.0.1:7005',
]
h=conhashnorep(ini_servers)
print(h.ring)

"""构建一些object来观察会分配到哪一个节点"""
#for i in range(20):
part_servers=['127.0.0.2:1','127.0.0.2:2']
h.count_server(200)
h.add_nodes(part_servers)
h.count_server(200)
h.remove_nodes(['127.0.0.1:1','127.0.0.1:2'])
h.count_server(200)

执行程序,得到如下结果

Python实现一致性hash(不设置虚拟节点与设置虚拟节点的对比)_第1张图片

查看可知,在直接删除‘127.0.0.1:1’,‘127.0.0.1:2’两个节点后,原本属于这两个节点的key并没有较均匀的分散到其他节点,而是直接积压在‘127.0.0.2:1’,‘127.0.0.2:2’这两个新节点上。

一致性哈希(设置虚拟节点)

"""对一致性hash进行学习,构造没有vnode的hash,增加和删除节点以进行观察,会产生对应的雪崩效应"""
from zlib import crc32

class conhashnorep(object):
    def __init__(self,nodes=None,replicas=5):
        """此处nodes代表需要初始化的node列表,为进行对比先提供虚拟节点副本数,ring是存储hash-node的字典,key是存储所有hash值的列表"""
        self.nodes=nodes
        self.replicas=replicas
        self.ring={}
        self._sorted_keys=[]
        self.add_nodes(nodes)

    def _add_node(self,node):
        for i in range(self.replicas):
            bnode="%s_vnode%s" % (node,i)
            bnode = bytes(bnode, encoding="utf8")
            nodehash = abs(crc32(bnode))
            self.ring[nodehash] = node
            self._sorted_keys.append(nodehash)
        self._sorted_keys.sort()

    def add_nodes(self,nodes):
        if nodes:
            for node in nodes:
                self._add_node(node)

    def remove_nodes(self,nodes):
        """对于remove的操作,如果直接操作已经产生的ring字典,会比较麻烦,因为ring是{hashnode:node}的键值对形式,
        直接处理涉及到字典直接查找值ring.values()/ring.keys(),以及通过值来remove(key)的操作,太麻烦"""
        if nodes:
            for node in nodes:
                for i in range(self.replicas):
                    bnode = "%s_vnode%s" % (node, i)
                    bnode = bytes(bnode, encoding="utf8")
                    nodehash = abs(crc32(bnode))
                    del self.ring[nodehash]
                    self._sorted_keys.remove(nodehash)

    def get_node(self,key):
        bkey=bytes(key,encoding="utf8")
        keyhash=abs(crc32(bkey))
        i=0
        for nodehash in self._sorted_keys:
            i += 1
            if keyhash < nodehash:
                #print("%d Keyhash:%s Nodehash:%s" % (i,keyhash,nodehash))
                return self.ring[nodehash]
            else:
                continue
        if i==len(self._sorted_keys):
            #print("None Keyhash:%s Nodehash:%s" % (keyhash,self._sorted_keys[0]))
            return  self.ring[self._sorted_keys[0]]

    def count_server(self,number):
        '''server_list用于存放重复出现的server,server_counnt用于统计出现频数'''
        server_list = []
        server_count = {}
        for i in range(number):
            kei = "key_%s" % i
            server = self.get_node(kei)
            server_list.append(server)
        for m in set(server_list):
            server_count[m] = server_list.count(m)
        '''根据字典的键进行排序,如果需要逆序则添加 reverse=True'''
        server_count=dict(sorted(server_count.items(), key=lambda e: e[1],reverse=True))
        print("ServerCount:", server_count)


ini_servers = [
    '127.0.0.1:1',
    '127.0.0.1:2',
    '127.0.0.1:3',
    '127.0.0.1:4',
    #'127.0.0.1:7005',
]
h=conhashnorep(ini_servers)
print(h.ring)

"""构建一些object来观察会分配到哪一个节点"""
#for i in range(20):
part_servers=['127.0.0.2:1','127.0.0.2:2']
h.count_server(200)
h.add_nodes(part_servers)
h.count_server(200)
h.remove_nodes(['127.0.0.1:1','127.0.0.1:2'])
h.count_server(200)

运行结果如下:

可见,去掉‘127.0.0.1:1’和‘127.0.0.1:2’两个节点,并没有完全积压到某一个节点,相比较而言做了一定的分散。

后续

一致性hash的使用在分布式系统的实现应该比较常见,比如分布式存储,分布式计算(多队列多任务),在实际的使用中那种数据结构会取得比较好的效率还未实践,本文代码参考了python官方的hash ring的实现,也可以考虑使用一些别的东西,比如memcache(这个我个人感觉类似于一个代码加速器或者数据库中间件,但是可能这样的理解有问题)。也可以考虑用别的方式来实现一致性hash(存储量或者任务量达到一定程度,且是交互式查询时需要提升性能),例如map,例如红黑树,以前学艺不精,现在慢慢捡以前丢的东西,后续有时间有机会再来补充。

你可能感兴趣的:(Python,算法)