Memcached Hash算法
本文来自网易云社区
作者:吕宗胜
Hash算法
1. Memcached Hash介绍
我们在前面的文章中已经介绍过了Memcached的内存管理方式,LRU的策略。由于Memcached的数据存储方式基本上是基于双向链表来实现的,而链表实现的最大好处在于可以快速的进行增删改,但其最大的不足在于其数据的获取只能通过遍历链表的方式来进行。而Memcached使用了Hash算法来进行数据的快速读取。
2. Hash算法
Memcached的Hash算法原理上非常简单。我们用下面的图来说明。
这个数据结构跟我们熟知的HashMap是一致的,数据hash到不同的桶中,当Hash发生冲突的时候,采用了链表来记录相同Hash值的数据。使用Hash算法最重要的一点是如何解决Hash冲突,Memcached采用的链表来解决Hash冲突是较为基本的方式。这种方式的缺陷是当数据量增多,Hash冲突增多时,会发生链表过长的情况。Memcached在这种情况下,会采用扩大桶数量的方式来优化。Memcached的Hash算法本身并不复杂,这里也不再花大篇幅来介绍其Hash算法。
3. 源代码分析
首先我们来看看Memcached的Hash算法:
unsigned int hashpower = HASHPOWER_DEFAULT;/* 这里的hash算法采用的还是按位与的方式来定位Bucket,1<<(n)表示hash桶的数量 */#define hashsize(n) ((ub4)1<<(n))/* 这里是Hash的掩码,数据的hash值与掩码取与操作可以定位到唯一的Hash桶 */#define hashmask(n) (hashsize(n)-1)
下面我们来看看Memcached的增删查找操作:
/* hash列表中Item元素的查找 */item *assoc_find(const char *key, const size_t nkey, const uint32_t hv) {
item *it; unsigned int oldbucket; /* 这一步是找到hash的桶号 */
if (expanding && /* 在Hash列表进行rehash的时候,是按照桶号顺序进行的,所以如果该桶号>=目前正在处理的桶号时,意味着该数据还是旧Hash表中*/
(oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)
{
it = old_hashtable[oldbucket];
} else {
it = primary_hashtable[hv & hashmask(hashpower)];
} item *ret = NULL; int depth = 0; /* 这一步是Hash冲突列表的遍历查找 */
while (it) { /* Item值匹配的标准:
1. key的长度相等
2. key值相等
*/
if ((nkey == it->nkey) && (memcmp(key, ITEM_key(it), nkey) == 0)) {
ret = it; break;
}
it = it->h_next;
++depth;
}
MEMCACHED_ASSOC_FIND(key, nkey, depth); return ret;
}/* 该方法是插入操作,该Key值必须是不存在才行 */int assoc_insert(item *it, const uint32_t hv) { unsigned int oldbucket; /* 这一步是找到该数据应存储的桶号 */
if (expanding &&
(oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)
{
it->h_next = old_hashtable[oldbucket];
old_hashtable[oldbucket] = it;
} else {
it->h_next = primary_hashtable[hv & hashmask(hashpower)];
primary_hashtable[hv & hashmask(hashpower)] = it;
} pthread_mutex_lock(&hash_items_counter_lock);
hash_items++; /* 进行rehash的条件判断,满足rehash的条件如下:
1. 目前不是正处在rehash中
2. hash表中的所有数据量>hash表容量的1.5倍
*/
if (! expanding && hash_items > (hashsize(hashpower) * 3) / 2) {
assoc_start_expand();
}
pthread_mutex_unlock(&hash_items_counter_lock); MEMCACHED_ASSOC_INSERT(ITEM_key(it), it->nkey, hash_items); return 1;
}/* hash表中元素的删除 */void assoc_delete(const char *key, const size_t nkey, const uint32_t hv) { /* 指针的指针,要删除元素的地址指针*/
item **before = _hashitem_before(key, nkey, hv); if (*before) {
item *nxt;
pthread_mutex_lock(&hash_items_counter_lock);
hash_items--;
pthread_mutex_unlock(&hash_items_counter_lock); /* The DTrace probe cannot be triggered as the last instruction
* due to possible tail-optimization by the compiler
*/
MEMCACHED_ASSOC_DELETE(key, nkey, hash_items);
nxt = (*before)->h_next;
(*before)->h_next = 0; /* probably pointless, but whatever. */
*before = nxt; return;
} /* Note: we never actually get here. the callers don't delete things
they can't find. */
assert(*before != 0);
}static item** _hashitem_before (const char *key, const size_t nkey, const uint32_t hv) {
item **pos; unsigned int oldbucket; if (expanding &&
(oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)
{
pos = &old_hashtable[oldbucket];
} else {
pos = &primary_hashtable[hv & hashmask(hashpower)];
} while (*pos && ((nkey != (*pos)->nkey) || memcmp(key, ITEM_key(*pos), nkey))) {
pos = &(*pos)->h_next;
} return pos;
}
在看过了Memcached Hash表中数据的增删查,下面来看看Hash表的扩容实现:
/* 该方法只是Hash扩容的初始化方法 */static void assoc_expand(void) {
old_hashtable = primary_hashtable; /* 从这里可以看出,Hash扩容的方式是重新申请两倍大小的Hash表*/
primary_hashtable = calloc(hashsize(hashpower + 1), sizeof(void *)); if (primary_hashtable) { if (settings.verbose > 1) fprintf(stderr, "Hash table expansion starting\n");
hashpower++;
expanding = true;
expand_bucket = 0;
STATS_LOCK();
stats.hash_power_level = hashpower;
stats.hash_bytes += hashsize(hashpower) * sizeof(void *);
stats.hash_is_expanding = 1;
STATS_UNLOCK();
} else {
primary_hashtable = old_hashtable; /* Bad news, but we can keep running. */
}
}static volatile int do_run_maintenance_thread = 1;#define DEFAULT_HASH_BULK_MOVE 1int hash_bulk_move = DEFAULT_HASH_BULK_MOVE;/* ReHash的线程任务 */static void *assoc_maintenance_thread(void *arg) { mutex_lock(&maintenance_lock); while (do_run_maintenance_thread) { int ii = 0; /* There is only one expansion thread, so no need to global lock. */
/* 这里的hash_bulk_move标记一次rehash的桶的最小个数*/
for (ii = 0; ii < hash_bulk_move && expanding; ++ii) {
item *it, *next; int bucket; void *item_lock = NULL; /* bucket = hv & hashmask(hashpower) =>the bucket of hash table
* is the lowest N bits of the hv, and the bucket of item_locks is
* also the lowest M bits of hv, and N is greater than M.
* So we can process expanding with only one item_lock. cool! */
/*这里对整个桶进行加锁*/
if ((item_lock = item_trylock(expand_bucket))) { for (it = old_hashtable[expand_bucket]; NULL != it; it = next) {
next = it->h_next;
bucket = hash(ITEM_key(it), it->nkey) & hashmask(hashpower);
it->h_next = primary_hashtable[bucket];
primary_hashtable[bucket] = it;
} /* 已经处理掉的桶置为NULL */
old_hashtable[expand_bucket] = NULL; expand_bucket++; /* rehash完成的标记 */
if (expand_bucket == hashsize(hashpower - 1)) {
expanding = false; free(old_hashtable);
STATS_LOCK();
stats.hash_bytes -= hashsize(hashpower - 1) * sizeof(void *);
stats.hash_is_expanding = 0;
STATS_UNLOCK(); if (settings.verbose > 1) fprintf(stderr, "Hash table expansion done\n");
} } else {
usleep(10*1000);
} if (item_lock) {
item_trylock_unlock(item_lock);
item_lock = NULL;
}
} if (!expanding) { /* We are done expanding.. just wait for next invocation */
started_expanding = false;
pthread_cond_wait(&maintenance_cond, &maintenance_lock); /* assoc_expand() swaps out the hash table entirely, so we need
* all threads to not hold any references related to the hash
* table while this happens.
* This is instead of a more complex, possibly slower algorithm to
* allow dynamic hash table expansion without causing significant
* wait times.
*/
pause_threads(PAUSE_ALL_THREADS);
assoc_expand();
pause_threads(RESUME_ALL_THREADS);
}
} return NULL;
}
本文来自网易云社区,经作者吕宗胜授权发布
相关文章:
【推荐】 HTTP/2部署使用
【推荐】 HBase–存储文件HFile结构解析
【推荐】 为什么kubernetes天然适合微服务
Memcached Hash算法的更多相关文章
- 分布式缓存技术memcached学习(四)—— 一致性hash算法原理
分布式一致性hash算法简介 当你看到“分布式一致性hash算法”这个词时,第一时间可能会问,什么是分布式,什么是一致性,hash又是什么.在分析分布式一致性hash算法原理之前,我们先来了解一下这几 ...
- 一致性Hash算法在Memcached中的应用
前言 大家应该都知道Memcached要想实现分布式只能在客户端来完成,目前比较流行的是通过一致性hash算法来实现.常规的方法是将server的hash值与server的总台数进行求余,即hash% ...
- 分布式缓存技术memcached学习系列(四)—— 一致性hash算法原理
分布式一致性hash算法简介 当你看到"分布式一致性hash算法"这个词时,第一时间可能会问,什么是分布式,什么是一致性,hash又是什么.在分析分布式一致性hash算法原理之前, ...
- (转) 一致性Hash算法在Memcached中的应用
前言 大家应该都知道Memcached要想实现分布式只能在客户端来完成,目前比较流行的是通过一致性hash算法来实现.常规的方法是将 server的hash值与server的总台数进行求余,即hash ...
- memcached和一致性hash算法
1 一致性hash算法的一致性 这里的一致性指的是该算法可以保持memcached和数据库中的数据的一致性. 2 什么是一致性hash算法 2.1 为什么需要一致性hash算法 现在有大量的key v ...
- 转: memcached Java客户端spymemcached的一致性Hash算法
转自:http://colobu.com/2015/04/13/consistent-hash-algorithm-in-java-memcached-client/ memcached Java客户 ...
- 一致性hash算法在memcached中的使用
一.概述 1.我们的memcacheclient(这里我看的spymemcache的源代码).使用了一致性hash算法ketama进行数据存储节点的选择.与常规的hash算法思路不同.仅仅是对我们要存 ...
- 追踪分布式Memcached默认的一致性hash算法
<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255) ...
- php hash算法实现memcached分布式
一.概述Memcached和mysql一样,是一款客户端/服务器端(C/S)系统管理软件,有IP.端口,一旦启动,服务器就一直处于可用状态.Mysql是通过SQL语句管理“磁盘中”的文件,Memcac ...
随机推荐
- 【JeeSite】区域和菜单管理
/** * 区域Entity * @author ThinkGem * @version 2013-05-15 */ public class Area extends TreeEntity<A ...
- c# 常见验证邮箱、电话号码、日期等格式
#region 验证邮箱验证邮箱 /**//// <summary> /// 验证邮箱 /// </summary> /// <param name="sour ...
- Mac 使用问题
Mac 使用 Mac改变系统截图存储位置 鼠标在屏幕中间上下滚动时,有时反应不灵敏: 看看偏好设置-> 鼠标 -> 跟踪速度或者手势部分是否有哪里设置不当: 考虑重新开启鼠标鼠标底部有一个 ...
- Ajax全接触(1)
Ajax全称:Asynchronous JavaScript and XML(异步的JavaScript和XML) .Ajax不是某种编程语言 是一种在无需重新加载整个网页的情况之下能够更新部分网页的 ...
- 移动设备HTML5页面布局
在HTML5标准添加的新元素中,用于常见页面结 构的包括header footer footer nav aside aside article section hgroup . 下面简单介绍一下这个 ...
- vim 输入特殊字符
在VIM中可以通过二合字符和十进制进行输入, 在输入模式中,Ctrl + V, 十进制 在输入模式中,Ctrl + K, 二合字符(区分大小写) 注意:特殊字符也算一个字节 通过用的^@是为了保证结尾 ...
- 2018 Wannafly summer camp Day2--Utawarerumono
Utawarerumono 描述 题目描述: 算术是为数不多的会让久远感到棘手的事情.通常她会找哈克帮忙,但是哈克已经被她派去买东西了.于是她向你寻求帮助. 给出一个关于变量x,y的不定方程ax+by ...
- TiDB集群手动安装
TIDB的安装 TiDB 是 PingCAP 公司受 Google Spanner / F1 论文启发而设计的开源分布式 HTAP (Hybrid Transactional and Analytic ...
- 爬虫——urllib.request库的基本使用
所谓网页抓取,就是把URL地址中指定的网络资源从网络流中读取出来,保存到本地.在Python中有很多库可以用来抓取网页,我们先学习urllib.request.(在python2.x中为urllib2 ...
- MB/s与Mbit/s的区别
数据传输率的单位一般采用MB/s或Mbit/s,尤其在内部数据传输率上官方数据中更多的采用Mbit/s为单位.此处有必要讲解一下两个单位二者之间的差异: MB/s的含义是兆字节每秒,Mbit/s的含义 ...