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


    author: selfimpr

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

    mail: lgg860911@yahoo.com.cn

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

    1. <?php  
    2. $mem = new Memcached;  
    3. $mem->addServers(array(array('10.8.8.32',11300,100),array('10.8.8.32',11301,0)));  
    4. $mem->setOption(Memcached::OPT_DISTRIBUTION, Memcached::DISTRIBUTION_CONSISTENT);  
    5. $mem->setOption(Memcached::OPT_HASH, Memcached::HASH_CRC);  
    6. for ($i=0;$i<10;$i++){  
    7.     $key = "item_$i";  
    8.     $arr = $mem->getServerByKey($key);  
    9.     echo ($key.":\t".$arr['port']."\n");  
    10. }  
    11. print_r($mem->getServerList());  

    代码很简单, 就是创建了一个php的Memcached客户端, 增加了两台服务器, 设置了分布式算法为一致性哈希, Hash算法为CRC.

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

    1. item_1: 11301  
    2. item_2: 11301  
    3. item_3: 11301  
    4. item_4: 11301  
    5. item_5: 11301  
    6. item_6: 11301  
    7. item_7: 11301  
    8. item_8: 11301  
    9. item_9: 11301  
    10. Array  
    11. (  
    12.     [0] => Array  
    13.         (  
    14.             [host] => 10.8.8.32  
    15.             [port] => 11300  
    16.             [weight] => 100  
    17.         )  
    18.   
    19.     [1] => Array  
    20.         (  
    21.             [host] => 10.8.8.32  
    22.             [port] => 11301  
    23.             [weight] => 0  
    24.         )  
    25.   
    26. )  

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

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

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

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

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

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

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

    1. struct memcached_st {  
    2.   uint8_t purging;  
    3.   bool is_allocated;  
    4.   uint8_t distribution;  
    5.   uint8_t hash;  
    6. ...  
    7.   memcached_hash hash_continuum;  
    8. ...  
    9. };  

    请记住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代码描述如下:

    1. <?php  
    2. $servers    = array(  
    3.     array('10.8.8.32', 11301),   
    4.     array('10.8.8.32', 11300),   
    5. );  
    6. $points     = array();  
    7. $index      = 0;  
    8. foreach ( $servers as $server ) {  
    9.     $i  = 0;  
    10.     while ( $i ++ < 100 ) { //libmemcached中100是两个常量求得的值  
    11.         $points[]   = array(  
    12.             'index' => $index,   
    13.             'value' => hash_value,   
    14.         );  
    15.     }  
    16.     $index ++;  
    17. }  
    18. //这里再对$servers按照元素的'value'排序  

    也就是: 以$host:$port-$i作为key产生100个hash值, 所有服务器产生的这些hash值再排序

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

    1. value= memcached_generate_hash_value(sort_host, sort_host_length, ptr->hash_continuum);  

    也就是求每个点的hash值, 可以看到, 这里用了ptr中的hash_continuum字段给定的hash算法计算.

    3. 将key分布到服务器上

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

    1. <?php  
    2. $points = array();  //之前取到的服务器产生的点集  
    3. $hash   = 1;        //要查找的key的hash值  
    4. $begin = $left = 0;  
    5. $end = $right = floor(count($points) / 2);  
    6. while ( $left < $right ) {  
    7.     $middle = $left + floor(($left + $right) / 2);  
    8.     if ( $points[$middle]['value'] < $hash ) $left = $middle + 1;  
    9.     else $right = $middle;  
    10. }  
    11. //数组越界检查  
    12. if ( $right = $end ) $right = $begin;  
    13. //这里就得到了key分布到的服务器是所有服务器中的第$index个  
    14. $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.csdn.net/lgg201

    mail: lgg860911@yahoo.com.cn

    上一篇文档见

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

    这里就不废话了, 直接上代码:

    1. #include <stdio.h>  
    2. #include <stdlib.h>  
    3. #include <stdint.h>  
    4. #include <string.h>  
    5.   
    6. /*---------------------------以下部分摘自libmemcached/crc.c--------------------------*/  
    7. static const uint32_t crc32tab[256] = {  
    8.   0x00000000, 0x77073096, 0xee0e612c, 0x990951ba,  
    9.   0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,  
    10.   0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,  
    11.   0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,  
    12.   0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,  
    13.   0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,  
    14.   0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec,  
    15.   0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,  
    16.   0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,  
    17.   0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,  
    18.   0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940,  
    19.   0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,  
    20.   0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116,  
    21.   0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,  
    22.   0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,  
    23.   0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,  
    24.   0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a,  
    25.   0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,  
    26.   0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818,  
    27.   0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,  
    28.   0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,  
    29.   0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,  
    30.   0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c,  
    31.   0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,  
    32.   0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,  
    33.   0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,  
    34.   0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,  
    35.   0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,  
    36.   0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086,  
    37.   0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,  
    38.   0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4,  
    39.   0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,  
    40.   0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,  
    41.   0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,  
    42.   0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,  
    43.   0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,  
    44.   0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe,  
    45.   0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,  
    46.   0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,  
    47.   0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,  
    48.   0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252,  
    49.   0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,  
    50.   0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60,  
    51.   0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,  
    52.   0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,  
    53.   0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,  
    54.   0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04,  
    55.   0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,  
    56.   0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a,  
    57.   0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,  
    58.   0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,  
    59.   0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,  
    60.   0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e,  
    61.   0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,  
    62.   0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,  
    63.   0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,  
    64.   0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,  
    65.   0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,  
    66.   0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0,  
    67.   0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,  
    68.   0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6,  
    69.   0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,  
    70.   0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,  
    71.   0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,  
    72. };  
    73.   
    74. uint32_t hash_crc32(const char *key, size_t key_length)  
    75. {  
    76.   uint32_t x;  
    77.   uint32_t crc= UINT32_MAX;  
    78.   
    79.   for (x= 0; x < key_length; x++)  
    80.     crc= (crc >> 8) ^ crc32tab[(crc ^ (key[x])) & 0xff];  
    81.   
    82.   return ~crc;  
    83. }  
    84. /*---------------------------以上部分摘自libmemcached/crc.c--------------------------*/  
    85.   
    86. //libmemcached中使用crc求hash值的方法  
    87. uint32_t memcached_hash_crc32(const char *key, size_t key_len) {  
    88.     return ((hash_crc32(key, key_len) >> 16) & 0x7fff);  
    89. }  
    90.   
    91. /** 
    92.  * 模拟libmemcached的一致性hash算法中的点   
    93.  * libmemcached中, 假设有A,B两台服务器, 则其一致性算法为: 
    94.  *  1. 假设每台服务器100个点 
    95.  *  2. 每个点是一个类似下面的server_t的结构, 一个字段存放server的索引, 一个字段存放值 
    96.  *  3. 每个服务器构造自己对应的100个点时, 对host:port-i求hash值, 其中host为服务器地址, port为端口, i为0-99的数 
    97.  *  4. 将两个服务器总共产生的200个点放入一个数组, 按照点的value(即上面求得的hash值)排序 
    98.  * 下面是拿到一个key进行分布时的策略 
    99.  *  1. 对key求一个hash值 
    100.  *  2. 从上面得到的服务器的200个点的数组中, 二分查找离这个key产生的hash值最近的点 
    101.  *  3. 拿到这个点对应的服务器 
    102.  */  
    103. typedef struct _server {  
    104.     char host[128];  
    105.     uint32_t value;  
    106. } server_t;  
    107.   
    108. //libmemcached中点的比较函数  
    109. int server_cmp(const void *s1, const void *s2) {  
    110.     server_t *cs1 = (server_t *)s1;  
    111.     server_t *cs2 = (server_t *)s2;  
    112.     if ( cs1->value == cs2->value ) return 0;  
    113.     else if ( cs1->value > cs2->value ) return 1;  
    114.     else return -1;  
    115. }  
    116.   
    117. //打印从servers开始的n个点  
    118. void print_servers(const server_t *servers, int n) {  
    119.     int i = 0;  
    120.     while ( i < n) {  
    121.         printf("server: %s, value: %d\n", (servers + i)->host, (servers + i)->value);  
    122.         i ++;  
    123.     }  
    124. }  
    125. //构造示例性的2台服务器产生的点, libmemcached中的算法简化了就是如此  
    126. server_t *construct_servers() {  
    127.     server_t *servers = malloc(sizeof(server_t) * 200);  
    128.     int i = 0, j;  
    129.     int server_length = 0;  
    130.     char server[128];  
    131.     while ( i < 2 ) {  
    132.         for ( j = 0; j < 100; j ++ ) {  
    133.             server_length = sprintf(server, "127.0.0.1:%d-%d", i ? 11211 : 11212, j);  
    134.             strcpy((servers + i * 100 + j)->host, server);  
    135.             (servers + i * 100 + j)->value = memcached_hash_crc32(server, server_length);  
    136.         }  
    137.         i ++;  
    138.     }  
    139.     qsort(servers, 200, sizeof(server_t), server_cmp);  
    140.     //print_servers(servers, 200);  
    141.     return servers;  
    142. }  
    143. //给定一个key, 查找对应的服务器, libmemcached的查找算法简化板  
    144. void search(server_t *servers, int num, char *key, int key_len) {  
    145.     server_t *begin, *left, *middle, *right, *end;    
    146.     begin = left = servers;  
    147.     end = right = servers + num;  
    148.     uint32_t hash;  
    149.     hash = memcached_hash_crc32(key, key_len);  
    150.   
    151.     while (left < right) {  
    152.         middle= left + (right - left) / 2;  
    153.         if (middle->value < hash) left= middle + 1;  
    154.         else right= middle;  
    155.     }  
    156.     if (right == end) right= begin;  
    157.     printf("key: %s, server: %s\n", key, right->host);  
    158. }  
    159.   
    160. //下面是模拟的一个分布测试程序  
    161. void main(void) {  
    162.     server_t *servers;  
    163.     uint32_t hash;  
    164.     char key[128];  
    165.     int i = 0, key_len;  
    166.   
    167.     servers = construct_servers();  
    168.   
    169.     while ( i < 100 ) {  
    170.         key_len = sprintf(key, "item-%d", i ++);  
    171.         search(servers, 200, key, key_len);  
    172.     }  

    文章来源:http://blog.csdn.net/lgg201/article/details/6856112

  • 相关阅读:
    centos7.6系统搭建ibm mq
    eureka服务如何下线及启动
    linux系统遇见挖矿如何处理
    Ceph剖析:数据分布之CRUSH算法与一致性Hash
    makefile 书写方法
    ceph 相关blog
    RabbitMQ 之 安装
    BaGet:搭建 Nuget 私服
    RabbitMQ 简介及其安装
    Redis 简介
  • 原文地址:https://www.cnblogs.com/pingping/p/2210023.html
Copyright © 2020-2023  润新知