一致性哈希是由Karger等人于1997年提出的一种特殊的哈希算法,目的是解决分布式缓存的问题,现在在分布式系统中有着广泛的应用。本文将对ketama、jump consistent hash、rendezvous hash和maglev hash四种算法进行对比分析。
一、一致性哈希的特性
- 平衡性
不同key通过算法映射后,可以比较均衡地分布到所有的后端节点上。
- 单调性
当有新的节点上线后,系统中原有的key要么还是映射到原来的节点上,要么映射到新加入的节点上,不会出现从一个老节点重新映射到另一个老节点。
- 稳定性
当服务发生扩缩容的时候,发生迁移的数据量尽可能少。
二、问题背景
假设我们有N个cache服务器节点,那如何将数据映射到这N个节点上呢,最简单的方法就是用数据计算出一个hash值,然后用hash值对N取模,如:hash(data) % N,这样只要计算出来的hash值比较均匀,那数据也就能比较均匀地映射到N个节点上了。但这带来的问题就是,如果发生扩缩容,节点的数量发生了变化,那很多数据的映射关系都会发生变化。显然这种方法虽然简单,但并不太能解决我们的需求。
三、四种常见一致性哈希算法
下面分别介绍对比四种比较常见的一致性哈希算法,看看一致性哈希算法是怎么解决这问题的。
1. 经典一致性哈希
经典的一致性哈希算法也就是我们常说的割环法 ,大家应该都比较熟悉。简单来说就是,我们把节点通过hash的方式,映射到一个范围是[0,2^32]的环上,同理,把数据也通过hash的方式映射到环上,然后按顺时针方向查找第一个hash值大于等于数据的hash值的节点,该节点即为数据所分配到的节点。而更好点的做法是带虚拟节点的方法,我们可以为每个物理节点分配若干个虚拟节点,然后把虚拟节点映射到hash环,分配给每个物理节点虚拟节点数量对应每个物理节点的权重,如下图1所示。这样还是按顺时针的方法查找数据所落到的虚拟节点,再看该虚拟节点是属于哪个物理节点就可以知道数据是分配给哪个物理节点了。
图1
这种割环法的实现多种,下面以比较有名的Ketama Hash实现为例进行对比分析。Ketama Hash的关键源码如下:
#服务器节点例子,第一列为地址,第二列为内存
#------ Server --------Mem-#
#255.255.255.255:6553566666#
10.0.1.1:11211600
10.0.1.2:11211300
10.0.1.3:11211200
10.0.1.4:11211350
10.0.1.5:112111000
10.0.1.6:11211800
10.0.1.7:11211950
10.0.1.8:11211100
typedef struct
{
unsigned int point; // point on circle
char ip[22];
} mcs;
typedef struct
{
char addr[22];
unsigned long memory;
} serverinfo;
typedef struct
{
int numpoints;
void* modtime;
void* array; //array of mcs structs
} continuum;
typedef continuum* ketama_continuum;
/** \brief Generates the continuum of servers (each server as many points on a circle).
* \param key Shared memory key for storing the newly created continuum.
* \param filename Server definition file, which will be parsed to create this continuum.
* \return 0 on failure, 1 on success. */
static int
ketama_create_continuum( key_t key, char* filename )
{
if (shm_ids == NULL) {
init_shm_id_tracker();
}
if (shm_data == NULL) {
init_shm_data_tracker();
}
int shmid;
int* data; /* Pointer to shmem location */
unsigned int numservers = 0;
unsigned long memory;
serverinfo* slist;
slist = read_server_definitions( filename, &numservers, &memory );
/* Check numservers first; if it is zero then there is no error message
* and we need to set one. */
if ( numservers < 1 )
{
set_error( "No valid server definitions in file %s", filename );
return 0;
}
else if ( slist == 0 )
{
/* read_server_definitions must've set error message. */
return 0;
}
#ifdef DEBUG