设为首页 收藏本站
查看: 614|回复: 0

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

[复制链接]
发表于 2015-9-1 13:58:37 | 显示全部楼层 |阅读模式
  author: selfimpr
  blog: http://blog.iyunv.com/lgg201
  mail: lgg860911@yahoo.com.cn
  
  事情的起源, 是同事使用下面的代码, 得到了一个诡异的结果, 而且是稳定的产生我们不期望的结果.



view plain

  • <?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.
  运行了很多次这个测试用例, 产生的输入都如下:



view plain

  • 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 {};下面摘取其中的部分定义:



view plain

  • 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代码描述如下:



view plain

  • <?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中, 我们需要找到下面这句代码:



view plain

  • value= memcached_generate_hash_value(sort_host, sort_host_length, ptr->hash_continuum);  
  也就是求每个点的hash值, 可以看到, 这里用了ptr中的hash_continuum字段给定的hash算法计算.
  3. 将key分布到服务器上
  这里的分布过程, 其实就是对上面产生的点集进行一个二分查找, 找到离key的hash值最近的点, 以其对应服务器作为key的服务器, 用php代码描述如下:



view plain

  • <?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字段的操作, 然后测试用例做响应修改即可.
  -------
  author: selfimpr
  blog: http://blog.iyunv.com/lgg201
  mail: lgg860911@yahoo.com.cn
  
  上一篇文档见
  libmemcached一致性hash算法详解(1)----php-memcached客户端一致性哈希与crc算法共用产生的bug分析
  
  这里就不废话了, 直接上代码:



view plain

  • #include <stdio.h>  
  • #include <stdlib.h>  
  • #include <stdint.h>  
  • #include <string.h>  
  •   
  • /*---------------------------以下部分摘自libmemcached/crc.c--------------------------*/  
  • static const uint32_t crc32tab[256] = {  
  •   0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,  
  •   0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,  
  •   0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,  
  •   0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,  
  •   0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,  
  •   0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,  
  •   0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,  
  •   0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,  
  •   0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,  
  •   0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,  
  •   0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,  
  •   0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,  
  •   0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,  
  •   0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,  
  •   0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,  
  •   0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,  
  •   0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,  
  •   0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,  
  •   0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,  
  •   0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,  
  •   0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,  
  •   0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,  
  •   0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,  
  •   0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,  
  •   0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,  
  •   0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,  
  •   0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,  
  •   0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,  
  •   0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,  
  •   0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,  
  •   0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,  
  •   0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,  
  •   0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,  
  •   0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,  
  •   0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,  
  •   0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,  
  •   0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,  
  •   0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,  
  •   0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,  
  •   0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,  
  •   0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,  
  •   0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,  
  •   0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,  
  •   0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,  
  •   0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,  
  •   0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,  
  •   0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,  
  •   0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,  
  •   0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,  
  •   0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,  
  •   0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,  
  •   0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,  
  •   0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,  
  •   0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,  
  •   0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,  
  •   0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,  
  •   0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,  
  •   0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,  
  •   0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,  
  •   0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,  
  •   0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,  
  •   0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,  
  •   0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,  
  •   0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,  
  • };  
  •   
  • uint32_t hash_crc32(const char *key, size_t key_length)  
  • {  
  •   uint32_t x;  
  •   uint32_t crc= UINT32_MAX;  
  •   
  •   for (x= 0; x < key_length; x++)  
  •     crc= (crc >> 8) ^ crc32tab[(crc ^ (key[x])) & 0xff];  
  •   
  •   return ~crc;  
  • }  
  • /*---------------------------以上部分摘自libmemcached/crc.c--------------------------*/  
  •   
  • //libmemcached中使用crc求hash值的方法  
  • uint32_t memcached_hash_crc32(const char *key, size_t key_len) {  
  •     return ((hash_crc32(key, key_len) >> 16) & 0x7fff);  
  • }  
  •   
  • /**
  • * 模拟libmemcached的一致性hash算法中的点   
  • * libmemcached中, 假设有A,B两台服务器, 则其一致性算法为:
  • *  1. 假设每台服务器100个点
  • *  2. 每个点是一个类似下面的server_t的结构, 一个字段存放server的索引, 一个字段存放值
  • *  3. 每个服务器构造自己对应的100个点时, 对host:port-i求hash值, 其中host为服务器地址, port为端口, i为0-99的数
  • *  4. 将两个服务器总共产生的200个点放入一个数组, 按照点的value(即上面求得的hash值)排序
  • * 下面是拿到一个key进行分布时的策略
  • *  1. 对key求一个hash值
  • *  2. 从上面得到的服务器的200个点的数组中, 二分查找离这个key产生的hash值最近的点
  • *  3. 拿到这个点对应的服务器
  • */  
  • typedef struct _server {  
  •     char host[128];  
  •     uint32_t value;  
  • } server_t;  
  •   
  • //libmemcached中点的比较函数  
  • int server_cmp(const void *s1, const void *s2) {  
  •     server_t *cs1 = (server_t *)s1;  
  •     server_t *cs2 = (server_t *)s2;  
  •     if ( cs1->value == cs2->value ) return 0;  
  •     else if ( cs1->value > cs2->value ) return 1;  
  •     else return -1;  
  • }  
  •   
  • //打印从servers开始的n个点  
  • void print_servers(const server_t *servers, int n) {  
  •     int i = 0;  
  •     while ( i < n) {  
  •         printf("server: %s, value: %d\n", (servers + i)->host, (servers + i)->value);  
  •         i ++;  
  •     }  
  • }  
  • //构造示例性的2台服务器产生的点, libmemcached中的算法简化了就是如此  
  • server_t *construct_servers() {  
  •     server_t *servers = malloc(sizeof(server_t) * 200);  
  •     int i = 0, j;  
  •     int server_length = 0;  
  •     char server[128];  
  •     while ( i < 2 ) {  
  •         for ( j = 0; j < 100; j ++ ) {  
  •             server_length = sprintf(server, "127.0.0.1:%d-%d", i ? 11211 : 11212, j);  
  •             strcpy((servers + i * 100 + j)->host, server);  
  •             (servers + i * 100 + j)->value = memcached_hash_crc32(server, server_length);  
  •         }  
  •         i ++;  
  •     }  
  •     qsort(servers, 200, sizeof(server_t), server_cmp);  
  •     //print_servers(servers, 200);  
  •     return servers;  
  • }  
  • //给定一个key, 查找对应的服务器, libmemcached的查找算法简化板  
  • void search(server_t *servers, int num, char *key, int key_len) {  
  •     server_t *begin, *left, *middle, *right, *end;   
  •     begin = left = servers;  
  •     end = right = servers + num;  
  •     uint32_t hash;  
  •     hash = memcached_hash_crc32(key, key_len);  
  •   
  •     while (left < right) {  
  •         middle= left + (right - left) / 2;  
  •         if (middle->value < hash) left= middle + 1;  
  •         else right= middle;  
  •     }  
  •     if (right == end) right= begin;  
  •     printf("key: %s, server: %s\n", key, right->host);  
  • }  
  •   
  • //下面是模拟的一个分布测试程序  
  • void main(void) {  
  •     server_t *servers;  
  •     uint32_t hash;  
  •     char key[128];  
  •     int i = 0, key_len;  
  •   
  •     servers = construct_servers();  
  •   
  •     while ( i < 100 ) {  
  •         key_len = sprintf(key, "item-%d", i ++);  
  •         search(servers, 200, key, key_len);  
  •     }  
  • }  
  文章来源:http://blog.iyunv.com/lgg201/article/details/6856112

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-108412-1-1.html 上篇帖子: 11 Memcached 缓存雪崩现象 下篇帖子: telnet测试memcached服务器(转)
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表