分布式缓存系统 Memcached 状态机之SET、GET命令
首先对状态机中的各种状态做个简单总结,具体可见状态转换示意图:
1.listening:这个状态是主线程的默认状态,它只有这一个状态:负责监听socket,接收客户连接,将连接socket派发给工作线程。
2.conn_new_cmd:每个工作线程的接收到新连接的初始状态,为处理该连接socket准系列准备工作:如清空读写buf等。
3.conn_waiting:当读缓冲区无数据可以处理时,工作线进入等待状态:在event_base中注册读事件,然后状态机暂停,挂起当前connection(函数退出,回调函数的attachment会记录这个connection),等待有新的信息过来,然后通过回调函数的attachment重新找到这个connection,然后启动状态机。
4.conn_read:有网络数据到来,工作线程从sfd中读取客户端的指令信息。
5.conn_parse_cmd:解析读缓冲中的数据,得到客户端的具体指令,如果是update的指令,那么需要跳转到conn_nread中,因为需要在从网络中读取固定byte的数据,如果是查询之类的指令,就直接查询完成后,跳转到conn_mwrite中,返回数据到客户端。
6.conn_nread:从网络中读取指定大小的数据,这个数据就是更新到item的数据,然后将数据更新到hash和lru中去,然后跳转到conn_write,向客户端返回处理结果状态信息。
7.conn_write:这个状态主要是调用out_string函数会跳转到这个状态,一般都是提示信息和返回的状态信息,然后输出这些数据,然后根据write_to_go的状态,继续跳转
8.conn_mwrite:这个写是把查询msglist(具体item数据)返回到客户端,这个msglist存的是item的数据,用于那种get等获得item的操作的返回数据。
9.conn_swallow:对于那种update操作,如果分配item失败,显然后面的nread,是无效的,客户端是不知道的,这样客户端继续发送特定的数量的数据,就需要把读到的这些数据忽略掉,然后如果把后面指定的数据都忽略掉了(set的两部提交,数据部分忽略掉),那么conn跳转到conn_new_cmd,如果读nread的那些特定数量的数据没有读到,直接跳转到conn_closing。
10.conn_closing:服务器端主动关闭连接,调用close函数关闭文件描述符,同时把conn结构体放到空闲队列CQ中,供新的连接重用这写conn结构体。
接下来就主要分析conn_nread、conn_mwrite、conn_mwrite等这几个跟具体操作指令如get、update等相关的状态。
这些指令都跟底层的数据item的操作相关,如将某个key对应的item从lru链表中删除
(移动对应的空闲链表上)同时在哈希表中删除该item等操作。这部分可参考前面的《slab和item的主要操作》
以及更前面的关于《哈希表》、《lab内存管理》等分析。
当状态机解析出客户端具体指令后就转而执行其指令操作。
首先解析得到SET指令:
//二进制协议,具体解析函数
dispatch_bin_command(conn *c) {
//....
//...
switch (c->cmd) {
case PROTOCOL_BINARY_CMD_SET: /* FALLTHROUGH */
case PROTOCOL_BINARY_CMD_ADD: /* FALLTHROUGH */
case PROTOCOL_BINARY_CMD_REPLACE:
if (extlen == 8 && keylen != 0 && bodylen >= (keylen + 8)) {
bin_read_key(c, bin_reading_set_header, 8);
} else {
protocol_error = 1;
}
break;
}
其中函数bin_read_key中设置状态为conn_nread,使得状态机从网络读取指定字节的数据,用以SET或ADD或REPLACE对应的item数据。
static void bin_read_key(conn *c, enum bin_substates next_substate, int extra) {
//...
//...
/* preserve the header in the buffer.. */
c->ritem = c->rcurr + sizeof(protocol_binary_request_header);
conn_set_state(c, conn_nread);
}
到此,进入nread状态:
case conn_nread:
complete_nread(c);
//...
res = read(c->sfd, c->ritem, c->rlbytes);//读取网络item数据
其中complete_nread:
static void complete_nread(conn *c) {
//....
complete_nread_binary(c);
//.....
}
其中complete_nread_binary函数:
static void complete_nread_binary(conn *c) {
switch(c->substate) {
//....
case bin_read_set_value: //将给定的key对应的item的value设为给定值
complete_update_bin(c);
break;
case bin_reading_get_key:
case bin_reading_touch_key:
process_bin_get_or_touch(c);
break;
//....
}
其中函数complete_update_bin,调用store_item函数。 存储item函数:store_item,可用于add set update等命令语义。
* Stores an item in the cache (high level, obeys set/add/replace semantics)
*/
enum store_item_type store_item(item *item, int comm, conn* c) {
enum store_item_type ret;
uint32_t hv;
hv = hash(ITEM_key(item), item->nkey);//根据key及key长度,计算哈希值
item_lock(hv);
ret = do_store_item(item, comm, c, hv);//与底层数据操作的接口函数
item_unlock(hv);
return ret;
}
经过层层调用,终于到达真正执行add set update等命令的底层操作接口函数:
do_store_item:根据具体具有set语义的命令,对哈希表、slab中的item数据进行操作。
并返回存储操作结果:
enum store_item_type {
NOT_STORED=0, STORED, EXISTS, NOT_FOUND //存储失败;成功;已存在;或没找到
};
函数do_store_item:
enum store_item_type do_store_item(item *it, int comm, conn *c, const uint32_t hv) {
char *key = ITEM_key(it);//获取要set的item对应的key
item *old_it = do_item_get(key, it->nkey, hv);//根据key获取原来的item,如果没不存在则返回NULL
enum store_item_type stored = NOT_STORED;//item状态标记
item *new_it = NULL;
int flags;
if (old_it != NULL && comm == NREAD_ADD) {//如果old_it不为NULL,且操作为add操作
//add只添加原不存在的item,如果存在则只是将该item移到LRU队列头部
do_item_update(old_it);//更新原item在lru队列中的位置和过期时间
//如果原item为空,且操作为repalce等,则不执行任何操作
} else if (!old_it && (comm == NREAD_REPLACE
|| comm == NREAD_APPEND || comm == NREAD_PREPEND))
{
/* replace only replaces an existing value; don't store */
} else if (comm == NREAD_CAS) {//以cas方式读取
/* validate cas operation */
if(old_it == NULL) {
// LRU expired
stored = NOT_FOUND;//修改状态
pthread_mutex_lock(&c->thread->stats.mutex);
c->thread->stats.cas_misses++;
pthread_mutex_unlock(&c->thread->stats.mutex);
}
////old_it不为NULL,且cas属性一致
else if (ITEM_get_cas(it) == ITEM_get_cas(old_it)) {
pthread_mutex_lock(&c->thread->stats.mutex);
c->thread->stats.slab_stats[old_it->slabs_clsid].cas_hits++;//更新线程统计信息
pthread_mutex_unlock(&c->thread->stats.mutex);
//执行item的替换操作,用新的item替换老的item (哈希表 lru)
item_replace(old_it, it, hv);
stored = STORED;
} else {
pthread_mutex_lock(&c->thread->stats.mutex);
c->thread->stats.slab_stats[old_it->slabs_clsid].cas_badval++;
pthread_mutex_unlock(&c->thread->stats.mutex);
if(settings.verbose > 1) {
fprintf(stderr, "CAS: failure: expected %llu, got %llu\n",
(unsigned long long)ITEM_get_cas(old_it),
(unsigned long long)ITEM_get_cas(it));
}
stored = EXISTS;//修改状态值,修改状态值为已经存在,且不存储最新的数据
}
} else {
//以追加方式执行写(追加到旧数据后面 追加到旧数据前面)
if (comm == NREAD_APPEND || comm == NREAD_PREPEND) {
/*
* Validate CAS
*/
if (ITEM_get_cas(it) != 0) {
// CAS much be equal
if (ITEM_get_cas(it) != ITEM_get_cas(old_it)) {
stored = EXISTS;//已存在
}
}
if (stored == NOT_STORED) {//状态值为没有存储,也就是cas验证通过,则执行写操作
flags = (int) strtol(ITEM_suffix(old_it), (char **) NULL, 10);
new_it = do_item_alloc(key, it->nkey, flags, old_it->exptime, it->nbytes + old_it->nbytes - 2 /* CRLF */, hv);
if (new_it == NULL) {//空间不足
if (old_it != NULL)
do_item_remove(old_it);//试着删除原item
return NOT_STORED;
}
//追加方式,将原item的数据拷贝到新item的数据空间,然后将新item(it)的数据追加到原数据的后面
if (comm == NREAD_APPEND) {
memcpy(ITEM_data(new_it), ITEM_data(old_it), old_it->nbytes);
memcpy(ITEM_data(new_it) + old_it->nbytes - 2 /* CRLF */, ITEM_data(it), it->nbytes);
} else {
/* NREAD_PREPEND */
memcpy(ITEM_data(new_it), ITEM_data(it), it->nbytes);//将新item数据拷贝到new_it的数据区
//将原数据追加到新数据的后面
memcpy(ITEM_data(new_it) + it->nbytes - 2 /* CRLF */, ITEM_data(old_it), old_it->nbytes);
}
it = new_it;
}
}
if (stored == NOT_STORED) {
if (old_it != NULL)//旧数据不为空,则替换
item_replace(old_it, it, hv);
else
do_item_link(it, hv);//旧数据为空,则将新的item添加到哈希表 lru队列
c->cas = ITEM_get_cas(it);
stored = STORED;//数据已存储
}
}
if (old_it != NULL)
do_item_remove(old_it); //释放本次引用
if (new_it != NULL)
do_item_remove(new_it);//释放本次引用
if (stored == STORED) {
c->cas = ITEM_get_cas(it);
}
return stored;//返回操作结果状态
}
以上就是SET语义相关的操作,其逻辑还是很清晰的。其中调用了很多关于哈希表,lru队列的操作函数,具体可参见前面的相关内容,在此不再详述。
下面继续看GET相关的操作指令。至于怎么由状态机逐步调用到底层操作函数的过程不再列出了,直接看底层的相关操作接口吧:
//根据key信息和key的长度信息读取数据
item *item_get(const char *key, const size_t nkey) {
item *it;
uint32_t hv;
hv = hash(key, nkey, 0);//获取哈希值
item_lock(hv);//执行分段加锁
it = do_item_get(key, nkey, hv);//执行get操作
item_unlock(hv);//释放锁
return it;
}
函数do_item_get:
get操作在读取数据时,会判断数据的有效性,从而不用专门去处理过期数据,这也正是惰性机制的体现。
//执行读取操作 (get中体现的正是惰性删除机制)
item *do_item_get(const char *key, const size_t nkey, const uint32_t hv) {
item *it = assoc_find(key, nkey, hv);//在哈希表中查找该item,然后利用该item的clsid信息去lru中查找该item
if (it != NULL) {
refcount_incr(&it->refcount);//item的引用次数+1
if (slab_rebalance_signal && //如果正在进行slab调整,且该item是调整的对象
((void *)it >= slab_rebal.slab_start && (void *)it < slab_rebal.slab_end)) {
do_item_unlink_nolock(it, hv);//将item从hashtable和LRU链中移除
do_item_remove(it);//删除item
it = NULL;//置为空
}
}
int was_found = 0;
//打印调试信息
if (settings.verbose > 2) {
if (it == NULL) {
fprintf(stderr, "> NOT FOUND %s", key);
} else {
fprintf(stderr, "> FOUND KEY %s", ITEM_key(it));
was_found++;
}
}
if (it != NULL) {
//判断Memcached初始化是否开启过期删除机制,如果开启,则执行删除相关操作
if (settings.oldest_live != 0 && settings.oldest_live <= current_time &&
it->time <= settings.oldest_live) {
do_item_unlink(it, hv);//将item从hashtable和LRU链中移除
do_item_remove(it);//删除item
it = NULL;
if (was_found) {
fprintf(stderr, " -nuked by flush");
}
//判断item是否过期
} else if (it->exptime != 0 && it->exptime <= current_time) {
do_item_unlink(it, hv);//将item从hashtable和LRU链中移除
do_item_remove(it);//删除item
it = NULL;
if (was_found) {
fprintf(stderr, " -nuked by expire");
}
} else {
it->it_flags |= ITEM_FETCHED;//item的标识修改为已经读取
DEBUG_REFCNT(it, '+');
}
}
if (settings.verbose > 2)
fprintf(stderr, "\n");
return it;
}
以上就是SET、GET的操作过程了。至于其他的命令实现于此类似,由于底层的的所有操作在前面的相关小节中已经作了较为深入的分析,因此在这就不再一一详述了。
到这,整个状态机drive_machine的主要过程就分析得差不多了。通过最近三小节的分析,对memcached的整个运行流程,有了本质的理解,再结合前面底层数据存储结构的分析,整个memcached的基本框架终于搭起来了。
后面将进行一系列的总结归纳,并结合几个小的实例具体去配置、运行下,不能总是纸上谈兵的!
分布式缓存系统 Memcached 状态机之SET、GET命令的更多相关文章
- 分布式缓存系统 Memcached 状态机之socket连接与派发
上节已经分析到了主线程中监听socket注册事件和工作线程中连接socket注册事件的回调函数都是event_handler,且event_handler的核心部分都是一个有限状态机:drive_ma ...
- 分布式缓存系统 Memcached 状态机之网络数据读取与解析
整个状态机的基本流程如下图所示,后续分析将按该流程来进行. 接上节分解,主线程将接收的连接socket分发给了某工作线程,然后工作线程从任务队列中取出该连接socket的CQ_ITEM,开始处理该连接 ...
- 分布式缓存系统 Memcached 整体架构
分布式缓存系统 Memcached整体架构 Memcached经验分享[架构方向] Memcached 及 Redis 架构分析和比较
- 分布式缓存系统Memcached简介与实践
缘起: 在数据驱动的web开发中,经常要重复从数据库中取出相同的数据,这种重复极大的增加了数据库负载.缓存是解决这个问题的好办法.但是ASP.NET中的虽然已经可以实现对页面局部进行缓存,但还是不够灵 ...
- 分布式缓存系统Memcached简介与实践(.NET memcached client library)
缘起: 在数据驱动的web开发中,经常要重复从数据库中取出相同的数据,这种重复极大的增加了数据库负载.缓存是解决这个问题的好办法.但是ASP.NET中的虽然已经可以实现对页面局部进行缓存,但还是不够灵 ...
- [Memcached]分布式缓存系统Memcached在Asp.net下的应用
Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态.数据库驱动网站的速度.Memcached ...
- (转)C# 中使用分布式缓存系统Memcached
转自:http://blog.csdn.net/devgis/article/details/8212917 缘起: 在数据驱动的web开发中,经常要重复从数据库中取出相同的数据,这种重复极大的增加了 ...
- 分布式缓存系统Memcached在Asp.net下的应用
Memcached 是一个高性能的分布式内存对象缓存系统.用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来降低读取数据库的次数,从而提高动态.数据库驱动站点的速度. Memcache ...
- 分布式缓存系统Memcached简介与以及在.net下的实践(转)
缘起: 在数据驱动的web开发中,经常要重复从数据库中取出相同的数据,这种重复极大的增加了数据库负载.缓存是解决这个问题的好办法.但是ASP.NET中的虽然已经可以实现对页面局部进行缓存,但还是不够灵 ...
随机推荐
- 用纯css写一个常见的小三角形
.test{ margin:50px auto; width: 0; height: 0; overflow: hidden; border-width: 10px; border-color: #0 ...
- 绝对布局absoluteLayout
绝对布局absoluteLayout 一.简介 二.实例 绝对布局我们是指定的横纵坐标,所以可以这样直接拖 绝对布局实际中用的少
- Selenium with Python 002 - 快速入门
一.简单实例演示 1.创建 python_org_search.py: #!/usr/bin/env python from selenium import webdriver from seleni ...
- softmax回归(理论部分解释)
前面我们已经说了logistic回归,训练样本是,(且这里的是d维,下面模型公式的x是d+1维,其中多出来的一维是截距横为1,这里的y=±1也可以写成其他的值,这个无所谓不影响模型,只要是两类问题就可 ...
- 【python】self用法详解
在介绍Python的self用法之前,先来介绍下Python中的类和实例我们知道,面向对象最重要的概念就是类(class)和实例(instance). 类是抽象的模板,比如学生这个抽象的事物,可以用一 ...
- Java基础摘要(一)
三大特性 封装 所谓封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏.封装是面向对象的特征之一,是对象和类概念的主要特性.简单的说,一 ...
- jenkins-启动和关闭服务
笔者没有把Jenkins配置到tomcat中,每次都是用命令行来启动Jenkins.但是遇到一个问题:Jenkins一直是开着的,想关闭也关闭不了.百度了一些资料,均不靠谱(必须吐槽一下百度).于是进 ...
- 【Java 并发】Executor框架机制与线程池配置使用
[Java 并发]Executor框架机制与线程池配置使用 一,Executor框架Executor框架便是Java 5中引入的,其内部使用了线程池机制,在java.util.cocurrent 包下 ...
- socketserver模块简介
1. socketserver模块简介 在python的socket编程中,实用socket模块的时候,是不能实现多个连接的,当然如果加入其 它的模块是可以的,例如select模块,在这里见到的介绍下 ...
- 【MFC】断言(ASSERT)的用法
摘自:Moondark http://www.cnblogs.com/moondark/archive/2012/03/12/2392315.html 断言(ASSERT)的用法 我一直以为as ...