云顶娱乐平台 5

【云顶娱乐平台】php自定义hash函数实例_php技巧_脚本之家

本文实例讲述了php自定义hash函数实现方法。分享给大家供大家参考。具体分析如下:

背景

trim系列函数是用于去除字符串中首尾的空格或其他字符。ltrim函数只去除掉字符串首部的字符,rtrim函数只去除字符串尾部的字符。

string trim ( string $str [, string $character_mask = " tnrx0B" ] )

看一个例子:

$str = "e?type";

echo $str;
echo "n";

echo rtrim($str, '?type');
echo "n";

不知道别人怎么想,我觉得这个返回的应该是”e”吧。

但是实际上返回了一个空的字符串。

又是一个黑魔法吗?

接着我们看下mmc_pool_find是处理的

2.3,将配置作用到模块——REGISTER_INI_ENTRIES

经常能够在不同扩展的PHP_MINIT_FUNCTION里看到REGISTER_INI_ENTRIES。REGISTER_INI_ENTRIES主要负责完成两件事情,第一,对模块的全局空间XXX_G进行填充,同步configuration_hash中的值到XXX_G中去。其次,它还生成了EG(ini_directives)。

REGISTER_INI_ENTRIES也是一个宏,展开之后实则为zend_register_ini_entries方法。具体来看下zend_register_ini_entries的实现:

ZEND_API int zend_register_ini_entries(const zend_ini_entry *ini_entry, int module_number TSRMLS_DC) /* {{{ */
{
    // ini_entry为zend_ini_entry类型数组,p为数组中每一项的指针
    const zend_ini_entry *p = ini_entry;
    zend_ini_entry *hashed_ini_entry;
    zval default_value;

    // EG(ini_directives)就是registered_zend_ini_directives
    HashTable *directives = registered_zend_ini_directives;
    zend_bool config_directive_success = 0;

    // 还记得ini_entry最后一项固定为{ 0, 0, NULL, ... }么
    while (p->name) {
        config_directive_success = 0;

        // 将p指向的zend_ini_entry加入EG(ini_directives)
        if (zend_hash_add(directives, p->name, p->name_length, (void*)p, sizeof(zend_ini_entry), (void **) &hashed_ini_entry) == FAILURE) {
            zend_unregister_ini_entries(module_number TSRMLS_CC);
            return FAILURE;
        }
        hashed_ini_entry->module_number = module_number;

        // 根据name去configuration_hash中查询,取出来的结果放在default_value中
        // 注意default_value的值比较原始,一般是数字、字符串、数组等,具体取决于php.ini中的写法
        if ((zend_get_configuration_directive(p->name, p->name_length, &default_value)) == SUCCESS) {
            // 调用on_modify更新到模块的全局空间XXX_G中
            if (!hashed_ini_entry->on_modify || hashed_ini_entry->on_modify(hashed_ini_entry, Z_STRVAL(default_value), Z_STRLEN(default_value), hashed_ini_entry->mh_arg1, hashed_ini_entry->mh_arg2, hashed_ini_entry->mh_arg3, ZEND_INI_STAGE_STARTUP TSRMLS_CC) == SUCCESS) {
                hashed_ini_entry->value = Z_STRVAL(default_value);
                hashed_ini_entry->value_length = Z_STRLEN(default_value);
                config_directive_success = 1;
            }
        }

        // 如果configuration_hash中没有找到,则采用默认值
        if (!config_directive_success && hashed_ini_entry->on_modify) {
            hashed_ini_entry->on_modify(hashed_ini_entry, hashed_ini_entry->value, hashed_ini_entry->value_length, hashed_ini_entry->mh_arg1, hashed_ini_entry->mh_arg2, hashed_ini_entry->mh_arg3, ZEND_INI_STAGE_STARTUP TSRMLS_CC);
        }
        p++;
    }
    return SUCCESS;
}

简单来说,可以把上述代码的逻辑表述为:

1,将模块声明的ini配置项添加到EG(ini_directives)中。注意,ini配置项的值可能在随后被修改。

2,尝试去configuration_hash中寻找各个模块需要的ini。

  • 如果能够找到,说明用户叜ini文件中配置了该值,那么采用用户的配置。
  • 如果没有找到,OK,没有关系,因为模块在声明ini的时候,会带上默认值。

3,将ini的值同步到XX_G里面。毕竟在php的执行过程中,起作用的还是这些XXX_globals。具体的过程是调用每条ini配置对应的on_modify方法完成,on_modify由模块在声明ini的时候进行指定。

我们来具体看下on_modify,它其实是一个函数指针,来看两个具体的Core模块的配置声明:

STD_PHP_INI_BOOLEAN("log_errors",      "0",    PHP_INI_ALL, OnUpdateBool, log_errors,         php_core_globals, core_globals)
STD_PHP_INI_ENTRY("log_errors_max_len","1024", PHP_INI_ALL, OnUpdateLong, log_errors_max_len, php_core_globals, core_globals)

对于log_errors,它的on_modify被设置为OnUpdateBool,对于log_errors_max_len,则on_modify被设置为OnUpdateLong。

进一步假设我们在php.ini中的配置为:

log_errors = On
log_errors_max_len = 1024

具体来看下OnUpdateBool函数:

ZEND_API ZEND_INI_MH(OnUpdateBool) 
{
    zend_bool *p;

    // base表示core_globals的地址
    char *base = (char *) mh_arg2;

    // p表示core_globals的地址加上log_errors字段的偏移量
    // 得到的即为log_errors字段的地址
    p = (zend_bool *) (base+(size_t) mh_arg1);  

    if (new_value_length == 2 && strcasecmp("on", new_value) == 0) {
        *p = (zend_bool) 1;
    }
    else if (new_value_length == 3 && strcasecmp("yes", new_value) == 0) {
        *p = (zend_bool) 1;
    }
    else if (new_value_length == 4 && strcasecmp("true", new_value) == 0) {
        *p = (zend_bool) 1;
    }
    else {
        // configuration_hash中存放的value是字符串"1",而非"On"
        // 因此这里用atoi转化成数字1
        *p = (zend_bool) atoi(new_value);
    }
    return SUCCESS;
}

最令人费解的估计就是mh_arg1和mh_arg2了,其实对照前面所述的zend_ini_entry定义,mh_arg1,mh_arg2还是很容易参透的。mh_arg1表示字节偏移量,mh_arg2表示XXX_globals的地址。因此,(char
*)mh_arg2 +
mh_arg1的结果即为XXX_globals中某个字段的地址。具体到本case中,就是计算core_globals中log_errors的地址。因此,当OnUpdateBool最后执行到

*p = (zend_bool) atoi(new_value);

其作用就相当于

core_globals.log_errors = (zend_bool) atoi(“1”);

分析完了OnUpdateBool,我们再来看OnUpdateLong便觉得一目了然:

ZEND_API ZEND_INI_MH(OnUpdateLong)
{
    long *p;
    char *base = (char *) mh_arg2;

    // 获得log_errors_max_len的地址
    p = (long *) (base+(size_t) mh_arg1);

    // 将"1024"转化成long型,并赋值给core_globals.log_errors_max_len
    *p = zend_atol(new_value, new_value_length);
    return SUCCESS;
}

最后需要注意的是,zend_register_ini_entries函数中,如果configuration_hash中存在配置,则当调用on_modify结束后,hashed_ini_entry中的value和value_length会被更新。也就是说,如果用户在php.ini中配置过,则EG(ini_directives)存放的就是实际配置的值。如果用户没配,EG(ini_directives)中存放的是声明zend_ini_entry时给出的默认值。

zend_register_ini_entries中的default_value变量命名比较糟糕,相当容易造成误解。其实default_value并非表示默认值,而是表示用户实际配置的值。

function SimpleHash{ $n = 0; // The magic happens here: // I just loop trough all letters and add the // ASCII value to a integer variable. for ($c=0; $c < strlen $n += ord; // After we went trough all letters // we have a number that represents the // content of the string return $n;}

$TestString = 'www.jb51.net';print SimpleHash; // returns: 1082

结论

其实这也不算一个bug,文档说的很清楚,trim系列函数是字符级别的,可是惯性就让我们以为是字符串级别的,以后想清楚了再用。

// hash策略状态
typedef struct mmc_standard_state {
int num_servers; // 服务器数量
mmc_t **buckets; // 哈希桶,和权重值相关
int num_buckets; // 哈系桶的数量
mmc_hash_function hash; // hash算法
} mmc_standard_state_t;

void *mmc_standard_create_state(mmc_hash_function hash) /* {{{ */
{
// 初始化状态
mmc_standard_state_t *state = emalloc(sizeof(mmc_standard_state_t));
memset(state, 0, sizeof(mmc_standard_state_t));
// 选择的hash函数赋给hash属性
state->hash = hash;
return state;
}

1,解析INI配置文件

由于php.ini需要在SAPI过程中一直生效,那么解析ini文件并据此来构建php配置的工作,必定是发生SAPI的一开始。换句话说,也就是必定发生在php的启动过程中。php需要任意一个实际的请求到达之前,其内部已经生成好这些配置。

反映到php的内核,即为php_module_startup函数。

php_module_startup主要负责对php进行启动,通常它会在SAPI开始的时候被调用。btw,还有一个常见的函数是php_request_startup,它负责将在每个请求到来的时刻进行初始化,php_module_startup与php_request_startup是两个标识性的动作,不过对他们进行分析并不在本文的探讨范围内。

举个例子,当php挂接在apache下面做一个module,那么apache启动的时候,便会激活所有这些module,其中包括php
module。在激活php
module时,便会调用到php_module_startup。php_module_startup函数完成了茫茫多的工作,一旦php_module_startup调用结束就意味着,OK,php已经启动,现在可以接受请求并作出响应了。

在php_module_startup函数中,与解析ini文件相关的实现是:

/* this will read in php.ini, set up the configuration parameters,
   load zend extensions and register php function extensions
   to be loaded later */
if (php_init_config(TSRMLS_C) == FAILURE) {
    return FAILURE;
}

可以看到,其实就是调用了php_init_config函数,去完成对ini文件的parse。parse工作主要进行lex&grammar分析,并将ini文件中的key、value键值对提取出来并保存。php.ini的格式很简单,等号左侧为key,右侧为value。每当一对kv被提取出来之后,php将它们存储到哪儿呢?答案就是之前提到的configuration_hash。

static HashTable configuration_hash;

configuration_hash声明在php_ini.c中,它是一个HashTable类型的数据结构。顾名思义,其实就是张hash表。题外话,在php5.3之前的版本是没法获取configuration_hash的,因为它是php_ini.c文件的一个static的变量。后来php5.3添加了php_ini_get_configuration_hash接口,该接口直接返回&configuration_hash,使
得php各个扩展可以方便的一窥configuration_hash全貌…真是普大喜奔…

注意四点:

第一,php_init_config不会做除了词法语法以外的任何校验。也就是说,假如我们在ini文件中添加一行
hello=world,只要这是一个格式正确的配置项,那么最终configuration_hash中就会包含一个键为hello、值为world的元素,configuration_hash最大限度的反映出ini文件。

第二,ini文件允许我们以数组的形式进行配置。例如ini文件中写入以下三行:

drift.arr[]=1
drift.arr[]=2
drift.arr[]=3

那么最终生成的configuration_hash表中,就会存在一个key为drift.arr的元素,其value为一个包含的1,2,3三个数字的数组。这是一种极为罕见的配置方法。

第三,php还允许我们除了默认的php.ini文件(准确说是php-%s.ini)之外,另外构建一些ini文件。这些ini文件会被放入一个额外的目录。该目录由环境变量PHP_INI_SCAN_DIR来指定,当php_init_config解析完了php.ini之后,会再次扫描此目录,然后找出目录中所有.ini文件来分析。这些额外的ini文件中产生的kv键值对,也会被加入到configuration_hash中去。

这是一个偶尔有用的特性,假设我们自己开发php的扩展,却又不想将配置混入php.ini,便可以另外写一份ini,并通过PHP_INI_SCAN_DIR告诉php该去哪儿找到它。当然,其缺点也显而易见,其需要设置额外的环境变量来支持。更好的解决办法是,开发者在扩展中自己调用php_parse_user_ini_file或zend_parse_ini_file去解析对应的ini文件。

第四,在configuration_hash中,key是字符串,那么值的类型是什么?答案也是字符串(除了上述很特殊的数组)。具体来说,比如下面的配置:

display_errors = On
log_errors = Off
log_errors_max_len = 1024

 那么最后configuration_hash中实际存放的键值对为:

key: "display_errors"
val : "1"

key: "log_errors"
val : ""

key: "log_errors_max_len"
val : "1024"

注意log_errors,其存放的值连”0″都不是,就是一个实实在在地空字符串。另外,log_errors_max_len也并非数字,而是字符串1024。

分析至此,基本上解析ini文件相关的内容都说清楚了。简单总结一下:

1,解析ini发生在php_module_startup阶段

2,解析结果存放在configuration_hash里。

希望本文所述对大家的php程序设计有所帮助。

php_trim函数先调用了php_charmask,这个函数试将过滤字符设置为mask[char]

1的形式,这样就是一个哈希数组,然后可用于后面的判断。如果第二个参数是范围值时,调用了memset函数给mask数组赋值。

根据源码提炼出了以下的小demo:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void php_charmask(unsigned char *input, size_t len, char *mask);
char *rtrim(char *str,char *character_mask);

int main(int argc, char const *argv[])
{
    printf("%sn",rtrim("e?type","?type"));    
    return 0;
}

char *rtrim(char *str,char *character_mask)
{
    char *res;
    char mask[256];
    register size_t i;

    size_t len = strlen(str);

    php_charmask((unsigned char*)character_mask, strlen(character_mask), mask);

    if (len > 0) {
        i = len - 1;
        do {
            if (mask[(unsigned char)str[i]]) {
                len--;
            } else {
                break;
            }
        } while (i-- != 0);
    }

    res = (char *) malloc(sizeof(char) * (len+1));
    memcpy(res,str,len);

    return res;
}

void php_charmask(unsigned char *input, size_t len, char *mask)
{
    unsigned char *end;
    unsigned char c;

    memset(mask, 0, 256);

    for (end = input+len; input < end; input++) {
        c = *input;
        mask[c]= 1;
    }
}

在php_charmask中,构造了一个简单的hash数组

云顶娱乐平台 1

image.png

在最上面的例子中,?type分别落到了这个数组中的不同位置(这也就是为什么mask的大小要设置成256,和ASCII的数量一致)。

所以在最上面的例子中,从后往前e,p,y,t,?,e都命中了hash。

具体fnv算法的深入实现可以参考Fowler–Noll–Vo hash
function

2.2,如何确定一个模块需要哪些配置呢?

模块需要什么样的INI配置,都是在各个模块中自己定义的。举例来说,对于Core模块,有如下的配置项定义:

PHP_INI_BEGIN()
    ......
    STD_PHP_INI_ENTRY_EX("display_errors", "1", PHP_INI_ALL,    OnUpdateDisplayErrors, display_errors, php_core_globals, core_globals, display_errors_mode)
    STD_PHP_INI_BOOLEAN("enable_dl",       "1", PHP_INI_SYSTEM, OnUpdateBool,          enable_dl,      php_core_globals, core_globals)
    STD_PHP_INI_BOOLEAN("expose_php",      "1", PHP_INI_SYSTEM, OnUpdateBool,          expose_php,     php_core_globals, core_globals)
    STD_PHP_INI_BOOLEAN("safe_mode",       "0", PHP_INI_SYSTEM, OnUpdateBool,          safe_mode,      php_core_globals, core_globals)
    ......
PHP_INI_END()

可以在php-srcmainmain.c文件大概450+行找到上述代码。其中涉及的宏比较多,有ZEND_INI_BEGIN
、ZEND_INI_END、PHP_INI_ENTRY_EX、STD_PHP_INI_BOOLEAN等等,本文不一一赘述,感兴趣的读者可自行分析。

上述代码进行宏展开后得到:

static const zend_ini_entry ini_entries[] = {
    ..
    { 0, PHP_INI_ALL,    "display_errors",sizeof("display_errors"),OnUpdateDisplayErrors,(void *)XtOffsetOf(php_core_globals, display_errors), (void *)&core_globals, NULL, "1", sizeof("1")-1, NULL, 0, 0, 0, display_errors_mode },
    { 0, PHP_INI_SYSTEM, "enable_dl",     sizeof("enable_dl"),     OnUpdateBool,         (void *)XtOffsetOf(php_core_globals, enable_dl),      (void *)&core_globals, NULL, "1", sizeof("1")-1, NULL, 0, 0, 0, zend_ini_boolean_displayer_cb },
    { 0, PHP_INI_SYSTEM, "expose_php",    sizeof("expose_php"),    OnUpdateBool,         (void *)XtOffsetOf(php_core_globals, expose_php),     (void *)&core_globals, NULL, "1", sizeof("1")-1, NULL, 0, 0, 0, zend_ini_boolean_displayer_cb },
    { 0, PHP_INI_SYSTEM, "safe_mode",     sizeof("safe_mode"),     OnUpdateBool,         (void *)XtOffsetOf(php_core_globals, safe_mode),      (void *)&core_globals, NULL, "0", sizeof("0")-1, NULL, 0, 0, 0, zend_ini_boolean_displayer_cb },
    ...
    { 0, 0, NULL, 0, NULL, NULL, NULL, NULL, NULL, 0, NULL, 0, 0, 0, NULL }
};

我们看到,配置项的定义,其本质上就是定义了一个zend_ini_entry类型的数组。zend_ini_entry结构体的字段具体含义为:

struct _zend_ini_entry {
    int module_number;                // 模块的id
    int modifiable;                   // 可被修改的范围,例如php.ini,ini_set
    char *name;                       // 配置项的名称
    uint name_length;
    ZEND_INI_MH((*on_modify));        // 回调函数,配置项注册或修改的时候会调用
    void *mh_arg1;                    // 通常为配置项字段在XXX_G中的偏移量
    void *mh_arg2;                    // 通常为XXX_G
    void *mh_arg3;                    // 通常为保留字段,极少用到

    char *value;                      // 配置项的值
    uint value_length;

    char *orig_value;                 // 配置项的原始值
    uint orig_value_length;
    int orig_modifiable;              // 配置项的原始modifiable
    int modified;                     // 是否发生过修改,如果有修改,则orig_value会保存修改前的值

    void (*displayer)(zend_ini_entry *ini_entry, int type);
};

这里演示php实现的一个简单hash算法,可以用来加密,不过这个函数过于简单,不能用来解密

php底层rtrim的一个“bug”

memcache_delete:/* {{{ proto bool memcache_delete( object memcache, string key [, int expire ])
Deletes existing item */
PHP_FUNCTION(memcache_delete)
{
mmc_t *mmc;
mmc_pool_t *pool;
int result = -1, key_len;
zval *mmc_object = getThis();
char *key;
long time = 0;
char key_tmp[MMC_KEY_MAX_SIZE];
unsigned int key_tmp_len;

if (mmc_object == NULL) {
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Os|l", &mmc_object, memcache_class_entry_ptr, &key, &key_len, &time) == FAILURE) {
return;
}
}
else {
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l", &key, &key_len, &time) == FAILURE) {
return;
}
}

if (!mmc_get_pool(mmc_object, &pool TSRMLS_CC) || !pool->num_servers) {
RETURN_FALSE;
}

if (mmc_prepare_key_ex(key, key_len, key_tmp, &key_tmp_len TSRMLS_CC) != MMC_OK) {
RETURN_FALSE;
}

// 先获得服务器资源
while (result < 0 && (mmc = mmc_pool_find(pool, key_tmp, key_tmp_len TSRMLS_CC)) != NULL) {
// 根据资源向缓存服务器发送请求删除存储的数据 
if ((result = mmc_delete(mmc, key_tmp, key_tmp_len, time TSRMLS_CC)) < 0) {
mmc_server_failure(mmc TSRMLS_CC);
}
}

if (result > 0) {
RETURN_TRUE;
}
RETURN_FALSE;
}
/* }}} */

3,总结

至此,三块数据configuration_hash,EG(ini_directives)以及PG、BG、PCRE_G、JSON_G、XXX_G…已经都交代清楚了。

总结一下:

1,configuration_hash,存放php.ini文件里的配置,不做校验,其值为字符串。
2,EG(ini_directives),存放的是各个模块中定义的zend_ini_entry,如果用户在php.ini配置过(configuration_hash中存在),则值被替换为configuration_hash中的值,类型依然是字符串。
3,XXX_G,该宏用于访问模块的全局空间,这块内存空间可用来存放ini配置,并通过on_modify指定的函数进行更新,其数据类型由XXX_G中的字段声明来决定。

 


trim执行步骤

trim、ltrim、rtrim三个函数都是调用了php_do_trim函数,区别在于第二个参数mode的不同。本文主要对trim函数进行分析,ltrim和rtrim函数跟trim的类似。然后php_do_trim会调用了php_trim来实现功能,因此trim函数的核心函数时php_trim函数。其执行步骤如下:

  1. 根据what的值设置保存过滤字符的mask数组
  2. 过滤在字符串首部的待过滤字符
  3. 过滤在字符串尾部的待过滤字符

php_trim函数执行的流程图如下:

云顶娱乐平台 2

image.png

int mmc_exec_retrieval_cmd(mmc_pool_t *pool, const char *key, int key_len, zval **return_value, zval *return_flags TSRMLS_DC) /* {{{ */
{
mmc_t *mmc;
char *command, *value;
int result = -1, command_len, response_len, value_len, flags = 0;

MMC_DEBUG(("mmc_exec_retrieval_cmd: key '%s'", key));

command_len = spprintf(&command, 0, "get %s", key);
// 遍历寻找到key对应的value值
while (result < 0 && (mmc = mmc_pool_find(pool, key, key_len TSRMLS_CC)) != NULL) {
......
}

if (return_flags != NULL) {
zval_dtor(return_flags);
ZVAL_LONG(return_flags, flags);
}

efree(command);
return result;
}
static int mmc_exec_retrieval_cmd_multi(mmc_pool_t *pool, zval *keys, zval **return_value, zval *return_flags TSRMLS_DC) /* {{{ */
{
......
do {
result_status = num_requests = 0;
zend_hash_internal_pointer_reset_ex(Z_ARRVAL_P(keys), &pos);

// 遍历key得到所有key对应的服务器资源存入pool->requests中
while (zend_hash_get_current_data_ex(Z_ARRVAL_P(keys), (void **)&zkey, &pos) == SUCCESS) {
if (mmc_prepare_key(*zkey, key, &key_len TSRMLS_CC) == MMC_OK) {
/* schedule key if first round or if missing from result */
if ((!i || !zend_hash_exists(Z_ARRVAL_PP(return_value), key, key_len)) &&
// 根据key寻找到服务器
(mmc = mmc_pool_find(pool, key, key_len TSRMLS_CC)) != NULL) {
if (!(mmc->outbuf.len)) {
smart_str_appendl(&(mmc->outbuf), "get", sizeof("get")-1);
pool->requests[num_requests++] = mmc;
}

smart_str_appendl(&(mmc->outbuf), " ", 1);
smart_str_appendl(&(mmc->outbuf), key, key_len);
MMC_DEBUG(("mmc_exec_retrieval_cmd_multi: scheduled key '%s' for '%s:%d' request length '%d'", key, mmc->host, mmc->port, mmc->outbuf.len));
}
}

zend_hash_move_forward_ex(Z_ARRVAL_P(keys), &pos);
}

......

} while (result_status < 0 && MEMCACHE_G(allow_failover) && i++ < MEMCACHE_G(max_failover_attempts));

......

return result_status;
}

2,配置作用到模块

php的大致结构可以看成是最下层有一个zend引擎,它负责与OS进行交互、编译php代码、提供内存托管等等,在zend引擎的上层,排列着很多很多的模块。其中最核心的就一个Core模块,其他还有比如Standard,PCRE,Date,Session等等…这些模块还有另一个名字叫php扩展。我们可以简单理解为,每个模块都会提供一组功能接口给开发者来调用,举例来说,常用的诸如explode,trim,array等内置函数,便是由Standard模块提供的。

为什么需要谈到这些,是因为在php.ini里除了针对php自身,也就是针对Core模块的一些配置(例如safe_mode,display_errors,max_execution_time等),还有相当多的配置是针对其他不同模块的。

例如,date模块,它提供了常见的date,
time,strtotime等函数。在php.ini中,它的相关配置形如:

[Date]
;date.timezone = 'Asia/Shanghai'
;date.default_latitude = 31.7667
;date.default_longitude = 35.2333
;date.sunrise_zenith = 90.583333
;date.sunset_zenith = 90.583333

除了这些模块拥有独立的配置,zend引擎也是可配的,只不过zend引擎的可配项非常少,只有error_reporting,zend.enable_gc和detect_unicode三项。

在上一小节中我们已经谈到,php_module_startup会调用php_init_config,其目的是解析ini文件并生成configuration_hash。那么接下来在php_module_startup中还会做什么事情呢?很显然,就是会将configuration_hash中的配置作用于Zend,Core,Standard,SPL等不同模块。当然这并非一个一蹴而就的过程,因为php通常会包含有很多模块,php启动的过程中这些模块也会依次进行启动。那么,对模块A进行配置的过程,便是发生在模块A的启动过程中。

有扩展开发经验的同学会直接指出,模块A的启动不就是在PHP_MINIT_FUNCTION(A)中么?

是的,如果模块A需要配置,那么在PHP_MINIT_FUNCTION中,可以调用REGISTER_INI_ENTRIES()来完成。REGISTER_INI_ENTRIES会根据当前模块所需要的配置项名称,去configuration_hash查找用户设置的配置值,并更新到模块自己的全局空间中。

执行步骤

typedef struct mmc_hash {
mmc_hash_create_state create_state; // 创建hash策略状态,主要是接纳了hash函数算法
mmc_hash_free_state free_state; // 释放hash策略状态
mmc_hash_find_server find_server; // 根据key和分布式算法定位到某台服务器
mmc_hash_add_server add_server; // 根据hash策略、算法以及权重值添加服务器资源
} mmc_hash_t;

2.1,模块的全局空间

要理解如何将ini配置从configuration_hash作用到各个模块之前,有必要先了解一下php模块的全局空间。对于不同的php模块,均可以开辟一块属于自己的存储空间,并且这块空间对于该模块来说,是全局可见的。一般而言,它会被用来存放该模块所需的ini配置。也就是说,configuration_hash中的配置项,最终会被存放到该全局空间中。在模块的执行过程中,只需要直接访问这块全局空间,就可以拿到用户针对该模块进行的设置。当然,它也经常被用来记录模块在执行过程中的中间数据。

我们以bcmath模块来举例说明,bcmath是一个提供数学计算方面接口的php模块,首先我们来看看它有哪些ini配置:

PHP_INI_BEGIN()
    STD_PHP_INI_ENTRY("bcmath.scale", "0", PHP_INI_ALL, OnUpdateLongGEZero, bc_precision, zend_bcmath_globals, bcmath_globals)
PHP_INI_END()

bcmath只有一个配置项,我们可以在php.ini中用bcmath.scale来配置bcmath模块。

接下来继续看看bcmatch模块的全局空间定义。在php_bcmath.h中有如下声明:

ZEND_BEGIN_MODULE_GLOBALS(bcmath)
    bc_num _zero_;
    bc_num _one_;
    bc_num _two_;
    long bc_precision;
ZEND_END_MODULE_GLOBALS(bcmath)

 宏展开之后,即为:

typedef struct _zend_bcmath_globals {
    bc_num _zero_;
    bc_num _one_;
    bc_num _two_;
    long bc_precision;
} zend_bcmath_globals;

其实,zend_bcmath_globals类型就是bcmath模块中的全局空间类型。这里仅仅声明了zend_bcmath_globals结构体,在bcmath.c中还有具体的实例化定义:

// 展开后即为zend_bcmath_globals bcmath_globals;
ZEND_DECLARE_MODULE_GLOBALS(bcmath) 

可以看出,用ZEND_DECLARE_MODULE_GLOBALS完成了对变量bcmath_globals的定义。

bcmath_globals是一块真正的全局空间,它包含有四个字段。其最后一个字段bc_precision,对应于ini配置中的bcmath.scale。我们在php.ini中设置了bcmath.scale的值,随后在启动bcmath模块的时候,bcmath.scale的值被更新到bcmath_globals.bc_precision中去。

把configuration_hash中的值,更新到各个模块自己定义的xxx_globals变量中,就是所谓的将ini配置作用到模块。一旦模块启动完成,那么这些配置也都作用到位。所以在随后的执行阶段,php模块无需再次访问configuration_hash,模块仅需要访问自己的XXX_globals,就可以拿到用户设定的配置。

bcmath_globals,除了有一个字段为ini配置项,其他还有三个字段为何意?这就是模块全局空间的第二个作用,它除了用于ini配置,还可以存储模块执行过程中的一些数据。

云顶娱乐平台 3

再例如json模块,也是php中一个很常用的模块:

ZEND_BEGIN_MODULE_GLOBALS(json)
    int error_code;
ZEND_END_MODULE_GLOBALS(json)

可以看到json模块并不需要ini配置,它的全局空间只有一个字段error_code。error_code记录了上一次执行json_decode或者json_encode中发生的错误。json_last_error函数便是返回这个error_code,来帮助用户定位错误原因。

为了能够很便捷的访问模块全局空间变量,php约定俗成的提出了一些宏。比如我们想访问json_globals中的error_code,当然可以直接写做json_globals.error_code(多线程环境下不行),不过更通用的写法是定义JSON_G宏:

#define JSON_G(v) (json_globals.v)

我们使用JSON_G(error_code)来访问json_globals.error_code。本文刚开始的时候,曾提到PG、BG、JSON_G、PCRE_G,XXX_G等等,这些宏在php源代码中也是很常见的。现在我们可以很轻松的理解它们,PG宏可以访问Core模块的全局变量,BG访问Standard模块的全局变量,PCRE_G则访问PCRE模块的全局变量。

#define PG(v) (core_globals.v)
#define BG(v) (basic_globals.v)

源码

看一下php的底层实现:

云顶娱乐平台 4

image.png

这个是php7 trim系列函数的源码,红框内就是rtrim的代码。

云顶娱乐平台 5

image.png

这个是php_charmask的源码。

void mmc_standard_add_server(void *s, mmc_t *mmc, unsigned int weight) /* {{{ */
{
mmc_standard_state_t *state = s;
int i;

// 哈希桶初始化或重新分配相应的权重数值对应的空间
if (state->num_buckets) {
state->buckets = erealloc(state->buckets, sizeof(mmc_t *) * (state->num_buckets + weight));
}
else {
state->buckets = emalloc(sizeof(mmc_t *) * (weight));
}
// 在某个区间内为哈希桶赋予服务器状态
for (i=0; i<weight; i++) {
buckets[state->num_buckets + i] = mmc;
}

state->num_buckets += weight;
state->num_servers++;
}

因此本文打算分两篇,第一篇阐述php.ini配置原理,第二篇讲动态修改php配置。

源码解读

前段时间,因为一个项目的关系,研究了php通过调用memcache和memcached
PECL扩展库的接口存储到分布式缓存服务器的机制,在此做我根据他们各自的源码进行分析,希望能对这方面感兴趣的人有些帮助。
本篇文章我会针对php和memcache扩展库的交互根据源码展开分析。
PHP调用memcache的接口通常会是如下过程:

php.ini的配置大致会涉及到三块数据,configuration_hash,EG(ini_directives)以及PG、BG、PCRE_G、JSON_G、XXX_G等。如果不清楚这三种数据的含义也没有关系,下文会详细解释。

由上面的分析可知,set方法对应的是memcache_set函数:

这篇文章不会详细叙述某个ini配置项的用途,这些在手册上已经讲解的面面俱到。我只是想从某个特定的角度去挖掘php的实现机制,会涉及到一些php内核方面的知识:-)

至此,memcache_add_server中mmc_pool_new函数流程结束,接着来看mmc_pool_add函数:

使用php的同学都知道php.ini配置的生效会贯穿整个SAPI的生命周期。在一段php脚本的执行过程中,如果手动修改ini配置,是不会启作用的。此时如果无法重启Apache或者nginx等,那么就只能显式的在php代码中调用ini_set接口。ini_set是php向我们提供的一个动态修改配置的函数,需要注意的是,利用ini_set所设置的配置与ini文件中设置的配置,其生效的时间范围并不相同。在php脚本执行结束之后,ini_set的设置便会随即失效。

在持久化hash模式下,对应的是mmc_consistent_add_server函数:

typedef struct mmc_pool {
mmc_t **servers; // 所有服务器的状态
int num_servers; // 服务器数量
mmc_t **requests; // 根据get的array key请求顺序返回的服务器数组状态
int compress_threshold; // 待存储的数据压缩的下限值
double min_compress_savings; // 待存储的数据最小的压缩百分比
zend_bool in_free; // 标记该pool是否被释放
mmc_hash_t *hash; // hash策略容器
void *hash_state; // hash函数
} mmc_pool_t;
switch (MEMCACHE_G(hash_function)) {
case MMC_HASH_FNV1A:
hash = &mmc_hash_fnv1a; // 采用fnv1a算法
break;
default:
hash = &mmc_hash_crc32; // 采用crc32算法
}
// hash策略中根据选择的hash函数创建对应的状态
pool->hash_state = pool->hash->create_state(hash);
}
static unsigned int mmc_hash_crc32(const char *key, int key_len) /* CRC32 hash {{{ */
{
unsigned int crc = ~0;
int z;

for (z=0; z<key_len; z++) {
CRC32(crc, key[z]);
}

return ~crc;
}
mmc_hash_t mmc_consistent_hash = {
mmc_consistent_create_state,
mmc_consistent_free_state,
mmc_consistent_find_server,
mmc_consistent_add_server
};

对应PHP的代码:

4. 向缓存服务器获得已保存的数据

对应PHP的代码:

由上面的分析可知,get方法对应的是memcache_get函数:

然后来看看fnv算法实现:

$mmc = new Memcache();

有关CRC32再深入的实现可以参考Cyclic redundancy
check

1. Memcache的初始化
对应PHP的代码:

mmc_consistent_find_servermmc_t *mmc_consistent_find_server(void *s, const char *key, int key_len TSRMLS_DC) /* {{{ */
{
mmc_consistent_state_t *state = s;
mmc_t *mmc;

if (state->num_servers > 1) {
unsigned int i, hash = state->hash(key, key_len);
// 如果哈希桶没有进行过排序,则进行圆环排序操作
if (!state->buckets_populated) {
mmc_consistent_populate_buckets(state);
}
mmc = state->buckets[hash % MMC_CONSISTENT_BUCKETS];

// 如果获取到的服务器状态有问题,则重新hash遍历寻找到可用的缓存服务器为止 
for (i=0; !mmc_open(mmc, 0, NULL, NULL TSRMLS_CC) && MEMCACHE_G(allow_failover) && i<MEMCACHE_G(max_failover_attempts); i++) {
char *next_key = emalloc(key_len + MAX_LENGTH_OF_LONG + 1);
int next_len = sprintf(next_key, "%s-%d", key, i);
MMC_DEBUG(("mmc_consistent_find_server: failed to connect to server '%s:%d' status %d, trying next", mmc->host, mmc->port, mmc->status));

hash = state->hash(next_key, next_len);
mmc = state->buckets[hash % MMC_CONSISTENT_BUCKETS];

efree(next_key);
}
}
else {
mmc = state->points[0].server;
mmc_open(mmc, 0, NULL, NULL TSRMLS_CC);
}

return mmc->status != MMC_STATUS_FAILED ? mmc : NULL;
}
// 持久化哈希算法的核心部分
static void mmc_consistent_populate_buckets(mmc_consistent_state_t *state) /* {{{ */
{
unsigned int z, step = 0xffffffff / MMC_CONSISTENT_BUCKETS;

qsort((void *)state->points, state->num_points, sizeof(mmc_consistent_point_t), mmc_consistent_compare);
for (z=0; z<MMC_CONSISTENT_BUCKETS; z++) {
state->buckets[z] = mmc_consistent_find(state, step * z);
}

state->buckets_populated = 1;
}
static int mmc_consistent_compare(const void *a, const void *b) /* {{{ */
{
if (((mmc_consistent_point_t *)a)->point < ((mmc_consistent_point_t *)b)->point) {
return -1;
}
if (((mmc_consistent_point_t *)a)->point > ((mmc_consistent_point_t *)b)->point) {
return 1;
}
return 0;
}
static mmc_t *mmc_consistent_find(mmc_consistent_state_t *state, unsigned int point) /* {{{ */
{
int lo = 0, hi = state->num_points - 1, mid;

while (1) {
/* point is outside interval or lo >= hi, wrap-around */
if (point <= state->points[lo].point || point > state->points[hi].point) {
return state->points[lo].server;
}

/* test middle point */
mid = lo + (hi - lo) / 2;
MMC_DEBUG(("mmc_consistent_find: lo %d, hi %d, mid %d, point %u, midpoint %u", lo, hi, mid, point, state->points[mid].point));

/* perfect match */
if (point <= state->points[mid].point && point > (mid ? state->points[mid-1].point : 0)) {
return state->points[mid].server;
}

/* too low, go up */
if (state->points[mid].point < point) {
lo = mid + 1;
}
else {
hi = mid - 1;
}
}
}

由上面的说明可知add_server在标准hash模式下对应mmc_standard_add_server函数:

根据上面的两个switch可以知道,在create_state的时候,是有两种策略选择的可能性,接着传入的hash参数也存在两种可能性,这里我先分析标准hash存储策略,以及对应的两种hash算法,然后再分析持久化hash策略。
先看下mmc_consistent_hash结构://
根据mmc_hash_t的定义包含了四种具体函数实现

typedef struct mmc_hash {
mmc_hash_create_state create_state; // 创建hash策略状态,主要是接纳了hash函数算法
mmc_hash_free_state free_state; // 释放hash策略状态
mmc_hash_find_server find_server; // 根据key和分布式算法定位到某台服务器
mmc_hash_add_server add_server; // 根据hash策略、算法以及权重值添加服务器资源
} mmc_hash_t;
$mmc->delete('key');

接着看mmc_exec_retrieval_cmd和mmc_exec_retrieval_cmd_multi函数:

PHP_FUNCTION(memcache_get)
{
......
// 获得pool
if (!mmc_get_pool(mmc_object, &pool TSRMLS_CC) || !pool->num_servers) {
RETURN_FALSE;
}
// 当key不为数组的情况下处理
if (Z_TYPE_P(zkey) != IS_ARRAY) {
// 检查key的合法性
if (mmc_prepare_key(zkey, key, &key_len TSRMLS_CC) == MMC_OK) {
// 获取key获取value
if (mmc_exec_retrieval_cmd(pool, key, key_len, &return_value, flags TSRMLS_CC) < 0) {
zval_dtor(return_value);
RETVAL_FALSE;
}
}
else {
RETVAL_FALSE;
}
// 为数组的情况下处理
} else if (zend_hash_num_elements(Z_ARRVAL_P(zkey))){
//根据数据key获取数组值
if (mmc_exec_retrieval_cmd_multi(pool, zkey, &return_value, flags TSRMLS_CC) < 0) {
zval_dtor(return_value);
RETVAL_FALSE;
}
} else {
RETVAL_FALSE;
}
}
static zend_function_entry php_memcache_class_functions[] = {
PHP_FALIAS(addserver, memcache_add_server, NULL)
PHP_FALIAS(set, memcache_set, NULL)
PHP_FALIAS(get, memcache_get, NULL)
PHP_FALIAS(delete, memcache_delete, NULL)
......
};
PHP_MINIT_FUNCTION(memcache)
{
// 初始化Memcache类实体,给类定在php空间中的调用名称以及类所拥有的方法
zend_class_entry memcache_class_entry;
INIT_CLASS_ENTRY(memcache_class_entry, "Memcache", php_memcache_class_functions);
memcache_class_entry_ptr = zend_register_internal_class(&memcache_class_entry TSRMLS_CC);
......
}

以上代码有持久化hash算法的赋值实现,具体深入的了解请看Consistent
hashing和国内大侠charlee翻译的小日本的文章memcached全面剖析–PDF总结篇。
Consistent hashing
算法最大的特点是当你的缓存服务器数量变更的时候,它能够最大化的保留原有的缓存不变,而不需要重新分布原有缓存的服务器位置。
至此,整个memcache_add_server流程结束。
3. 向缓存服务器保存数据

echo $mmc->get('key');

至此,memcache_delete过程结束。

/* 32 bit magic FNV-1a prime and init */
#define FNV_32_PRIME 0x01000193
#define FNV_32_INIT 0x811c9dc5
static unsigned int mmc_hash_fnv1a(const char *key, int key_len) /* FNV-1a hash {{{ */
{
unsigned int hval = FNV_32_INIT;
int z;

for (z=0; z<key_len; z++) {
hval ^= (unsigned int)key[z];
hval *= FNV_32_PRIME;
}

return hval;
}

由之前的分析可知,delete对应的为

再看

mmc_hash_t mmc_standard_hash = {
mmc_standard_create_state,
mmc_standard_free_state,
mmc_standard_find_server,
mmc_standard_add_server
};

最后我们看看mmc_consistent_hash结构:

crc的算法实现:

static void mmc_pool_init_hash(mmc_pool_t *pool TSRMLS_DC) /* {{{ */
{
mmc_hash_function hash;// 初始化hash函数
// 根据php.ini中的memcache.hash_strategy配置选择hash存储策略,默认为标准hash存储策略
switch (MEMCACHE_G(hash_strategy)) {
case MMC_CONSISTENT_HASH:
pool->hash = &mmc_consistent_hash;// 采用持久化hash存储策略
break;
default:
pool->hash = &mmc_standard_hash;// 采用标准hash存储策略
}

看php_mmc_store函数:

对应PHP的代码:

以上过程是在Module
Initialization的环节已经做好,在new的过程中,并无其余处理。
2. 添加缓存服务器,使之成为分布式存储

对应C的代码:// Memcache类对应的方法名已经实际在c中实现过程的函数名,在接下来的分析中会用到。忽略不会分析到的方法。

//
根据php.ini中的memcache.hash_function配置选择hash函数,默认为crc32算法

/* {{{ proto bool memcache_set( object memcache, string key, mixed var [, int flag [, int expire ] ] )
Sets the value of an item. Item may exist or not */
PHP_FUNCTION(memcache_set)
{
// Memcache对象中的add,set和replace皆会走该函数
php_mmc_store(INTERNAL_FUNCTION_PARAM_PASSTHRU, "set", sizeof("set") - 1);
}

由上面的php_memcache_class_functions结构可以看出,addServer方法对应的是memcache_add_server函数,因此对应C的代码:

然后我们看下mmc_hash_t的结构,再接下去的分析中会用到://
结构定义中包含了四种抽象函数,作为基本结构,用于定义子结构

来自:

void mmc_pool_add(mmc_pool_t *pool, mmc_t *mmc, unsigned int weight) /* {{{ */
{
/* add server and a preallocated request pointer */
if (pool->num_servers) {
pool->servers = erealloc(pool->servers, sizeof(mmc_t *) * (pool->num_servers + 1));
pool->requests = erealloc(pool->requests, sizeof(mmc_t *) * (pool->num_servers + 1));
}
else {
pool->servers = emalloc(sizeof(mmc_t *));
pool->requests = emalloc(sizeof(mmc_t *));
}

pool->servers[pool->num_servers] = mmc;
pool->num_servers++;
// 根据pool状态,当前要添加的服务器状态和权重调用add_server函数
pool->hash->add_server(pool->hash_state, mmc, weight);
}
static void php_mmc_store(INTERNAL_FUNCTION_PARAMETERS, char *command, int command_len) /* {{{ */
{
mmc_pool_t *pool;
......
// 获得pool
if (!mmc_get_pool(mmc_object, &pool TSRMLS_CC) || !pool->num_servers) {
RETURN_FALSE;
}
// 对不同的存储的值类型进行不同的处理
switch (Z_TYPE_P(value)) {
// 字符串类型
case IS_STRING:
result = mmc_pool_store(
pool, command, command_len, key_tmp, key_tmp_len, flags, expire, 
Z_STRVAL_P(value), Z_STRLEN_P(value) TSRMLS_CC);
break;
// 长整型,浮点型,布尔型
case IS_LONG:
case IS_DOUBLE:
case IS_BOOL: {
......
result = mmc_pool_store(
pool, command, command_len, key_tmp, key_tmp_len, flags, expire, 
Z_STRVAL(value_copy), Z_STRLEN(value_copy) TSRMLS_CC);

zval_dtor(&value_copy);
break;
}
// 默认为数组类型
default: {
......
result = mmc_pool_store(
pool, command, command_len, key_tmp, key_tmp_len, flags, expire, 
buf.c, buf.len TSRMLS_CC);
}
}
......
}
<?php
$mmc = new Memcache();
$mmc->addServer('node1', 11211);
$mmc->addServer('node2', 11211, MemcacheConfig::MEMCACHE_PERSISTENT, 2);
$mmc->set('key', 'value');
echo $mmc->get('key');
$mmc->delete('key');

现在初始化hash算法已经逐渐显露,继续追踪mmc_pool_init_hash函数:

#define MMC_CONSISTENT_POINTS 160 /* points per server */

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;

/* buffer for "host:port-i" */
char *key = emalloc(strlen(mmc->host) + MAX_LENGTH_OF_LONG * 2 + 3);

/* add weight * MMC_CONSISTENT_POINTS number of points for this server */
state->points = erealloc(state->points, sizeof(mmc_consistent_point_t) * (state->num_points + points));

// 将区块内的server赋予当前服务器状态,point赋予hash函数处理后的值
for (i=0; i<points; i++) {
key_len = sprintf(key, "%s:%d-%d", mmc->host, mmc->port, i);
state->points[state->num_points + i].server = mmc;
state->points[state->num_points + i].point = state->hash(key, key_len);
MMC_DEBUG(("mmc_consistent_add_server: key %s, point %lu", key, state->points[state->num_points + i].point));
}

state->num_points += points;
state->num_servers++;

// 新增加服务器后需重新计算buckets顺序
state->buckets_populated = 0;

efree(key);
}
$mmc->set('key', 'value');
int mmc_pool_store(mmc_pool_t *pool, const char *command, int command_len, const char *key, int key_len, int flags, int expire, const char *value, int value_len TSRMLS_DC) /* {{{ */
{
/* 该省略过程处理数据压缩,处理待发送的请求数据 */
......

// 通过key确定待保存的服务器
while (result < 0 && (mmc = mmc_pool_find(pool, key, key_len TSRMLS_CC)) != NULL) {
// 向缓存服务器发送请求,保存数据
if ((result = mmc_server_store(mmc, request, request_len TSRMLS_CC)) < 0) {
mmc_server_failure(mmc TSRMLS_CC);
}
}

if (key_copy != NULL) {
efree(key_copy);
}
if (data != NULL) {
efree(data);
}
efree(request);
return result;
}

由上代码可以看出,存储数据主要是交由mmc_pool_store处理:

至此,memcache_set过程结束。

#define mmc_pool_find(pool, key, key_len) 
pool->hash->find_server(pool->hash_state, key, key_len)
mmc_standard_find_servermmc_t *mmc_standard_find_server(void *s, const char *key, int key_len TSRMLS_DC) /* {{{ */
{
mmc_standard_state_t *state = s;
mmc_t *mmc;

if (state->num_servers > 1) {
// 用设定的hash函数算法,找到对应的服务器
unsigned int hash = mmc_hash(state, key, key_len), i;
mmc = state->buckets[hash % state->num_buckets];

// 如果获取到的服务器状态有问题,则重新hash遍历寻找到可用的缓存服务器为止 
for (i=0; !mmc_open(mmc, 0, NULL, NULL TSRMLS_CC) && MEMCACHE_G(allow_failover) && i<MEMCACHE_G(max_failover_attempts); i++) {
char *next_key = emalloc(key_len + MAX_LENGTH_OF_LONG + 1);
int next_len = sprintf(next_key, "%d%s", i+1, key);
MMC_DEBUG(("mmc_standard_find_server: failed to connect to server '%s:%d' status %d, trying next", mmc->host, mmc->port, mmc->status));

hash += mmc_hash(state, next_key, next_len);
mmc = state->buckets[hash % state->num_buckets];

efree(next_key);
}
}
else {
mmc = state->buckets[0];
mmc_open(mmc, 0, NULL, NULL TSRMLS_CC);
}

return mmc->status != MMC_STATUS_FAILED ? mmc : NULL;
}
/* number of precomputed buckets, should be power of 2 */
#define MMC_CONSISTENT_BUCKETS 1024

typedef struct mmc_consistent_point {
mmc_t *server; // 服务器状态
unsigned int 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 hash; // hash函数
} mmc_consistent_state_t;

void *mmc_consistent_create_state(mmc_hash_function hash) /* {{{ */
{
// 初始化state
mmc_consistent_state_t *state = emalloc(sizeof(mmc_consistent_state_t));
memset(state, 0, sizeof(mmc_consistent_state_t));
// 将hash函数赋值给hash属性
state->hash = hash;
return state;
}

一样是四个函数,看下对应的create_state中的mmc_consistent_create_state的实现:

由上可知,pool->hash->create_state的函数调用实际是对mmc_standard_create_state的函数调用,继续看mmc_standard_create_state函数代码的实现:

PHP_FUNCTION(memcache_add_server)
{
zval **connection, *mmc_object = getThis(), *failure_callback = NULL;
// 整个Memcache中最重要的一个结构mmc_pool_t
mmc_pool_t *pool;
// 当前新添服务器的结构变量
mmc_t *mmc;
......
// 如果pool之前没有初始化过,则初始化
if (zend_hash_find(Z_OBJPROP_P(mmc_object), "connection", sizeof("connection"), (void **) &connection) == FAILURE) {
// 调用mmp_pool_new完成初始化
pool = mmc_pool_new(TSRMLS_C);
......
}
else {
......
}
//将新增服务器添加到pool中
mmc_pool_add(pool, mmc, weight);
RETURN_TRUE;
}

原来是再次多态调用了find_server函数,由之前的分析可以得知find_server在标准hash模式中的函数为mmc_standard_find_server,在持久化hash模式中的函数为mmc_consistent_find_server,一样先看

由上可见分布式hash的核心函数皆为mmc_pool_find,首先找到key对应的服务器资源,然后根据服务器资源请求数据。
至此,memcache_get的过程结束。
5.向缓存服务器删除已保存的数据
对应的php代码:

$mmc->addServer('node1', 11211);
$mmc->addServer('node2', 11211, MemcacheConfig::MEMCACHE_PERSISTENT, 2);

短短几行代码,一个缓存key的生命周期就已经完整层现。从Memcache的初始化,到addServer添加两个服务器节点,接着set一个key到服务器上,然后get到这个key输出,最后delete这个key。在这个生命周期里,Memcache在底层究竟做了哪些事情,保证了数据存储服务器的均匀分布,数据的完整性?
接下来,我会根据上述生命周期的顺序,循序渐进的分析(由于主题是分布式算法的分析,所以接下来不相干的代码我会略去,很多分析我会直接备注在源码上)。

接着我们追踪memcache_add_server函数中的mmc_pool_new函数调用方法:

来看下mmc_pool_t结构的定义: