zoukankan      html  css  js  c++  java
  • memcached学习笔记——存储命令源码分析下篇

    上一篇回顾:《memcached学习笔记——存储命令源码分析上篇》通过分析memcached的存储命令源码的过程,了解了memcached如何解析文本命令和mencached的内存管理机制。

    本文是延续上一篇,继续分析存储命令的源码。接上一篇内存分配成功后,本文主要讲解:1、memcached存储方式;2、add和set命令的区别。

    memcached存储方式

    哈希表(HashTable)

    哈希表在实践中使用的非常广泛,例如编译器通常会维护的一个符号表来保存标记,很多高级语言中也显式的支持哈希表。 哈希表通常提供查找(Search),插入(Insert),删除(Delete)等操作,这些操作在最坏的情况下和链表的性能一样为O(n)。 不过通常并不会这么坏,合理设计的哈希算法能有效的避免这类情况,通常哈希表的这些操作时间复杂度为O(1)。 这也是它被钟爱的原因。

    memcached是通过一个HashTable来存储所有的item(注:memcached的slab模型是管理内存的,HashTable是用来存储数据的),memcached中HashTable的哈希冲突解决方法就是链接法,memcached的如此高效查询可以归功于HashTable。

    memcached的HashTable是声明和操作在assoc.c文件中,下面我们先看看memcached的HashTable声明和相关操作定义

     1 /* primary_hashtable是主要的HashTable */
     2 static item** primary_hashtable = 0;
     3 
     4 /* 如果memcached对HashTable进行扩展,那么旧的数据就会被存放在old_hashtable */
     5 static item** old_hashtable = 0;
     6 
     7 /* HashTable中保存item的数量 */
     8 static unsigned int hash_items = 0;
     9 
    10 /* expanding是标记HashTable是否进行了扩展,如果进行了扩展,那么进行查询的时候就会在primary_hashtable和old_hashtable中查询,否则只会在primary_hashtable中查询 */
    11 static bool expanding = false;
    12 
    13 /* HashTable初始化函数 */
    14 void assoc_init(const int hashpower_init);
    15 /* HashTable查询操作 */
    16 item *assoc_find(const char *key, const size_t nkey, const uint32_t hv);
    17 /* HashTable插入操作 */
    18 int assoc_insert(item *item, const uint32_t hv);
    19 /* HashTable删除操作 */
    20 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)根据返回的结果,向客户端返回本次命令的最终结果

     1 static void complete_nread_ascii(conn *c) {
     2     assert(c != NULL);
     3 
     4     item *it = c->item;
     5     int comm = c->cmd;
     6     enum store_item_type ret;
     7 
     8     pthread_mutex_lock(&c->thread->stats.mutex);
     9     c->thread->stats.slab_stats[it->slabs_clsid].set_cmds++;
    10     pthread_mutex_unlock(&c->thread->stats.mutex);
    11 
    12     if (strncmp(ITEM_data(it) + it->nbytes - 2, "
    ", 2) != 0) {
    13         out_string(c, "CLIENT_ERROR bad data chunk");
    14     } else {
    15       ret = store_item(it, comm, c);  // memcached存储item操作
    16       
    17 
    18     //........
    19 
    20 
    21       switch (ret) {
    22       case STORED:
    23           out_string(c, "STORED");       // 存储成功后客户端得到的结果
    24           break;
    25       case EXISTS:
    26           out_string(c, "EXISTS");
    27           break;
    28       case NOT_FOUND:
    29           out_string(c, "NOT_FOUND");
    30           break;
    31       case NOT_STORED:
    32           out_string(c, "NOT_STORED");  // 我们通过add存储一个已经存在的key的时候会得到这样的结果
    33           break;
    34       default:
    35           out_string(c, "SERVER_ERROR Unhandled storage type.");
    36       }
    37 
    38     }
    39     
    40     //.........
    41 }

    store_item(thread.c)

     1 enum store_item_type store_item(item *item, int comm, conn* c) {
     2     enum store_item_type ret;
     3     uint32_t hv;
     4 
     5     // memcached根据hash算法计算出当前item的key的hash值hv
     6     hv = hash(ITEM_key(item), item->nkey, 0);
     7 
     8     item_lock(hv);
     9     // memcached存储item的核心操作
    10     ret = do_store_item(item, comm, c, hv);
    11     item_unlock(hv);
    12     return ret;
    13 }

    do_store_item(memcached.c)

     1 enum store_item_type do_store_item(item *it, int comm, conn *c, const uint32_t hv) {                                        //comm 是命令
     2     char *key = ITEM_key(it);
     3 
     4     // 通过key和hv哈希值查询HashTable(assoc_find,后面会讲解)中是否已经存在对应的item
     5     item *old_it = do_item_get(key, it->nkey, hv);
     6 
     7     // 存储的结果,初始值是NOT_STORED
     8     enum store_item_type stored = NOT_STORED;
     9 
    10     item *new_it = NULL;
    11     int flags;
    12 
    13     if (old_it != NULL && comm == NREAD_ADD) {
    14         /* add only adds a nonexistent item, but promote to head of LRU */
    15         // 数据项不为空, 更新时间
    16         // 如果调用的是add命令并且,之前已经存在了相应的key的,那么就只是修改使用时间,stored的值还是NOT_STORED,
    17         // 所以调用add来添加已经存在的项,会得到NOT_STORED的结果,key对应的value没有改变,在complete_nread输出信息
    18         do_item_update(old_it);
    19 
    20     } else if (!old_it && (comm == NREAD_REPLACE
    21         || comm == NREAD_APPEND || comm == NREAD_PREPEND))
    22     {
    23     
    24         // ..........
    25         
    26     } else if (comm == NREAD_CAS) {
    27     
    28         // ............
    29         
    30     } else {  
    31         // old_it为空,并且comm为add、set、replace、append;或者old_it不为空,并且comm为set、replace、append
    32         /*
    33          * Append - combine new and old record into single one. Here it's
    34          * atomic and thread-safe.
    35          */
    36         if (comm == NREAD_APPEND || comm == NREAD_PREPEND) {
    37         
    38             // ..........
    39             
    40         }
    41         
    42         // 存储的结果,初始值是NOT_STORED
    43         if (stored == NOT_STORED) {
    44             if (old_it != NULL)
    45                 item_replace(old_it, it, hv);    // old_it不为空,并且命令为set:在HashTable中用新的item it替换旧的item old_it
    46             else
    47                 do_item_link(it, hv);      // old_it为空,并且命令为add、set:那么就把item it插入到HashTable中
    48 
    49             c->cas = ITEM_get_cas(it);
    50 
    51             stored = STORED;   // 修改存储的结果
    52         }
    53     }// end if
    54 
    55     
    56     // .........
    57 
    58     return stored;
    59 }

    最后我们看看assoc_find函数,HashTable的查询操作

     1 item *assoc_find(const char *key, const size_t nkey, const uint32_t hv) {
     2     item *it;
     3     unsigned int oldbucket;
     4 
     5     // 正如我们上面:expanding是标记HashTable是否进行了扩展,如果进行了扩展,那么进行查询的时候就会在primary_hashtable和old_hashtable中查询,否则只会在primary_hashtable中查询
     6     // 这里通过key和hv哈希值,先定位item所在链表
     7     if (expanding &&
     8         (oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)
     9     {
    10         it = old_hashtable[oldbucket];
    11     } else {
    12         it = primary_hashtable[hv & hashmask(hashpower)];
    13     }
    14 
    15     item *ret = NULL;
    16     int depth = 0;
    17     // 遍历上面找到的链表it,从it中查询key对应的item
    18     // 返回找到的item ret,否则就返回NULL
    19     while (it) {
    20         if ((nkey == it->nkey) && (memcmp(key, ITEM_key(it), nkey) == 0)) {
    21             ret = it;
    22             break;
    23         }
    24         it = it->h_next;  
    25         ++depth;
    26     }
    27     MEMCACHED_ASSOC_FIND(key, nkey, depth);
    28     return ret;
    29 }

    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

  • 相关阅读:
    vmware esxi 过期,激活
    mysql 导入csv文件
    定时登录下载sftp服务器上的某些有规则的文件
    修改tmp的临时目录的地址
    jetty权威指南
    将linux下的rm命令改造成mv到指定的目录下
    mysql 5.6 grant授权的时候出现问题
    Keras vs. PyTorch
    Visualizing LSTM Layer with t-sne in Neural Networks
    keras Lambda 层
  • 原文地址:https://www.cnblogs.com/szuyuan/p/4176317.html
Copyright © 2011-2022 走看看