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


至此,我们就探索了这一内幕。结束了,关闭"疯狗模式”,说实话,整个过程是很享受的:)。难得的一晚,大脑清醒了不少,如有错误,欢迎批评指证。 

追踪分布式Memcached默认的一致性hash算法的更多相关文章

  1. 图解一致性hash算法和实现

    更多内容,欢迎关注微信公众号:全菜工程师小辉.公众号回复关键词,领取免费学习资料. 一致性hash算法是什么? 一致性hash算法,是麻省理工学院1997年提出的一种算法,目前主要应用于分布式缓存当中 ...

  2. 分布式缓存技术memcached学习(四)—— 一致性hash算法原理

    分布式一致性hash算法简介 当你看到“分布式一致性hash算法”这个词时,第一时间可能会问,什么是分布式,什么是一致性,hash又是什么.在分析分布式一致性hash算法原理之前,我们先来了解一下这几 ...

  3. 分布式缓存技术memcached学习系列(四)—— 一致性hash算法原理

    分布式一致性hash算法简介 当你看到"分布式一致性hash算法"这个词时,第一时间可能会问,什么是分布式,什么是一致性,hash又是什么.在分析分布式一致性hash算法原理之前, ...

  4. 一致性Hash算法在Redis分布式中的使用

    由于redis是单点,但是项目中不可避免的会使用多台Redis缓存服务器,那么怎么把缓存的Key均匀的映射到多台Redis服务器上,且随着缓存服务器的增加或减少时做到最小化的减少缓存Key的命中率呢? ...

  5. 转: memcached Java客户端spymemcached的一致性Hash算法

    转自:http://colobu.com/2015/04/13/consistent-hash-algorithm-in-java-memcached-client/ memcached Java客户 ...

  6. 一致性Hash算法在Memcached中的应用

    前言 大家应该都知道Memcached要想实现分布式只能在客户端来完成,目前比较流行的是通过一致性hash算法来实现.常规的方法是将server的hash值与server的总台数进行求余,即hash% ...

  7. Nginx+Memcache+一致性hash算法 实现页面分布式缓存(转)

    网站响应速度优化包括集群架构中很多方面的瓶颈因素,这里所说的将页面静态化.实现分布式高速缓存就是其中的一个很好的解决方案... 1)先来看看Nginx负载均衡 Nginx负载均衡依赖自带的 ngx_h ...

  8. 分布式一致性hash算法

    写在前面  在学习Redis的集群内容时,看到这么一句话:Redis并没有使用一致性hash算法,而是引入哈希槽的概念.而分布式缓存Memcached则是使用分布式一致性hash算法来实现分布式存储. ...

  9. (转) 一致性Hash算法在Memcached中的应用

    前言 大家应该都知道Memcached要想实现分布式只能在客户端来完成,目前比较流行的是通过一致性hash算法来实现.常规的方法是将 server的hash值与server的总台数进行求余,即hash ...

随机推荐

  1. python基础(一)—— 核心数据类型

    Hello World程序 [root@mysql ~]# python3 Python 3.6.5 (default, Jul  8 2018, 11:41:23) [GCC 4.4.7 20120 ...

  2. tcpdump抓包指令使用示例

    tcpdump是一个用于截取网络分组,并输出分组内容的工具. tcpdump凭借强大的功能和灵活的截取策略,使其成为类UNIX系统下用于网络分析和问题排查的首选工具.tcpdump提供了源代码,公开了 ...

  3. ssh服务使用介绍二

    老司机开车了 快上车 上文提到当我们使用ssh远程链接主机的时候,会出现询问的提示,如果我们链接的机器多了,一遍一遍的输入是不是很麻烦?怎么解决如下 vim /etc/ssh/ssh_config 如 ...

  4. pwntools使用简介3

    连接 本地process().远程remote().对于remote函数可以接url并且指定端口. IO模块 下面给出了PwnTools中的主要IO函数.这个比较容易跟zio搞混,记住zio是read ...

  5. 数据结构实验6:C++实现二叉树类

    实验6 学号:     姓名:      专业:   6.1 实验目的 掌握二叉树的动态链表存储结构及表示. 掌握二叉树的三种遍历算法(递归和非递归两类). 运用二叉树三种遍历的方法求解有关问题. 6 ...

  6. Cannot delete or update a parent row: a foreign key constraint fails....

    在操作”小弟“这张表时候报错 想在“小弟”上面加入数据或者更新数据,就要听老大的, 这句话后面跟着的表就是“老大”,必须老大有数据索引,“小弟“才可以加入或者更新 查看“小弟”表的外键,会发现有对“老 ...

  7. java环境配置——jdk8

    在官网下载最新版本的jdk 测试版本:jdk-8u60-windows-x64.exe 测试环境:Windows Server 2012 R2 Standard  X64 开始执行安装 安装过程中会选 ...

  8. mysql 递归查询父节点 和子节点

    查父集合 --drop FUNCTION `getParentList` )) ) BEGIN ) default ''; ) default rootId; WHILE rootId is not ...

  9. zoj 1251 Box of Bricks

    Box of Bricks Time Limit: 2 Seconds      Memory Limit: 65536 KB Little Bob likes playing with his bo ...

  10. Oracle常用内置数据表查询

    Oracle 查询库中所有表名.字段名.字段名说明,查询表的数据条数.表名.中文表名. 查询所有表名:select t.table_name from user_tables t;查询所有字段名:se ...