最近项目中用到PHP连接Memcach的场景。这个场景对于PHP开发者很常见。但就是这么常见的一个场景,带来了意想不到的一个问题。
我这边的php memcached扩展版本是
memcached support | enabled |
---|---|
Version | 2.1.0 |
libmemcached version | 1.0.8 |
Session support | yes |
igbinary support | no |
json support | no |
Directive | Local Value | Master Value |
---|---|---|
memcached.compression_factor | 1.3 | 1.3 |
memcached.compression_threshold | 2000 | 2000 |
memcached.compression_type | fastlz | fastlz |
memcached.serializer | php | php |
memcached.sess_binary | 0 | 0 |
memcached.sess_lock_wait | 150000 | 150000 |
memcached.sess_locking | 1 | 1 |
memcached.sess_prefix | memc.sess.key. | memc.sess.key. |
在本地服务器启动两个memcache实例,端口分别为1122,1123。PHP我使用的是MemcacheD扩展(注意,是D)。测试代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
然后运行这个脚本。正常情况下可以返回
这时候关掉实例1123,再次运行上面的代码,会返回
大家看到这里应该明白出了什么问题,我们上面这段代码无法自动的将fail的server踢出去。这将是一个非常大的隐患。
更麻烦的是,下面这段代码依然无法自动将fail的server去除(使用memcached扩展的默认配置)
1 2 3 4 5 6 7 8 9 | $memcache = new Memcached;
$memcache->addServers(array(array("HOST" => "127.0.0.1", "PORT" => 1122),array("HOST" => "127.0.0.1", "PORT" => 1123));
$mKey = "test_bocheng11";
$status = 2;
$r = $memcache->set($mKey, $status,60*60*24);
$status1 =$memcache->get($mKey);
var_dump($status1);
$memcache->delete($mKey); |
这段代码中我的key=”test_bocheng11″是可以被hash到1122的实例上的,因此我们打开1123实例,关闭1122实例后,会发现返回的是
bool(false)
这个问题最终是看了看libmemcached的源码找到了一个解决方案(我当时的libmemcached的版本是1.0.8,也许高版本的libmemcached已经解决了这个问题)
如下的代码可以解决自动failover的问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
这时只要1122,1123有一个实例正常运行,就可以保证memcache的数据能够存取。
重点是第7–9行的参数设置,但要注意的是,这套参数配置必须配合DISTRIBUTION_CONSISTENT生效,对于memcached扩展默认的DISTRIBUTION_MODULA依然是无法实现failover的。从libmemcached源码中依稀也可以看到一些踪影。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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_DISTRIBUTION_MODULA 选项,不会触发 update_continuum 操作,而这个操作会影响到下面这个方法中是否重新选择host的判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | |
update_continuum 部分代码摘要如下,可以看到判断server是否fail以及修改next_distribution_rebuild的部分。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | if (is_auto_ejecting)
{
live_servers= 0;
ptr->next_distribution_rebuild= 0;
for (host_index= 0; host_index < memcached_server_count(ptr); ++host_index)
{
if (list[host_index].next_retry <= now.tv_sec) live_servers++; else { if (ptr->next_distribution_rebuild == 0 || list[host_index].next_retry < ptr->next_distribution_rebuild)
ptr->next_distribution_rebuild= list[host_index].next_retry;
}
}
}
else
{
live_servers= memcached_server_count(ptr);
} |