本系列文章主要是针对php-memcache扩展而言。至于php-memcached扩展,看后面的情况吧,有时间就说说。
源码地址:http://git.oschina.net/moxi/php-memcache-test
基于php-memcache扩展,分析扩展中使用的一致性哈希算法
master分支是原始版本3.0.8 beta
master-test 分支是基于原始版本的测试版本,增加了测试方法
###关于Memcache和MemcachePool类 先看两个类的初始化:地址 <!-- lang: cpp --> INIT_CLASS_ENTRY(ce, "MemcachePool", php_memcache_pool_class_functions); memcache_pool_ce = zend_register_internal_class(&ce TSRMLS_CC);
INIT_CLASS_ENTRY(ce, "Memcache", php_memcache_class_functions);
memcache_ce = zend_register_internal_class_ex(&ce, memcache_pool_ce, NULL TSRMLS_CC);
我们看到两个类分别被注册,并且,Memcache类共用了MemcachePool类的方法。
再看两个类的方法定义: MemcachePool类 和 Memcache类
区别的地方是 Memcache类独有的以下方法
<!-- lang: cpp -->
static zend_function_entry php_memcache_class_functions[] = {
PHP_FALIAS(connect, memcache_connect, NULL)
PHP_FALIAS(pconnect, memcache_pconnect, NULL)
PHP_FALIAS(addserver, memcache_add_server, NULL)
{NULL, NULL, NULL}
};
###关于测试用的Memcached信息
memcached服务版本:
STAT version 1.4.21
STAT libevent 2.0.21-stable
要分析php-memcache的一致性哈希实现,只需要注意两个函数,memcache_add_server和 memcache_get。
Memcache 和 MemcachePool 两个方法的定义位置:
Memcache::addServer()
MemcachePool::addServer()
两个方法是用同一个连接函数:
php_mmc_pool_addserver(mmc_object, host, host_len, tcp_port, udp_port, weight, persistent, timeout, retry_interval, status, NULL TSRMLS_CC);
从这里我们也可以看出MemcachePool的addServer支持udp。
memcache_consistent_hash.c 这个文件是关于一致性哈希的具体实现。
addServer方法添加服务器时是用的是mmc_consistent_add_server ,这个函数就是关于一致性哈希添加服务器节点的。
<!-- lang: cpp -->
void mmc_consistent_add_server(void *s, mmc_t *mmc, unsigned int weight) /* {{{ */
{
mmc_consistent_state_t *state = s;
int i, key_len, points = weight * MMC_CONSISTENT_POINTS;
unsigned int seed = state->hash->init(), hash;//这里关于哈希函数初始化
/* buffer for "host:port-i\0" */
char *key = emalloc(strlen(mmc->host) + MAX_LENGTH_OF_LONG * 2 + 3);
key_len = sprintf(key, "%s:%d-", mmc->host, mmc->tcp.port);
seed = state->hash->combine(seed, key, key_len);//服务器信息 host+port+- 的哈希值
/* 这里是权重影响的points的数量 ,直接影响缓存服务器的节点分布 */
/* add weight * MMC_CONSISTENT_POINTS number of points for this server */
state->points = erealloc(state->points, sizeof(*state->points) * (state->num_points + points));
for (i=0; i<points; i++) {
key_len = sprintf(key, "%d", i);
/* 每个point的哈希值,分别用当前ponit数去重新哈希上面服务器信息的哈希 */
hash = state->hash->finish(state->hash->combine(seed, key, key_len));
state->points[state->num_points + i].server = mmc;
state->points[state->num_points + i].point = hash;
}
state->num_points += points;
state->num_servers++;
state->buckets_populated = 0;
efree(key);
}
用什么哈希函数,为什么是2^32-1?
php-memcache有两种哈希策略:crc32 和 fnv1a 。
哈希函数策略定义:地址
<!-- lang: cpp -->
typedef unsigned int (*mmc_hash_function_init)();
typedef unsigned int (*mmc_hash_function_combine)(unsigned int seed, const void *key, unsigned int key_len);
typedef unsigned int (*mmc_hash_function_finish)(unsigned int seed);
typedef struct mmc_hash_function {
mmc_hash_function_init init;
mmc_hash_function_combine combine;
mmc_hash_function_finish finish;
} mmc_hash_function_t;
extern mmc_hash_function_t mmc_hash_crc32;
extern mmc_hash_function_t mmc_hash_fnv1a;
#define mmc_hash(hash, key, key_len) ((hash)->finish((hash)->combine((hash)->init(), (key), (key_len))))
我们常用的哈希策略是crc32。
关于crc32和fnv1a的优缺点: FNV-1a比CRC32速度稍低,但是散列效果更好。
那就按照通用的CRC32来说。 PS:这种哈希算法方面不精通,纯摘自网络。
<!-- lang: cpp -->
/* 调用php的CRC32算法 */
#include "ext/standard/crc32.h"
static unsigned int mmc_hash_crc32_init() { return ~0; }
static unsigned int mmc_hash_crc32_finish(unsigned int seed) { return ~seed; }
static unsigned int mmc_hash_crc32_combine(unsigned int seed, const void *key, unsigned int key_len) /*
CRC32 hash {{{ */
{
const char *p = (const char *)key, *end = p + key_len;
while (p < end) {
CRC32(seed, *(p++));
}
return seed;
}
/* }}} */
mmc_hash_function_t mmc_hash_crc32 = {
mmc_hash_crc32_init,
mmc_hash_crc32_combine,
mmc_hash_crc32_finish
};
php-src/ext/standard/crc32.h php源码里的crc32就不贴代码了,看这个文件,就知道CRC32使用的是CRC32查表法。了解详情请自行谷歌。
CRC32:CRC本身是“冗余校验码”的意思,CRC32则表示会产生一个32bit(8位十六进制数)的校验值。由于CRC32产生校验值时源数据块的每一个bit(位)都参与了计算,所以数据块中即使只有一位发生了变化,也会得到不同的CRC32值。
所以一个值经过CRC32哈希之后,最大值就是0xffff ffff。转为无符号整形的取值范围就是0~0xffff fffe 即 0 ~ 2^32-1
###关于points 和 state 结构
<!-- lang: cpp -->
typedef struct mmc_consistent_point {/* 这个可以理解为 服务器节点的映像,一个哈希结构,多条记录对应一个缓存实体 */
mmc_t *server;//指向缓存节点信息的指针
unsigned int point;//上述中 服务器节点信息哈希后再用point值哈希的一个哈希值
} mmc_consistent_point_t;
typedef struct mmc_consistent_state {
int num_servers;//当前服务器实体数量
mmc_consistent_point_t *points;//缓存映像哈希表
int num_points;//缓存映像数量
mmc_t *buckets[MMC_CONSISTENT_BUCKETS];/* 这里就是 一致性哈希的所有节点哈希表 */
int buckets_populated;
mmc_hash_function_t *hash;/* 哈希函数 */
} mmc_consistent_state_t;
这部分结构就是实现一致性哈希的重点结构。聪明的你可以已经知道圆环上节点的数量了,下篇文章会详细解释。
下篇文章开始讲解一致性哈希的缓存节点分布实现。