首先对状态机中的各种状态做个简单总结,具体可见状态转换示意图:

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命令的更多相关文章

  1. 分布式缓存系统 Memcached 状态机之socket连接与派发

    上节已经分析到了主线程中监听socket注册事件和工作线程中连接socket注册事件的回调函数都是event_handler,且event_handler的核心部分都是一个有限状态机:drive_ma ...

  2. 分布式缓存系统 Memcached 状态机之网络数据读取与解析

    整个状态机的基本流程如下图所示,后续分析将按该流程来进行. 接上节分解,主线程将接收的连接socket分发给了某工作线程,然后工作线程从任务队列中取出该连接socket的CQ_ITEM,开始处理该连接 ...

  3. 分布式缓存系统 Memcached 整体架构

    分布式缓存系统 Memcached整体架构 Memcached经验分享[架构方向] Memcached 及 Redis 架构分析和比较

  4. 分布式缓存系统Memcached简介与实践

    缘起: 在数据驱动的web开发中,经常要重复从数据库中取出相同的数据,这种重复极大的增加了数据库负载.缓存是解决这个问题的好办法.但是ASP.NET中的虽然已经可以实现对页面局部进行缓存,但还是不够灵 ...

  5. 分布式缓存系统Memcached简介与实践(.NET memcached client library)

    缘起: 在数据驱动的web开发中,经常要重复从数据库中取出相同的数据,这种重复极大的增加了数据库负载.缓存是解决这个问题的好办法.但是ASP.NET中的虽然已经可以实现对页面局部进行缓存,但还是不够灵 ...

  6. [Memcached]分布式缓存系统Memcached在Asp.net下的应用

    Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态.数据库驱动网站的速度.Memcached ...

  7. (转)C# 中使用分布式缓存系统Memcached

    转自:http://blog.csdn.net/devgis/article/details/8212917 缘起: 在数据驱动的web开发中,经常要重复从数据库中取出相同的数据,这种重复极大的增加了 ...

  8. 分布式缓存系统Memcached在Asp.net下的应用

    Memcached 是一个高性能的分布式内存对象缓存系统.用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来降低读取数据库的次数,从而提高动态.数据库驱动站点的速度. Memcache ...

  9. 分布式缓存系统Memcached简介与以及在.net下的实践(转)

    缘起: 在数据驱动的web开发中,经常要重复从数据库中取出相同的数据,这种重复极大的增加了数据库负载.缓存是解决这个问题的好办法.但是ASP.NET中的虽然已经可以实现对页面局部进行缓存,但还是不够灵 ...

随机推荐

  1. 遇到不确定的json格式

    我们在调用webservice接口,或者http接口时,返回的json数据,有时候会因为情况不同,返回的数据格式也不一样. 比如我在调用增加档案接口时,传入要添加的档案id,如果成功了,success ...

  2. xml、json的序列化与反序列化

    xml数据 : XmlSerializer.Serialize   与  XmlSerializer.Deserialize,使用起来稍有些复杂,需要对 “实体模型” 的“对应属性”  进行  节点特 ...

  3. 离线安装Chrome扩展和App

    ------------------离线安装扩展------------------下载1. 用Get CRX扩展到Web Store页,点开一个扩展,右键 Get CRX 可将扩展下载到本机.2. ...

  4. Qt事件系统基本概念

    (转自:http://www.cnblogs.com/andy1987/p/3322059.html) 1. QT事件系统 Qt应用程序的消息处理是基于事件驱动的,程序的每个动作都是由某个事件所触发的 ...

  5. Java8_02_lambda表达式

    一.前言 这一节我们来了解下lambda表达式,主要关注以下几点: 行为参数化 匿名类 Lambda 表达式 方法 引用 二.行为参数化 1.概念 行为参数化(behavior parameteriz ...

  6. http://www.cnblogs.com/peida/archive/2013/05/31/3070790.html深入理解Java:SimpleDateFormat安全的时间格式化

    http://www.cnblogs.com/peida/archive/2013/05/31/3070790.html

  7. POJ 1265 Area (pick定理)

    题目大意:已知机器人行走步数及每一步的坐标变化量,求机器人所走路径围成的多边形的面积.多边形边上和内部的点的数量. 思路:叉积求面积,pick定理求点. pick定理:面积=内部点数+边上点数/2-1 ...

  8. input光标位置不居中问题

    文本输入框默认在谷歌,火狐浏览器中,光标是居中显示的.但在IE7中一开始会在顶部闪烁(输入文字后光标居中),加上行高就可以,值为文本框的高度. 注意要加*号,否则在谷歌浏览其中光标会在顶部闪烁. *l ...

  9. Django之模板继承

    为了在Django模板配置中减少代码的冗余,需使用模板继承 1. 语法 {% block classinfo %} {% endblock} 2. 步骤 (1)创建一个base.html把需要显示的页 ...

  10. python Beautiful Soup的使用

    上一节我们介绍了正则表达式,它的内容其实还是蛮多的,如果一个正则匹配稍有差池,那可能程序就处在永久的循环之中,而且有的小伙伴们也对写正则表 达式的写法用得不熟练,没关系,我们还有一个更强大的工具,叫B ...