memcached深度分析(7)

发表于:2015-07-10来源:uml.org.cn作者:火龙果软件点击数: 标签:数据库
这个扩展是使用php的stream直接连接memcached服务器并通过socket发送命令的。它不像libmemcache那样完善,也不支持add_server这种分布操作,但是因为它不依赖其它

  这个扩展是使用php的stream直接连接memcached服务器并通过socket发送命令的。它不像libmemcache那样完善,也不支持add_server这种分布操作,但是因为它不依赖其它的外界程序,兼容性要好一些,也比较稳定。至于效率,差别不是很大。

  另外,有很多的PHP class可以使用,比如MemcacheClient.inc.php,phpclasses.org上可以找到很多,一般都是对perl client API的再封装,使用方式很像。

  BSM_Memcache

  从C client来说,APR_Memcache是一个很成熟很稳定的client程序,支持线程锁和原子级操作,保证运行的稳定性。不过它是基于APR的(APR将在最后一节介绍),没有libmemcache的应用范围广,目前也没有很多基于它开发的程序,现有的多是一些Apache Module,因为它不能脱离APR环境运行。但是APR倒是可以脱离Apache单独安装的,在APR网站上可以下载APR和APR-util,不需要有Apache,可以直接安装,而且它是跨平台的。

  BSM_Memcache是我在BS.Magic项目中开发的一个基于APR_Memcache的PHP扩展,说起来有点拗口,至少它把APR扯进了PHP扩展中。这个程序很简单,也没做太多的功能,只是一种形式的尝试,它支持服务器分组。

  和mcache扩展支持多服务器分布存储不同,BSM_Memcache支持多组服务器,每一组内的服务器还是按照hash方式来分布保存数据,但是两个组中保存的数据是一样的,也就是实现了热备,它不会因为一台服务器发生单点故障导致数据无法获取,除非所有的服务器组都损坏(例如机房停电)。当然实现这个功能的代价就是性能上的牺牲,在每次添加删除数据的时候都要扫描所有的组,在get数据的时候会随机选择一组服务器开始轮询,一直到找到数据为止,正常情况下一次就可以获取得到。

  BSM_Memcache只支持这几个函数:

zend_function_entry bsm_memcache_functions[] =
{
    PHP_FE(mc_get,          NULL)
    PHP_FE(mc_set,          NULL)
    PHP_FE(mc_del,          NULL)
    PHP_FE(mc_add_group,    NULL)
    PHP_FE(mc_add_server,   NULL)
    PHP_FE(mc_shutdown,     NULL)
    {NULL, NULL, NULL}
};
mc_add_group函数返回一个整形(其实应该是一个object,我偷懒了~_~)作为组ID,
mc_add_server的时候要提供两个参数,一个是组ID,一个是服务器地址(ADDR : PORT)。 
/**
* Add a server group
*/
PHP_FUNCTION(mc_add_group)
{
    apr_int32_t group_id;
    apr_status_t rv;
 
    if (0 != ZEND_NUM_ARGS())
    {
        WRONG_PARAM_COUNT;
        RETURN_NULL();
    }
 
    group_id = free_group_id();
    if (-1 == group_id)
    {
        RETURN_FALSE;
    }
 
    apr_memcache_t *mc;
    rv = apr_memcache_create(p, MAX_G_SERVER, 0, &mc);
 
    add_group(group_id, mc);
 
    RETURN_DOUBLE(group_id);
}
/**
* Add a server into group
*/
PHP_FUNCTION(mc_add_server)
{
    apr_status_t rv;
    apr_int32_t group_id;
    double g;
    char *srv_str;
    int srv_str_l;
    if (2 != ZEND_NUM_ARGS())
    {
        WRONG_PARAM_COUNT;
    }
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ds", &g, &srv_str, &srv_str_l) == FAILURE)
    {
        RETURN_FALSE;
    }
    group_id = (apr_int32_t) g;
 
    if (-1 == is_validate_group(group_id))
    {
        RETURN_FALSE;
    }
    char *host, *scope;
    apr_port_t port;
    rv = apr_parse_addr_port(&host, &scope, &port, srv_str, p);
    if (APR_SUCCESS == rv)
    {
        // Create this server object
        apr_memcache_server_t *st;
        rv = apr_memcache_server_create(p, host, port, 0, 64, 1024, 600, &st);
        if (APR_SUCCESS == rv)
        {
            if (NULL == mc_groups[group_id])
            {
                RETURN_FALSE;
            }
 
            // Add server
            rv = apr_memcache_add_server(mc_groups[group_id], st);
 
            if (APR_SUCCESS == rv)
            {
                RETURN_TRUE;
            }
        }
    }
 
    RETURN_FALSE;
}
在set和del数据的时候,要循环所有的组: 
/**
* Store item into all groups
*/
PHP_FUNCTION(mc_set)
{
    char *key, *value;
    int key_l, value_l;
    double ttl = 0;
    double set_ct = 0;
    if (2 != ZEND_NUM_ARGS())
    {
        WRONG_PARAM_COUNT;
    }
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|d", &key, &key_l, &value, &value_l, ttl) == FAILURE)
    {
        RETURN_FALSE;
    }
    // Write data into every object
    apr_int32_t i = 0;
    if (ttl < 0)
    {
        ttl = 0;
    }
    apr_status_t rv;
 
    for (i = 0; i < MAX_GROUP; i++)
    {
        if (0 == is_validate_group(i))
        {
            // Write it!
            rv = apr_memcache_add(mc_groups[i], key, value, value_l, (apr_uint32_t) ttl, 0);
            if (APR_SUCCESS == rv)
            {
                set_ct++;
            }
        }
    }
 
    RETURN_DOUBLE(set_ct);
}
在mc_get中,首先要随机选择一个组,然后从这个组开始轮询:

/**
* Fetch a item from a random group
*/
PHP_FUNCTION(mc_get)
{               
    char *key, *value = NULL;
    int key_l;
    apr_size_t value_l;
 
    if (1 != ZEND_NUM_ARGS())
    {
        WRONG_PARAM_COUNT;
    }
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &key, &key_l) == FAILURE)
    {
        RETURN_MULL();
    }
    // I will try ...
    // Random read
    apr_int32_t curr_group_id = random_group();
    apr_int32_t i = 0;
    apr_int32_t try = 0;
    apr_uint32_t flag;
    apr_memcache_t *oper;
    apr_status_t rv;
 
    for (i = 0; i < MAX_GROUP; i++)
    {
        try = i + curr_group_id;
        try = try % MAX_GROUP;
        if (0 == is_validate_group(try))
        {
            // Get a value
            oper = mc_groups[try];
            rv = apr_memcache_getp(mc_groups[try], p, (const char *) key, &value, &value_l, 0);
            if (APR_SUCCESS == rv)

            {
                RETURN_STRING(value, 1);
            }
        }
    }
 
    RETURN_FALSE;
}
/**
* Random group id
* For mc_get()
*/
apr_int32_t random_group()
{
    struct timeval tv;
    struct timezone tz;
    int usec;
 
    gettimeofday(&tv, &tz);
 
    usec = tv.tv_usec;
 
    int curr = usec % count_group();
 
    return (apr_int32_t) curr;
}

原文转自:http://www.uml.org.cn/sjjm/201411134.asp