`
wsql
  • 浏览: 11777759 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
文章分类
社区版块
存档分类
最新评论

libmemcached一致性hash算法详解(1)----php-memcached客户端一致性哈希与crc算法共用产生的bug分析

 
阅读更多

author: selfimpr

blog: http://blog.csdn.net/lgg201

mail: lgg860911@yahoo.com.cn


事情的起源, 是同事使用下面的代码, 得到了一个诡异的结果, 而且是稳定的产生我们不期望的结果.

<?php
$mem = new Memcached;
$mem->addServers(array(array('10.8.8.32',11300,100),array('10.8.8.32',11301,0)));
$mem->setOption(Memcached::OPT_DISTRIBUTION, Memcached::DISTRIBUTION_CONSISTENT);
$mem->setOption(Memcached::OPT_HASH, Memcached::HASH_CRC);
for ($i=0;$i<10;$i++){
    $key = "item_$i";
    $arr = $mem->getServerByKey($key);
    echo ($key.":\t".$arr['port']."\n");
}
print_r($mem->getServerList());
代码很简单, 就是创建了一个php的Memcached客户端, 增加了两台服务器, 设置了分布式算法为一致性哈希, Hash算法为CRC.

运行了很多次这个测试用例, 产生的输入都如下:

item_1:	11301
item_2:	11301
item_3:	11301
item_4:	11301
item_5:	11301
item_6:	11301
item_7:	11301
item_8:	11301
item_9:	11301
Array
(
    [0] => Array
        (
            [host] => 10.8.8.32
            [port] => 11300
            [weight] => 100
        )

    [1] => Array
        (
            [host] => 10.8.8.32
            [port] => 11301
            [weight] => 0
        )

)
从上面的输出中我们可以看出, 两台服务器都是OK的, 但是, 只能所有的key都被分布到了一台服务器上.

后来, 我尝试对测试用例做了以下修改:

1. 将Hash算法换做其他支持的算法

2. 将分布式算法换成普通的算法

我做的以上尝试, 输出的结果都是我们期望的, key被分布到不同的服务器上.


然后就是痛苦的问题跟踪, 最终发现问题出在php的memcached客户端对libmemcached的实现上

在libmemcached中, 用来代表一组服务器(针对同一个客户端服务)的结构(libmemcached/memcached.h中定义)是: struct memcached_st {};下面摘取其中的部分定义:

struct memcached_st {
  uint8_t purging;
  bool is_allocated;
  uint8_t distribution;
  uint8_t hash;
...
  memcached_hash hash_continuum;
...
};
请记住hash和hash_continuum这两个字段.

然后我们看一个函数:

libmemcached/memcached_hash.c中的memcached_generate_hash函数, 进入这个函数的流程如下:

"php-memcached扩展php_memcached.c中getServeredByKey函数" 调用 "libmemcached的libmemcached/memcache_server.c中的memcached_server_by_key函数", 在其中又调用了 "libmemcached/memcached_hash.c中的memcached_generate_hash函数"

在这个函数中做了3件比较重要的事:

1. 生成要寻找的key的hash值

2. 如果需要, 更新服务器的一致性hash点集

3. 将key分布到服务器上


我们分别来看这3件事:

1. 生成key的hash值:

继续跟踪代码, 我们发现在generate_hash函数中有如下一句代码:

hash= memcached_generate_hash_value(key, key_length, ptr->hash);

查看memcached_generate_hash_value函数源代, 我们得知该函数是使用第3个参数指定的hash算法, 产生key的hash值, 这里使用的是ptr->hash

注: ptr就是前面提到的memcached_st结构

2. 更新服务器的一致性hash点集

这里, 我们需要说的是, 哪怕不需要, 在我们测试代码中的addServer调用时, 也会执行这个函数, 所以, 我们需要关注其中所做的事情

我们跟踪到update_continuum函数中, 分析源代码, 总结这个函数所做的事情, 用php代码描述如下:

<?php
$servers	= array(
	array('10.8.8.32', 11301), 
	array('10.8.8.32', 11300), 
);
$points		= array();
$index		= 0;
foreach ( $servers as $server ) {
	$i	= 0;
	while ( $i ++ < 100 ) { //libmemcached中100是两个常量求得的值
		$points[]	= array(
			'index'	=> $index, 
			'value'	=> hash_value, 
		);
	}
	$index ++;
}
//这里再对$servers按照元素的'value'排序
也就是: 以$host:$port-$i作为key产生100个hash值, 所有服务器产生的这些hash值再排序

这里在libmemcached的update_continuum中, 我们需要找到下面这句代码:

value= memcached_generate_hash_value(sort_host, sort_host_length, ptr->hash_continuum);
也就是求每个点的hash值, 可以看到, 这里用了ptr中的hash_continuum字段给定的hash算法计算.

3. 将key分布到服务器上

这里的分布过程, 其实就是对上面产生的点集进行一个二分查找, 找到离key的hash值最近的点, 以其对应服务器作为key的服务器, 用php代码描述如下:

<?php
$points	= array();	//之前取到的服务器产生的点集
$hash	= 1;		//要查找的key的hash值
$begin = $left = 0;
$end = $right = floor(count($points) / 2);
while ( $left < $right ) {
	$middle	= $left + floor(($left + $right) / 2);
	if ( $points[$middle]['value'] < $hash ) $left = $middle + 1;
	else $right = $middle;
}
//数组越界检查
if ( $right = $end ) $right = $begin;
//这里就得到了key分布到的服务器是所有服务器中的第$index个
$index	= $servers[$right]['index'];

主要的过程分析完了, 对造成这个问题的关键点用红字标识了出来, 我们可以看到, 对key和对服务器求hash值的算法在memcached_st结构中是由不同的字段指定的.

那么, 问题就明了了, 我们取看看php-memcached中的setOption方法的实现, 它只是修改了ptr->hash字段.

因此, 测试用例的运行情况是:

对key使用crc算法求hash

对服务器点集使用默认算法求hash值


经过对两种算法比较, 默认的算法产生的hash数值都比较大, 而crc产生的hash值最大就是几万的样子(具体上限没有计算)

所以, 原因就找到了, 服务器点集的hash值都大于key产生的hash值, 所以查找时永远都落在点集的第一个点上.


至此, 问题的原因已经查明, 解决方法也就有了: 修改php的memcached扩展, 在setOption中增加修改ptr->hash_continuum字段的操作, 然后测试用例做响应修改即可.


下一篇文章将展示的是一个从libmemcached源代码提取出来的简化版一致性hash算法, 简单明了, 可以很容易说明libmemcached的一致性hash算法实现

libmemcached一致性hash算法详解(2)----简化版的libmemcached一致性hash算法实现

分享到:
评论

相关推荐

    php-memcached-3.1.3.tar.gz

    安装php-memcached扩展 https://github.com/php-memcached-dev/php-memcached tar -zxvf ...-disable-memcached-sasl && make && make install

    php-memcached, 基于libmemcached库的memcached扩展.zip

    php-memcached, 基于libmemcached库的memcached扩展 插件生成状态 描述这是的扩展,使用libmemcached库连接到memcached服务器。是一种性能高。分布式内存对象缓存系统,一般用于在加速数据库负载的情况下加速动态web...

    memcached全套安装资料,memcached库,libevent库, C/C++ memcached 客户端库:libmemcached

    memcached 安装文件,libevent库, C/C++ memcached 客户端库:libmemcached 全套工具:libevent-1.3.tar.gz,memcached-1.2.2.tar.gz,libmemcached-0.42.tar.gz 测试通过的

    高运算性能,低碰撞率的hash算法MurmurHash算法.zip

    MurmurHash算法由Austin Appleby创建于2008年,现已应用到Hadoop、libstdc 、nginx、libmemcached,Redis,Memcached,Cassandra,HBase,Lucene等开源系统。2011年Appleby被Google雇佣,随后Google推出其变种的...

    libmemcached && libmemcached-devel-1.0.16.zip

    libmemcached && libmemcached-devel-1.0.16.zip

    linux版本下memcached+libmemcached

    在redhat5下测试过,可以使用的memcached,而且libmemcached也经过测试(c++版本的memcached api),还附赠一份代码

    linux下安装php的memcached客户端

    下面将介绍安装php的memcached客户端安装步骤 1、下载安装libmemcached客户端 官网地址:http://libmemcached.org/libMemcached.html $wget -c ...

    memcached:libmemcached C客户端的Ruby接口

    多种哈希模式,包括一致的哈希 速度惊人,包括带有no_reply的可选流水线IO memcached库通过SWIG包装了纯C的libmemcached客户端。 安装 您需要Ruby 1.8.7或Ruby 1.9.2。 其他版本可能会起作用,但不能保证。 您还...

    centos php memcached扩展

    php memcached扩展,内含安装手册以及源码包,libevent-2.0.22,libmemcached-1.0.18,memcached-1.4.25,memcached-2.2.0,已经在CentOS6.5上进行测试通过。

    libmemcached-win-yshurik-win.rar

    windows 版本libmemcached源码及库文件,需要vs2015打开编译,低版本没试过 本人用Qt实现的,具体实现还是比较简单

    php-memcached:基于libmemcached库的memcached扩展

    建立状态描述这是扩展,使用libmemcached库连接到memcached服务器。 是一种高性能的分布式内存对象缓存系统,本质上是通用的,但旨在通过减轻数据库负载来加速动态Web应用程序。建筑$ phpize$ ./configure$ make$ ...

    memcached支持 PHP 7.0 - 7.2

    php-memcached 3.x: Supports PHP 7.0 - 7.2. Requires libmemcached 1.x or higher. Optionally supports igbinary 2.0 or higher. Optionally supports msgpack 2.0 or higher.

    libmemcached-devel-1.0.16-5.el7.i686.rpm

    官方离线安装包,亲测可用。使用rpm -ivh [rpm完整包名] 进行安装

    libmemcached-0.42.tar.gz

    php有两个版本的memcache扩展,这个是memcached的扩展,安装memcached扩展的时候需要一个类库,这个就是所需要的依赖类包libmemcached

    memcached-1.0.2.tgz

    php安装memcache扩展有两种方式(memcached,memcache),这是memcached扩展的源码包,安装此扩展需要有libmemcached libevent支持

    libmemcached-1.0.17.tar.gz

    php有两个版本的memcache扩展,这个是memcached的扩展,安装memcached扩展的时候需要一个类库,这个就是所需要的依赖类包libmemcached

    php_memcached_dll:Windows php-memcached dll文件

    php-memcached依赖libmemcached copy libmemcached.dll to c:\Windows 安装php-memcached 将php-memcached.dll复制到您的php扩展目录 注意:请注意PHP的版本和线程安全性,无论是否 memcached_support: 3.1.4 ...

    memcached-2.1.0.tgz

    php安装memcache扩展有两种方式(memcached,memcache),这是memcached扩展的源码包,安装此扩展需要有libmemcached libevent支持

    memcached-for-Tcl:用于基于 Tcl 的应用程序的 memcached 客户端库

    实际的Memcached服务器安装独立于此客户端软件包的安装,本文档未解决。 在 FreeBSD 上: pkg 安装数据库/libmemcached 环境 CPPFLAGS=-I/usr/local/include LDFLAGS=-L/usr/local/lib ./configure --with-tcl=/...

Global site tag (gtag.js) - Google Analytics