zoukankan      html  css  js  c++  java
  • 追踪分布式Memcached默认的一致性hash算法

    <span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">又到了一个能够沉思的夜晚。近期事情比較繁杂,大脑全然平静不下来。就想着研究点东西来平复一下。</span>
    非常久前。曾被一个同事问到过关于PHP端Memcache分布式的细节问题,有一个业务有两台Memcached,举例:业务代码例如以下:
    <?php
    /**
    *生成100个数据到memcache里。能够思考下,数据会怎么存储
    */
    $memClient = new Memcached();
    $memClient->addServers(array(’10.21.1.11’,’11233’),array(’10.21.1.12’,’11233’));
    for($i = 0;$i < 100;$i++){
         $memClicent->set(“prefix_key_”.$i,$i,3600);
    }

    以下我要開始把这些值打印出来
    <?php
    $memClient = new Memcached();
    $memClient->addServers(array(’10.21.1.11’,’11233’),array(’10.21.1.12’,’11233’));
    for($i = 0;$i < 100;$i++){
         $memClicent->get(“prefix_key_”.$i);
    }

    这里的结果,是全部的值都被打印出来了,可是当我去掉当中一台server把连接变成,
    $memClient->addServers(array(’10.21.1.11’,’11233’)。这时打印出来的值少了一将近半。看来serverset函数帮我们自己主动把数据hash到两台server上,可是set是怎么hash的,用的什么算法。我们全然不知道。以下就开启”疯狗模式”,准备深挖源码。

    PS:Memcached能够手动设置hash算法,通过setOptions,而且Memcached自带了几种算法,比如Memcached::HASH_CRC,Memcached::HASH_FNV1_32,Memcached::MD5等

    首先从memcached扩展入手。查看php_memcached.c,看下set做了什么,发现扩展里全部存储类的操作都调用了一个函数php_memc_store_impl。继续深挖。
    PHP_METHOD(Memcached, set)
    {
        php_memc_store_impl(INTERNAL_FUNCTION_PARAM_PASSTHRU, MEMC_OP_SET, 0);
    }

    在php_memc_store_imple找到例如以下:
    status = memcached_set(m_obj->memc, key, key_len, payload, payload_len, expiration, flags);
    他调用了libmemcached里提供的memcached_set函数。并把键值,有效时间传了过去。


    在libmemcached/storage.cc里找到memcached_set。通过追踪发现终于存储数据是在memcached_send函里,在这里发现关键代码:
    uint32_t server_key= memcached_generate_hash_with_redistribution(ptr, group_key, group_key_length);
    memcached_instance_st* instance= memcached_instance_fetch(ptr, server_key);
    他通过我们的key来选取某台server。

    在libmemcached/hash.cc里找到memcached_generate_hash_with_redistribution函数,通过终于追踪在generate_hash函数里发现关键代码例如以下:
    return hashkit_digest(&ptr->hashkit, key, key_length);
    返回一个长整型,返回的这个值就是路由到哪个server的关键

    在libhashkit/digest.cc里找到了hashkit_digest函数,关键代码例如以下:
    return self->base_hash.function(key, key_length, self->base_hash.context);
    可是,可是,self->base_hash是什么鬼。看下hashkit_digest函数的形參
    uint32_t hashkit_digest(const hashkit_st *self, const char *key, size_t key_length)。这个self是来源于哪,于是開始回朔代码,终于回到了memcached扩展里最初的那个set
    status = memcached_set(m_obj->memc, key, key_len, payload, payload_len, expiration, flags);
    这里有个m_obj->memc。这是memcached_set传递的第一个參数,于是self->base_hash能够想像成
    m_obj->memc->hashkit->base_hash.function(key, key_length, self->base_hash.context);
    这个base_hash在哪里。


    我们先来看下m_obj是什么东西。

    在php_memcached.c的124行,看到m_obj = i_obj->obj;
    i_obj的结构体例如以下:
    typedef struct {
    	zend_object zo;
    
    	struct memc_obj {
    		memcached_st *memc;
    		zend_bool compression;
    		enum memcached_serializer serializer;
    		enum memcached_compression_type compression_type;
    #if HAVE_MEMCACHED_SASL
    		zend_bool has_sasl_data;
    #endif
    		long store_retry_count;
    	} *obj;
    
    	zend_bool is_persistent;
    	zend_bool is_pristine;
    	int rescode;
    	int memc_errno;
    } php_memc_t;


    在i_obj里我们看到memc是一个memcached_st的指针,还记得hashkit_digest的函数的第一个參数么,里面的ptr就是如今的m_obj->memc,memcached_st在libmemcached-1.0/struct/memcached.h,所以我们从memcached_st里找到hashkit的结构体是hashkit_st。话说最终快找到头了。

    hashkit_st结构体例如以下:
    struct hashkit_st
    {
      struct hashkit_function_st {
        hashkit_hash_fn function;
        void *context;
      } base_hash, distribution_hash;
    
      struct {
        bool is_base_same_distributed:1;
      } flags;
    
      struct {
        bool is_allocated:1;
      } options;
    
      void *_key;
    };


    看到base_hash在这里。这里有一个最关键的函数指针hashkit_hash_fn function,我们就是调用它指向的hash函数。可是这个指针是哪里被赋的值呢。

    我们回到memcached扩展在php_memcached.c里看一下memcached在初始化的时候做了什么(new Memcached()),在static PHP_METHOD(Memcached, __construct)里我们看到m_obj->memc = memcached_create(NULL);这一句代码,这是调用了libmemcached来初始化一个memcached的实例。

    我们在libmemcached/memcached.cc里追踪到了_memcached_init(Memcached *self)函数,关键代码例如以下:
      if (hashkit_create(&self->hashkit) == NULL)
      {
        return false;
      }

    hashkit_create给了m_obj->memc->hashkit初始化。最终(快断气了)。在libhashkit/hashkit.cc里追踪到了_hashkit_init(hashkit_st *self)。关键代码例如以下:
    static inline void _hashkit_init(hashkit_st *self)
    {
      self->base_hash.function= hashkit_one_at_a_time;
      self->base_hash.context= NULL;
    
      self->distribution_hash.function= hashkit_one_at_a_time;
      self->distribution_hash.context= NULL;
    
      self->flags.is_base_same_distributed= true;
      self->_key= NULL;
    }

    这里hashkit_one_at_a_time就是我们在没加入默认hash的时候终于调用到的hash函数。
    代码例如以下,很短,有兴趣的能够看下他的wiki:http://en.wikipedia.org/wiki/Jenkins_hash_function

    #include <libhashkit/common.h>
    
    uint32_t hashkit_one_at_a_time(const char *key, size_t key_length, void *context)
    {
      const char *ptr= key;
      uint32_t value= 0;
      (void)context;
    
      while (key_length--)
      {
        uint32_t val= (uint32_t) *ptr++;
        value += val;
        value += (value << 10);
        value ^= (value >> 6);
      }
      value += (value << 3);
      value ^= (value >> 11);
      value += (value << 15);
    
      return value;
    }



    至此,我们就探索了这一内幕。结束了,关闭"疯狗模式”,说实话,整个过程是很享受的:)。难得的一晚,大脑清醒了不少,如有错误,欢迎批评指证。 
  • 相关阅读:
    转载+自己心得
    分享
    领域驱动设计系列文章汇总
    ABP集合贴
    MVC学习系列——参考
    MVC学习系列——RazorViewEngine扩展
    MVC学习系列——Model验证扩展
    MVC学习系列——HtmlHelper扩展
    MVC学习系列——ModelBinder扩展
    MacBook强制清除gardle缓存
  • 原文地址:https://www.cnblogs.com/wzzkaifa/p/6898548.html
Copyright © 2011-2022 走看看