<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">又到了一个能够沉思的夜晚。近期事情比較繁杂,大脑全然平静不下来。就想着研究点东西来平复一下。</span>
非常久前。曾被一个同事问到过关于PHP端Memcache分布式的细节问题,有一个业务有两台Memcached,举例:业务代码例如以下:
<?php /** *生成100个数据到memcache里。能够思考下,数据会怎么存储 */ $memClient = new Memcached(); $memClient->addServers(array(’10.21.1.11’,’11233’),array(’10.21.1.12’,’11233’)); for($i = 0;$i < 100;$i++){ $memClicent->set(“prefix_key_”.$i,$i,3600); }
以下我要開始把这些值打印出来
<?php $memClient = new Memcached(); $memClient->addServers(array(’10.21.1.11’,’11233’),array(’10.21.1.12’,’11233’)); for($i = 0;$i < 100;$i++){ $memClicent->get(“prefix_key_”.$i); }
这里的结果,是全部的值都被打印出来了,可是当我去掉当中一台server把连接变成,
$memClient->addServers(array(’10.21.1.11’,’11233’)。这时打印出来的值少了一将近半。看来serverset函数帮我们自己主动把数据hash到两台server上,可是set是怎么hash的,用的什么算法。我们全然不知道。以下就开启”疯狗模式”,准备深挖源码。
PS:Memcached能够手动设置hash算法,通过setOptions,而且Memcached自带了几种算法,比如Memcached::HASH_CRC,Memcached::HASH_FNV1_32,Memcached::MD5等
首先从memcached扩展入手。查看php_memcached.c,看下set做了什么,发现扩展里全部存储类的操作都调用了一个函数php_memc_store_impl。继续深挖。
PHP_METHOD(Memcached, set) { php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_SET, 0); }
在php_memc_store_imple找到例如以下:
status = memcached_set(m_obj->memc, key, key_len, payload, payload_len, expiration, flags);
他调用了libmemcached里提供的memcached_set函数。并把键值,有效时间传了过去。
在libmemcached/storage.cc里找到memcached_set。通过追踪发现终于存储数据是在memcached_send函里,在这里发现关键代码:
uint32_t server_key= memcached_generate_hash_with_redistribution(ptr, group_key, group_key_length); memcached_instance_st* instance= memcached_instance_fetch(ptr, server_key);
他通过我们的key来选取某台server。
在libmemcached/hash.cc里找到memcached_generate_hash_with_redistribution函数,通过终于追踪在generate_hash函数里发现关键代码例如以下:
return hashkit_digest(&ptr->hashkit, key, key_length);返回一个长整型,返回的这个值就是路由到哪个server的关键
在libhashkit/digest.cc里找到了hashkit_digest函数,关键代码例如以下:
return self->base_hash.function(key, key_length, self->base_hash.context);
可是,可是,self->base_hash是什么鬼。看下hashkit_digest函数的形參
uint32_t hashkit_digest(const hashkit_st *self, const char *key, size_t key_length)。这个self是来源于哪,于是開始回朔代码,终于回到了memcached扩展里最初的那个set
status = memcached_set(m_obj->memc, key, key_len, payload, payload_len, expiration, flags);这里有个m_obj->memc。这是memcached_set传递的第一个參数,于是self->base_hash能够想像成
m_obj->memc->hashkit->base_hash.function(key, key_length, self->base_hash.context);
这个base_hash在哪里。
我们先来看下m_obj是什么东西。
在php_memcached.c的124行,看到m_obj = i_obj->obj;
i_obj的结构体例如以下:
typedef struct { zend_object zo; struct memc_obj { memcached_st *memc; zend_bool compression; enum memcached_serializer serializer; enum memcached_compression_type compression_type; #if HAVE_MEMCACHED_SASL zend_bool has_sasl_data; #endif long store_retry_count; } *obj; zend_bool is_persistent; zend_bool is_pristine; int rescode; int memc_errno; } php_memc_t;
在i_obj里我们看到memc是一个memcached_st的指针,还记得hashkit_digest的函数的第一个參数么,里面的ptr就是如今的m_obj->memc,memcached_st在libmemcached-1.0/struct/memcached.h,所以我们从memcached_st里找到hashkit的结构体是hashkit_st。话说最终快找到头了。
hashkit_st结构体例如以下:
struct hashkit_st { struct hashkit_function_st { hashkit_hash_fn function; void *context; } base_hash, distribution_hash; struct { bool is_base_same_distributed:1; } flags; struct { bool is_allocated:1; } options; void *_key; };
看到base_hash在这里。这里有一个最关键的函数指针hashkit_hash_fn function,我们就是调用它指向的hash函数。可是这个指针是哪里被赋的值呢。
我们回到memcached扩展在php_memcached.c里看一下memcached在初始化的时候做了什么(new Memcached()),在static PHP_METHOD(Memcached, __construct)里我们看到m_obj->memc = memcached_create(NULL);这一句代码,这是调用了libmemcached来初始化一个memcached的实例。
我们在libmemcached/memcached.cc里追踪到了_memcached_init(Memcached *self)函数,关键代码例如以下:
if (hashkit_create(&self->hashkit) == NULL) { return false; }
hashkit_create给了m_obj->memc->hashkit初始化。最终(快断气了)。在libhashkit/hashkit.cc里追踪到了_hashkit_init(hashkit_st *self)。关键代码例如以下:
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; self->_key= NULL; }
这里hashkit_one_at_a_time就是我们在没加入默认hash的时候终于调用到的hash函数。
代码例如以下,很短,有兴趣的能够看下他的wiki:http://en.wikipedia.org/wiki/Jenkins_hash_function
#include <libhashkit/common.h> uint32_t hashkit_one_at_a_time(const char *key, size_t key_length, void *context) { const char *ptr= key; uint32_t value= 0; (void)context; while (key_length--) { uint32_t val= (uint32_t) *ptr++; value += val; value += (value << 10); value ^= (value >> 6); } value += (value << 3); value ^= (value >> 11); value += (value << 15); return value; }
至此,我们就探索了这一内幕。结束了,关闭"疯狗模式”,说实话,整个过程是很享受的:)。难得的一晚,大脑清醒了不少,如有错误,欢迎批评指证。