memcached学习笔记——存储命令源码分析下篇
上一篇回顾:《memcached学习笔记——存储命令源码分析上篇》通过分析memcached的存储命令源码的过程,了解了memcached如何解析文本命令和mencached的内存管理机制。
本文是延续上一篇,继续分析存储命令的源码。接上一篇内存分配成功后,本文主要讲解:1、memcached存储方式;2、add和set命令的区别。
memcached存储方式
哈希表在实践中使用的非常广泛,例如编译器通常会维护的一个符号表来保存标记,很多高级语言中也显式的支持哈希表。 哈希表通常提供查找(Search),插入(Insert),删除(Delete)等操作,这些操作在最坏的情况下和链表的性能一样为O(n)。 不过通常并不会这么坏,合理设计的哈希算法能有效的避免这类情况,通常哈希表的这些操作时间复杂度为O(1)。 这也是它被钟爱的原因。
memcached是通过一个HashTable来存储所有的item(注:memcached的slab模型是管理内存的,HashTable是用来存储数据的),memcached中HashTable的哈希冲突解决方法就是链接法,memcached的如此高效查询可以归功于HashTable。
memcached的HashTable是声明和操作在assoc.c文件中,下面我们先看看memcached的HashTable声明和相关操作定义
/* primary_hashtable是主要的HashTable */
static item** primary_hashtable = ; /* 如果memcached对HashTable进行扩展,那么旧的数据就会被存放在old_hashtable */
static item** old_hashtable = ; /* HashTable中保存item的数量 */
static unsigned int hash_items = ; /* expanding是标记HashTable是否进行了扩展,如果进行了扩展,那么进行查询的时候就会在primary_hashtable和old_hashtable中查询,否则只会在primary_hashtable中查询 */
static bool expanding = false; /* HashTable初始化函数 */
void assoc_init(const int hashpower_init);
/* HashTable查询操作 */
item *assoc_find(const char *key, const size_t nkey, const uint32_t hv);
/* HashTable插入操作 */
int assoc_insert(item *item, const uint32_t hv);
/* HashTable删除操作 */
void assoc_delete(const char *key, const size_t nkey, const uint32_t hv);
memcached内存分配成功后,返回新item,接着把这item保存到HashTable中,complete_nread_ascii > store_item > do_store_item
在complete_nread_ascii(memcached.c)中store_item(thread.c)根据返回的结果,向客户端返回本次命令的最终结果
static void complete_nread_ascii(conn *c) {
assert(c != NULL);
item *it = c->item;
int comm = c->cmd;
enum store_item_type ret;
pthread_mutex_lock(&c->thread->stats.mutex);
c->thread->stats.slab_stats[it->slabs_clsid].set_cmds++;
pthread_mutex_unlock(&c->thread->stats.mutex);
if (strncmp(ITEM_data(it) + it->nbytes - , "\r\n", ) != ) {
out_string(c, "CLIENT_ERROR bad data chunk");
} else {
ret = store_item(it, comm, c); // memcached存储item操作
//........
switch (ret) {
case STORED:
out_string(c, "STORED"); // 存储成功后客户端得到的结果
break;
case EXISTS:
out_string(c, "EXISTS");
break;
case NOT_FOUND:
out_string(c, "NOT_FOUND");
break;
case NOT_STORED:
out_string(c, "NOT_STORED"); // 我们通过add存储一个已经存在的key的时候会得到这样的结果
break;
default:
out_string(c, "SERVER_ERROR Unhandled storage type.");
}
}
//.........
}
store_item(thread.c)
enum store_item_type store_item(item *item, int comm, conn* c) {
enum store_item_type ret;
uint32_t hv;
// memcached根据hash算法计算出当前item的key的hash值hv
hv = hash(ITEM_key(item), item->nkey, );
item_lock(hv);
// memcached存储item的核心操作
ret = do_store_item(item, comm, c, hv);
item_unlock(hv);
return ret;
}
do_store_item(memcached.c)
enum store_item_type do_store_item(item *it, int comm, conn *c, const uint32_t hv) { //comm 是命令
char *key = ITEM_key(it);
// 通过key和hv哈希值查询HashTable(assoc_find,后面会讲解)中是否已经存在对应的item
item *old_it = do_item_get(key, it->nkey, hv);
// 存储的结果,初始值是NOT_STORED
enum store_item_type stored = NOT_STORED;
item *new_it = NULL;
int flags;
if (old_it != NULL && comm == NREAD_ADD) {
/* add only adds a nonexistent item, but promote to head of LRU */
// 数据项不为空, 更新时间
// 如果调用的是add命令并且,之前已经存在了相应的key的,那么就只是修改使用时间,stored的值还是NOT_STORED,
// 所以调用add来添加已经存在的项,会得到NOT_STORED的结果,key对应的value没有改变,在complete_nread输出信息
do_item_update(old_it);
} else if (!old_it && (comm == NREAD_REPLACE
|| comm == NREAD_APPEND || comm == NREAD_PREPEND))
{
// ..........
} else if (comm == NREAD_CAS) {
// ............
} else {
// old_it为空,并且comm为add、set、replace、append;或者old_it不为空,并且comm为set、replace、append
/*
* Append - combine new and old record into single one. Here it's
* atomic and thread-safe.
*/
if (comm == NREAD_APPEND || comm == NREAD_PREPEND) {
// ..........
}
// 存储的结果,初始值是NOT_STORED
if (stored == NOT_STORED) {
if (old_it != NULL)
item_replace(old_it, it, hv); // old_it不为空,并且命令为set:在HashTable中用新的item it替换旧的item old_it
else
do_item_link(it, hv); // old_it为空,并且命令为add、set:那么就把item it插入到HashTable中
c->cas = ITEM_get_cas(it);
stored = STORED; // 修改存储的结果
}
}// end if
// .........
return stored;
}
最后我们看看assoc_find函数,HashTable的查询操作
item *assoc_find(const char *key, const size_t nkey, const uint32_t hv) {
item *it;
unsigned int oldbucket;
// 正如我们上面:expanding是标记HashTable是否进行了扩展,如果进行了扩展,那么进行查询的时候就会在primary_hashtable和old_hashtable中查询,否则只会在primary_hashtable中查询
// 这里通过key和hv哈希值,先定位item所在链表
if (expanding &&
(oldbucket = (hv & hashmask(hashpower - ))) >= expand_bucket)
{
it = old_hashtable[oldbucket];
} else {
it = primary_hashtable[hv & hashmask(hashpower)];
}
item *ret = NULL;
int depth = ;
// 遍历上面找到的链表it,从it中查询key对应的item
// 返回找到的item ret,否则就返回NULL
while (it) {
if ((nkey == it->nkey) && (memcmp(key, ITEM_key(it), nkey) == )) {
ret = it;
break;
}
it = it->h_next;
++depth;
}
MEMCACHED_ASSOC_FIND(key, nkey, depth);
return ret;
}
add和set命令的区别
从do_store_item函数中可以看出,1)如果是add命令,但是key对应的value已经存在,那么只是更新key的最近使用时间,value没有被新的value覆盖,返回NOT_STORED的结果;2)如果是add命令,第一次存储,那么value就会添加到HashTable中,返回STORED结果;3)如果是set命令,无论key对应的value是否已经存在,最后新的value会插入到HashTable中,返回STORED结果
最后,感谢各位在大冷天的看完小弟拙文,谢谢!
(完)
更多文章请移步:www.hcoding.com
memcached学习笔记——存储命令源码分析下篇的更多相关文章
- memcached学习笔记——存储命令源码分析上篇
原创文章,转载请标明,谢谢. 上一篇分析过memcached的连接模型,了解memcached是如何高效处理客户端连接,这一篇分析memcached源码中的process_update_command ...
- tornado 学习笔记6 Application 源码分析
Application 是Tornado重要的模块之一,主要是配置访问路由表及其他应用参数的设置. 源代码位于虚拟运行环境文件夹下(我的是env),具体位置为env > lib>sit-p ...
- [Golang学习笔记] 02 命令源码文件
源码文件的三种类型: 命令源文件:可以直接运行的程序,可以不编译而使用命令“go run”启动.执行. 库源码文件 测试源码文件 面试题:命令源码文件的用途是什么,怎样编写它? 典型回答: 命令源码文 ...
- OA学习笔记-010-Struts部分源码分析、Intercepter、ModelDriver、OGNL、EL
一.分析 二. 1.OGNL 在访问action前,要经过各种intercepter,其中ParameterFilterInterceptor会把各咱参数放到ValueStack里,从而使OGNL可以 ...
- Laravel学习笔记之Session源码解析(上)
说明:本文主要通过学习Laravel的session源码学习Laravel是如何设计session的,将自己的学习心得分享出来,希望对别人有所帮助.Laravel在web middleware中定义了 ...
- Hadoop学习笔记(10) ——搭建源码学习环境
Hadoop学习笔记(10) ——搭建源码学习环境 上一章中,我们对整个hadoop的目录及源码目录有了一个初步的了解,接下来计划深入学习一下这头神象作品了.但是看代码用什么,难不成gedit?,单步 ...
- debug:am、cmd命令源码分析
debug:am.cmd命令源码分析 目录 debug:am.cmd命令源码分析 am命令的实现 手机里的am am.jar cmd命令的实现 手机里的cmd cmd activity cmd.cpp ...
- 【RabbitMQ学习记录】- 消息队列存储机制源码分析
本文来自 网易云社区 . RabbitMQ在金融系统,OpenStack内部组件通信和通信领域应用广泛,它部署简单,管理界面内容丰富使用十分方便.笔者最近在研究RabbitMQ部署运维和代码架构,本篇 ...
- TiDB show processlist命令源码分析
背景 因为丰巢自去年年底开始在推送平台上尝试了TiDB,最近又要将承接丰巢所有交易的支付平台切到TiDB上.我本人一直没有抽出时间对TiDB的源码进行学习,最近准备开始一系列的学习和分享.由于我本人没 ...
随机推荐
- Cracking the coding interview--Q1.3
原文 Given two strings, write a method to decide if one is a permutation of the other. 译文 给你两个字符串,写一个方 ...
- AD怎样画 board shape
先用随便那一层的线,画出你想要的边框的形状,圆角可通过shift+空格来切换出来选中你刚刚话的形状(要是闭合面),design-board sharp-define from selected obj ...
- wifi10db定向天线制作及应注意的问题
2.4GHZ本身就是高频要求制作精度高,如果您动手能力差的话还是不要做的好许多网友看到网上的制作资料就急不可耐的去找材料,然后加班加点的制作.等做出了天线发现效果不怎么样,或出了这样和那样的问题,才肯 ...
- 1.Basic Structure
配置: rsyslogd 配置通过rsyslog.conf file,典型的在/etc下.默认的, rsyslogd 读取/etc/rsyslog.conf 文件,这个可以通过命令行选项改变 注意 配 ...
- BZOJ3399: [Usaco2009 Mar]Sand Castle城堡
3399: [Usaco2009 Mar]Sand Castle城堡 Time Limit: 3 Sec Memory Limit: 128 MBSubmit: 22 Solved: 17[Sub ...
- 【转】三星8552 手机提示升级系统 完成后重启 开机画面一直停留在三星的LOGO 一闪一闪 怎么办
原文网址:http://ask.zol.com.cn/q/309501.html 楼主你好,手机出现这种情况的话,可以先将电池取下来重新安装,如果不能的话,可以在关机状态下按住电源键+音量下键之后进入 ...
- hdu-3790最短路径问题
Problem Description 给你n个点,m条无向边,每条边都有长度d和花费p,给你起点s终点t,要求输出起点到终点的最短距离及其花费,如果最短距离有多条路线,则输出花费最少的. Inp ...
- 利用TreeSet给纯数字字符串排序
import java.util.Iterator;import java.util.TreeSet; /* * 给字符串中的数字排序 * String str = "10,2,11,1,3 ...
- Android学习笔记(二)之异步加载图片
最近在android开发中碰到比较棘手的问题,就是加载图片内存溢出.我开发的是一个新闻应用,应用中用到大量的图片,一个界面中可能会有上百张图片.开发android应用的朋友可能或多或少碰到加载图片内存 ...
- 用python演示一个简单的AST(抽象语法树)
如果对'a + 3 * b'进行解释,当中a=2,b=5 代码非常easy,就不再进行具体的解释了. Num = lambda env, n: n Var = lambda env, x: env[x ...