一、修改test程序
本节开始探索分布式算法,使用三个memcached服务器,将test程序修改为const char* server_list = "localhost:11211, localhost:11212, localhost:11213"。对于分布式算法,系统默认为MEMCACHED_DISTRIBUTION_MODULA,见_memcached_init()中self->distribution = MEMCACHED_DISTRIBUTION_MODULA。
二、hashkit_create
memcached_create调用_memcached_init(),其中会调用hashkit_create(&self->hashkit),进而调用_hashkit_init()。
static inline void _hashkit_init(hashkit_st *self)
{
self->base_hash.function= hashkit_one_at_a_time;
self->base_hash.context= NULL;
self->distribution_hash.function= hashkit_one_at_a_time;
self->distribution_hash.context= NULL;
self->flags.is_base_same_distributed= true;
}
三、run_distribution
memcached_server_push()中会调用run_distribution()。
memcached_return_t run_distribution(memcached_st *ptr)
{
if (ptr->flags.use_sort_hosts)
{
sort_hosts(ptr);
}
switch (ptr->distribution)
{
case MEMCACHED_DISTRIBUTION_CONSISTENT:
case MEMCACHED_DISTRIBUTION_CONSISTENT_KETAMA:
case MEMCACHED_DISTRIBUTION_CONSISTENT_KETAMA_SPY:
case MEMCACHED_DISTRIBUTION_CONSISTENT_WEIGHTED:
return update_continuum(ptr);
case MEMCACHED_DISTRIBUTION_VIRTUAL_BUCKET:
case MEMCACHED_DISTRIBUTION_MODULA:
break;
case MEMCACHED_DISTRIBUTION_RANDOM:
srandom((uint32_t) time(NULL));
break;
case MEMCACHED_DISTRIBUTION_CONSISTENT_MAX:
default:
assert_msg(0, "Invalid distribution type passed to run_distribution()");
}
return MEMCACHED_SUCCESS;
}
四、memcached_generate_hash_with_redistribution
memcached_set中会调用memcached_generate_hash_with_redistribution来获得一个serverID。
1、首先调用_generate_hash_wrapper(),最终调用hashkit_one_at_a_time ()计算key的哈希值。
2、_regen_for_auto_eject(ptr),对于MEMCACHED_DISTRIBUTION_MODULA,什么也不做。
3、dispatch_host(ptr, hash)
static uint32_t dispatch_host(const memcached_st *ptr, uint32_t hash)
{
switch (ptr->distribution)
{
case MEMCACHED_DISTRIBUTION_CONSISTENT:
case MEMCACHED_DISTRIBUTION_CONSISTENT_WEIGHTED:
case MEMCACHED_DISTRIBUTION_CONSISTENT_KETAMA:
case MEMCACHED_DISTRIBUTION_CONSISTENT_KETAMA_SPY:
{
uint32_t num= ptr->ketama.continuum_points_counter;
WATCHPOINT_ASSERT(ptr->ketama.continuum);
memcached_continuum_item_st *begin, *end, *left, *right, *middle;
begin= left= ptr->ketama.continuum;
end= right= ptr->ketama.continuum + num;
while (left < right)
{
middle= left + (right - left) / 2;
if (middle->value < hash)
left= middle + 1;
else
right= middle;
}
if (right == end)
right= begin;
return right->index;
}
case MEMCACHED_DISTRIBUTION_MODULA:
return hash % memcached_server_count(ptr);
case MEMCACHED_DISTRIBUTION_RANDOM:
return (uint32_t) random() % memcached_server_count(ptr);
case MEMCACHED_DISTRIBUTION_VIRTUAL_BUCKET:
{
return memcached_virtual_bucket_get(ptr, hash);
}
default:
case MEMCACHED_DISTRIBUTION_CONSISTENT_MAX:
WATCHPOINT_ASSERT(0); /* We have added a distribution without extending the logic */
return hash % memcached_server_count(ptr);
}
/* NOTREACHED */
}
可以看出:对于MEMCACHED_DISTRIBUTION_MODULA,只是简单的将hash % memcached_server_count(ptr),从而获得了一个serverID。
五、引子:Ketama一致性哈希算法(转载)
转载自http://langyu.iteye.com/blog/684087
一致性哈希算法(Consistent Hashing Algorithm)是一种分布式算法,常用于负载均衡。Memcached client也选择这种算法,解决将key-value均匀分配到众多Memcached server上的问题。它可以取代传统的取模操作,解决了取模操作无法应对增删Memcached Server的问题(增删server会导致同一个key,在get操作时分配不到数据真正存储的server,命中率会急剧下降),详细的介绍在这篇帖子中http://www.iteye.com/topic/611976(后文指代这篇文章的地方均称为引文)。
[下面以Memcached的分布式问题为讨论点,但将Memcached server抽象为节点(Node)]
引文中描述的一致性Hash算法有个潜在的问题是:
将节点hash后会不均匀地分布在环上,这样大量key在寻找节点时,会存在key命中各个节点的概率差别较大,无法实现有效的负载均衡。
如有三个节点Node1,Node2,Node3,分布在环上时三个节点挨的很近,落在环上的key寻找节点时,大量key顺时针总是分配给Node2,而其它两个节点被找到的概率都会很小。
这种问题的解决方案可以有:
改善Hash算法,均匀分配各节点到环上;[引文]使用虚拟节点的思想,为每个物理节点(服务器)在圆上分配100~200个点。这样就能抑制分布不均匀,最大限度地减小服务器增减时的缓存重新分布。用户数据映射在虚拟节点上,就表示用户数据真正存储位置是在该虚拟节点代表的实际物理服务器上。
在查看Spy Memcached client时,发现它采用一种称为Ketama的Hash算法,以虚拟节点的思想,解决Memcached的分布式问题。
Ketama is an implementation of a consistent hashing algorithm, meaning you can add
or remove servers from the memcached pool without causing a complete remap of all keys.
Here’s how it works:
* Take your list of servers (eg: 1.2.3.4:11211, 5.6.7.8:11211, 9.8.7.6:11211)
* Hash each server string to several (100-200) unsigned ints
* Conceptually, these numbers are placed on a circle called the continuum.
(imagine a clock face that goes from 0 to 2^32)
* Each number links to the server it was hashed from, so servers appear at several
points on the continuum, by each of the numbers they hashed to.
* To map a key->server, hash your key to a single unsigned int, and find the
next biggest number on the continuum. The server linked to that number
is the correct server for that key.
* If you hash your key to a value near 2^32 and there are no points on the
continuum greater than your hash, return the first server in the continuum.
If you then add or remove a server from the list, only a small proportion
of keys end up mapping to different servers.