上一篇回顾:《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声明和相关操作定义
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, "\r\n", 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