IT技术博客大学习 共学习 共进步

注意!PHP memcached扩展默认配置下无法自动failover

博学无忧 2014-12-01 23:33:56 浏览 2,443 次

   最近项目中用到PHP连接Memcache的场景。这个场景对于PHP开发者很常见。但就是这么常见的一个场景,带来了意想不到的一个问题。

   我这边的php memcached扩展版本是:

   memcached

MEMCACHED SUPPORT	 ENABLED
Version	                 2.1.0
libmemcached version	 1.0.8
Session support	         yes
igbinary support	 no
json support	         no

   案例如下:

   在本地服务器启动两个memcache实例,端口分别为1122,1123。PHP我使用的是MemcacheD扩展(注意,是D)。测试代码如下:

$memcache = new Memcached;
 
$memcache->setOption(Memcached::OPT_DISTRIBUTION, Memcached::DISTRIBUTION_CONSISTENT);
$memcache->setOption(Memcached::OPT_BINARY_PROTOCOL, true);
$memcache->setOption(Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
 
$memcache->setOption(Memcached::OPT_TCP_NODELAY, true); 
$memcache->setOption(Memcached::OPT_NO_BLOCK, true); 
$memcache->setOption(Memcached::OPT_CONNECT_TIMEOUT, 50);
$memcache->setOption(Memcached::OPT_SEND_TIMEOUT, 50);
$memcache->setOption(Memcached::OPT_RECV_TIMEOUT, 50);
$memcache->setOption(Memcached::OPT_POLL_TIMEOUT, 50);
$memcache->setOption(Memcached::OPT_HASH, Memcached::HASH_MD5);
 
$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);

   然后运行这个脚本。正常情况下可以返回

   int(2)

   这时候关掉实例1123,再次运行上面的代码,会返回

   bool(false)

   大家看到这里应该明白出了什么问题,我们上面这段代码无法自动的将fail的server踢出去。这将是一个非常大的隐患。

   更麻烦的是,下面这段代码依然无法自动将fail的server去除(使用memcached扩展的默认配置)

$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的问题?

$memcache = new Memcached;
$memcache->setOption(Memcached::OPT_DISTRIBUTION, Memcached::DISTRIBUTION_CONSISTENT);
$memcache->setOption(Memcached::OPT_BINARY_PROTOCOL, true);
$memcache->setOption(Memcached::OPT_LIBKETAMA_COMPATIBLE, true);
 
//自动failover配置
/*$memcache->setOption(Memcached::OPT_SERVER_FAILURE_LIMIT, 1);
$memcache->setOption(Memcached::OPT_RETRY_TIMEOUT, 30);
$memcache->setOption(Memcached::OPT_AUTO_EJECT_HOSTS, true);
*/
//最新的libmemcached的文档中可以看到更建议使用如下的选项
$memcache->setOption(Memcached::OPT_REMOVE_FAILED_SERVERS,true);
 
 
$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);

   这时只要1122,1123有一个实例正常运行,就可以保证memcache的数据能够存取。

   重点是第7-9行的参数设置,但要注意的是,这套参数配置必须配合DISTRIBUTION_CONSISTENT生效,对于memcached扩展默认的DISTRIBUTION_MODULA依然是无法实现failover的。从libmemcached源码中依稀也可以看到一些踪影。

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的判断

static inline void _regen_for_auto_eject(memcached_st *ptr)
{
  if (_is_auto_eject_host(ptr) && ptr->ketama.next_distribution_rebuild) //这个next_distribution_rebuild值会在update_continuum方法调用时被改变。
  {
    struct timeval now;
 
    if (gettimeofday(&now, NULL) == 0 and
        now.tv_sec -> ptr->ketama.next_distribution_rebuild)
    {
      run_distribution(ptr);
    }
  }
}
 
void memcached_autoeject(memcached_st *ptr)
{
  _regen_for_auto_eject(ptr);
}
 
uint32_t memcached_generate_hash_with_redistribution(memcached_st *ptr, const char *key, size_t key_length)
{
  uint32_t hash= _generate_hash_wrapper(ptr, key, key_length);
 
  _regen_for_auto_eject(ptr);
 
  return dispatch_host(ptr, hash);
}

   update_continuum 部分代码摘要如下,可以看到判断server是否fail以及修改next_distribution_rebuild的部分。

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);
  }

建议继续学习

  1. 分布式缓存系统 Memcached 入门 (阅读 16,043)
  2. 30分钟3300%性能提升――python+memcached网页优化小记 (阅读 13,582)
  3. Cacti 添加 Memcached 监控 (阅读 9,162)
  4. Redis和Memcached的区别 (阅读 7,944)
  5. memcached 源码阅读笔记 (阅读 5,266)
  6. 启用memcached压缩注意事项 (阅读 5,124)
  7. Memcached内存管理机制浅析 (阅读 5,082)
  8. Memcached and MySQL (阅读 4,363)
  9. Memcached的线程模型及状态机 (阅读 4,363)
  10. Memcached二三事儿 (阅读 4,184)