游戏服务器架构系列 - 一致性Hash
一致性Hash作用
主要是为了解决因为后端服务节点的动态删减导致节点不能正常服务的问题。
特别是在分布式缓存系统中,如果某台服务器失效,或者需要新增服务器,对于整个系统来说如果不采用合适的算法来保证一致性,那么缓存于系统中的所有数据都可能会失效,即由于系统节点数目变少或增多,客户端在请求某一数据时需要计算hash值,所以很可能找不到保存该数据的服务器节点,因此一致性hash就显得至关重要。
一致性Hash简介
一致性hash的核心在于将每个节点根据名称或者IP来进行hash,然后按照hash值将节点进行排序,分布在环上,然后将要查找的缓存键进行hash,根据这个hash值和在排好序的环上来找到需要从哪个节点查找数据。
增删节点导致的命中率对比
- 取模算法
取模是最常用的的算法,但是取模算法会因为服务实例的增减而大大降低缓存的命中率。假设有3台机器,编号0、1、2,还有n个key。如果某一个时间有一个节点挂掉,取模的底数从3变成了2。
宕机前:3台机器
key0 % 3 = 0
key1 % 3 = 1
key2 % 3 = 2
key3 % 3 = 0
key4 % 3 = 1
key5 % 3 = 2
key6 % 3 = 0
key7 % 3 = 1
key8 % 3 = 2
key9 % 3 = 0
key10 % 3 = 1
key11 % 3 = 2
key12 % 3 = 0
key13 % 3 = 1
key14 % 3 = 2
宕机后:2台机器
key0 % 2 = 0 --- 命中
key1 % 2 = 1 --- 命中
key2 % 2 = 0 --- 未命中
key3 % 2 = 1 --- 未命中
key4 % 2 = 0 --- 未命中
key5 % 2 = 1 --- 未命中
key6 % 2 = 0 --- 命中
key7 % 2 = 1 --- 命中
key8 % 2 = 0 --- 未命中
key9 % 2 = 1 --- 未命中
key10 % 2 = 0 --- 未命中
key11 % 2 = 0 --- 未命中
key12 % 2 = 0 --- 命中
key13 % 2 = 1 --- 命中
key14 % 2 = 2 --- 未命中
假设有m台服务器,变为m-1台服务器,每 m*(m-1)个数据中, 只有(m-1)个单元对m和(m-1)求模得到相同的结果,所以命中率在服务器down的短期内, 急剧下降至 (m-1)/(m*(m-1)) = 1/m
- 一致性Hash算法
(1)增加服务器的场景
如果增加一个node5,影响的范围就是node2和node5的2个hash值内的元素,因为原本这部分数据是落在node4上的,因为增加的node5,所以请求会落在node5上。这种场景对命中率的降低都不到1/m
(2)删除节点的场景
如果删除node4节点,原本该落在node4上的请求,都落到了node3上,所以这种场景对命中率的降低刚好在1/m。
如果随着m的值越来越大,一致性hash在节点增删的时候命中率还是会很平稳,不会急转直下,取模算法在节点增删的时候会导致命中率急剧下降。
平衡性
如果节点数很少,有的节点的hash值比较大,有的节点的hash值又很小,就会导致有的节点上的数据会很多,有的节点上的数据会很少,我们可以采用虚拟节点的方式来解决这个问题,给每个节点扩增32、64或者128个虚拟节点,给虚拟节点生成hash值,然后排序分布在环上,数据最终还是落在节点上。
实现
基于Golang的实现:https://github.com/MaxwellBackend/Games/tree/master/consistent_hash