Memcached源码分析

Memcached源码分析之Hash表扩容

Hash表是Memcached里面最重要的结构之一,其采用链接法来处理Hash冲突,当Hash表中的项太多时,也就是Hash冲突比较高的时候,Hash表的遍历就脱变成单链表,此时为了提供Hash的性能,Hash表需要扩容,Memcached的扩容条件是当表中元素个数超过Hash容量的1.5倍时就进行扩容,扩容过程由独立的线程来完成,扩容过程中会采用2个Hash表,将老表中的数据通过Hash算法映射到新表中,每次移动的桶的数目可以配置,默认是每次移动老表中的1个桶。

[cpp]  view plain  copy
 
  1. //hash表中增加元素  
  2. int assoc_insert(item *it, const uint32_t hv) {  
  3.     unsigned int oldbucket;  
  4.     //如果已经进行扩容且目前进行扩容还没到需要插入元素的桶,则将元素添加到旧桶中  
  5.     if (expanding &&(oldbucket = (hv & hashmask(hashpower - 1))) >= expand_bucket)  
  6.     {  
  7.         it->h_next = old_hashtable[oldbucket];//添加元素  
  8.         old_hashtable[oldbucket] = it;  
  9.     } else {//如果没扩容,或者扩容已经到了新的桶中,则添加元素到新表中  
  10.         it->h_next = primary_hashtable[hv & hashmask(hashpower)];//添加元素  
  11.         primary_hashtable[hv & hashmask(hashpower)] = it;  
  12.     }  
  13.   
  14.     hash_items++;//元素数目+1  
  15.     //还没开始扩容,且表中元素个数已经超过Hash表容量的1.5倍  
  16.     if (! expanding && hash_items > (hashsize(hashpower) * 3) / 2) {  
  17.         assoc_start_expand();//唤醒扩容线程  
  18.     }  
  19.   
  20.     MEMCACHED_ASSOC_INSERT(ITEM_key(it), it->nkey, hash_items);  
  21.     return 1;  
  22. }  
  23. //唤醒扩容线程  
  24. static void assoc_start_expand(void) {  
  25.     if (started_expanding)  
  26.         return;  
  27.     started_expanding = true;  
  28.     pthread_cond_signal(&maintenance_cond);//唤醒信号量  
  29. }  
  30. //启动扩容线程,扩容线程在main函数中会启动,启动运行一遍之后会阻塞在条件变量maintenance_cond上面,插入元素超过规定,唤醒条件变量  
  31. static void *assoc_maintenance_thread(void *arg) {  
  32.     //do_run_maintenance_thread的值为1,即该线程持续运行  
  33.     while (do_run_maintenance_thread) {  
  34.         int ii = 0;  
  35.   
  36.         item_lock_global();//加Hash表的全局锁  
  37.         mutex_lock(&cache_lock);//加cache_lock锁  
  38.         //执行扩容时,每次按hash_bulk_move个桶来扩容  
  39.         for (ii = 0; ii < hash_bulk_move && expanding; ++ii) {  
  40.             item *it, *next;  
  41.             int bucket;  
  42.             //老表每次移动一个桶中的一个元素  
  43.             for (it = old_hashtable[expand_bucket]; NULL != it; it = next) {  
  44.                 next = it->h_next;//要移动的下一个元素  
  45.   
  46.                 bucket = hash(ITEM_key(it), it->nkey, 0) & hashmask(hashpower);//按新的Hash规则进行定位  
  47.                 it->h_next = primary_hashtable[bucket];//挂载到新的Hash表中  
  48.                 primary_hashtable[bucket] = it;  
  49.             }  
  50.   
  51.             old_hashtable[expand_bucket] = NULL;//旧表中的这个Hash桶已经按新规则完成了扩容  
  52.   
  53.             expand_bucket++;//老表中的桶计数+1  
  54.             if (expand_bucket == hashsize(hashpower - 1)) {//hash表扩容结束,expand_bucket从0开始,一直递增  
  55.                 expanding = false;//修改扩容标志  
  56.                 free(old_hashtable);//释放老的表结构  
  57.                 STATS_LOCK();//更新一些统计信息  
  58.                 stats.hash_bytes -= hashsize(hashpower - 1) * sizeof(void *);  
  59.                 stats.hash_is_expanding = 0;  
  60.                 STATS_UNLOCK();  
  61.                 if (settings.verbose > 1)  
  62.                     fprintf(stderr, "Hash table expansion done\n");  
  63.             }  
  64.         }  
  65.   
  66.         mutex_unlock(&cache_lock);//释放cache_lock锁  
  67.         item_unlock_global();//释放Hash表的全局锁  
  68.   
  69.         if (!expanding) {//完成扩容  
  70.             //修改Hash表的锁类型,此时锁类型更新为分段锁,默认是分段锁,在进行扩容时,改为全局锁  
  71.             switch_item_lock_type(ITEM_LOCK_GRANULAR);  
  72.             slabs_rebalancer_resume();//释放用于扩容的锁  
  73.             /* We are done expanding.. just wait for next invocation */  
  74.             mutex_lock(&cache_lock);//加cache_lock锁,保护条件变量  
  75.             started_expanding = false;//修改扩容标识  
  76.             pthread_cond_wait(&maintenance_cond, &cache_lock);//阻塞扩容线程  
  77.             mutex_unlock(&cache_lock);  
  78.             slabs_rebalancer_pause();//加用于扩容的锁  
  79.             switch_item_lock_type(ITEM_LOCK_GLOBAL);//修改锁类型为全局锁  
  80.             mutex_lock(&cache_lock);//临时用来实现临界区  
  81.             assoc_expand();//执行扩容  
  82.             mutex_unlock(&cache_lock);  
  83.         }  
  84.     }  
  85.     return NULL;  
  86. }  
  87. //按2倍容量扩容Hash表  
  88. static void assoc_expand(void) {  
  89.     old_hashtable = primary_hashtable;//old_hashtable指向主Hash表  
  90.   
  91.     primary_hashtable = calloc(hashsize(hashpower + 1), sizeof(void *));//申请新的空间  
  92.     if (primary_hashtable) {//空间申请成功  
  93.         if (settings.verbose > 1)  
  94.             fprintf(stderr, "Hash table expansion starting\n");  
  95.         hashpower++;//hash等级+1  
  96.         expanding = true;//扩容标识打开  
  97.         expand_bucket = 0;  
  98.         STATS_LOCK();//更新全局统计信息  
  99.         stats.hash_power_level = hashpower;  
  100.         stats.hash_bytes += hashsize(hashpower) * sizeof(void *);  
  101.         stats.hash_is_expanding = 1;  
  102.         STATS_UNLOCK();  
  103.     } else {//空间事情失败  
  104.         primary_hashtable = old_hashtable;  
  105.     }  
  106. }  

Memcached源码分析之item结构

item是Memcached中抽象实际数据的结构,我们分析下item的一些特性,便于后续Memcached的其他特性分析。

[cpp]  view plain  copy
 
  1. typedef struct _stritem {  
  2.     struct _stritem *next;//item在slab中存储时,是以双链表的形式存储的,next即后向指针  
  3.     struct _stritem *prev;//prev为前向指针  
  4.     struct _stritem *h_next;//Hash桶中元素的链接指针  
  5.     rel_time_t      time; //最近访问时间  
  6.     rel_time_t      exptime;//过期时间  
  7.     int             nbytes;//数据大小  
  8.     unsigned short  refcount;//引用次数  
  9.     uint8_t         nsuffix;    //不清楚什么意思?  
  10.     uint8_t         it_flags;   //不清楚什么意思?  
  11.     uint8_t         slabs_clsid;//标记item属于哪个slabclass下  
  12.     uint8_t         nkey;       //key的长度  
  13.     union {  
  14.         uint64_t cas;  
  15.         char end;  
  16.     } data[];//真实的数据信息  
  17. } item;  

其结构图如下所示:

Memcached源码分析_第1张图片

即Item由两部分组成,item的属性信息和item的数据部分,属性信息解释如上,数据部分包括cas,key和真实的value信息,item在内存中的存储形式如下:

Memcached源码分析_第2张图片

这个图画出了部分结构,还有Hash表的结构没有画出。

Memcached源码分析_第3张图片


这里大概介绍了item的一些信息,后面我们会分析item插入Hash表等信息。

注:本篇博客的图片摘自:http://kenby.iteye.com/blog/1423989

                                               http://www.nosqlnotes.net/archives/222



Memcached源码阅读之get过程

我们在前面分析过,Memcached从网络读取完数据,解析数据,如果是get操作,则执行get操作,下面我们分析下get操作的流程。

[cpp]  view plain  copy
 
  1. //根据key信息和key的长度信息读取数据  
  2. item *item_get(const char *key, const size_t nkey) {  
  3.     item *it;  
  4.     uint32_t hv;  
  5.     hv = hash(key, nkey, 0);//获得分段锁信息,如果未进行扩容,则item的hash表是多个hash桶共用同一个锁,即是分段的锁  
  6.     item_lock(hv);//执行分段加锁  
  7.     it = do_item_get(key, nkey, hv);//执行get操作  
  8.     item_unlock(hv);//释放锁  
  9.     return it;  
  10. }  
  11. //执行分段加锁  
  12. void item_lock(uint32_t hv) {  
  13.     uint8_t *lock_type = pthread_getspecific(item_lock_type_key);  
  14.     if (likely(*lock_type == ITEM_LOCK_GRANULAR)) {  
  15.         mutex_lock(&item_locks[(hv & hashmask(hashpower)) % item_lock_count]);//执行分段加锁  
  16.     } else {//如果在扩容过程中  
  17.         mutex_lock(&item_global_lock);  
  18.     }  
  19. }  
  20. //执行分段解锁  
  21. void item_unlock(uint32_t hv) {  
  22.     uint8_t *lock_type = pthread_getspecific(item_lock_type_key);  
  23.     if (likely(*lock_type == ITEM_LOCK_GRANULAR)) {  
  24.         mutex_unlock(&item_locks[(hv & hashmask(hashpower)) % item_lock_count]);//释放分段锁  
  25.     } else {//如果在扩容过程中  
  26.         mutex_unlock(&item_global_lock);  
  27.     }  
  28. }  
  29. //执行读取操作  
  30. item *do_item_get(const char *key, const size_t nkey, const uint32_t hv) {  
  31.     item *it = assoc_find(key, nkey, hv);//从Hash表中获取相应的结构  
  32.     if (it != NULL) {  
  33.         refcount_incr(&it->refcount);//item的引用次数+1  
  34.         if (slab_rebalance_signal && //如果正在进行slab调整,且该item是调整的对象  
  35.             ((void *)it >= slab_rebal.slab_start && (void *)it < slab_rebal.slab_end)) {  
  36.             do_item_unlink_nolock(it, hv);//将item从hashtable和LRU链中移除  
  37.             do_item_remove(it);//删除item  
  38.             it = NULL;//置为空  
  39.         }  
  40.     }  
  41.     int was_found = 0;  
  42.     //打印调试信息  
  43.     if (settings.verbose > 2) {  
  44.         if (it == NULL) {  
  45.             fprintf(stderr, "> NOT FOUND %s", key);  
  46.         } else {  
  47.             fprintf(stderr, "> FOUND KEY %s", ITEM_key(it));  
  48.             was_found++;  
  49.         }  
  50.     }  
  51.   
  52.     if (it != NULL) {  
  53.         //判断Memcached初始化是否开启过期删除机制,如果开启,则执行删除相关操作  
  54.         if (settings.oldest_live != 0 && settings.oldest_live <= current_time &&  
  55.             it->time <= settings.oldest_live) {  
  56.             do_item_unlink(it, hv);//将item从hashtable和LRU链中移除             
  57.             do_item_remove(it);//删除item  
  58.             it = NULL;  
  59.             if (was_found) {  
  60.                 fprintf(stderr, " -nuked by flush");  
  61.             }  
  62.         //判断item是否过期  
  63.         } else if (it->exptime != 0 && it->exptime <= current_time) {  
  64.             do_item_unlink(it, hv);//将item从hashtable和LRU链中移除  
  65.             do_item_remove(it);//删除item  
  66.             it = NULL;  
  67.             if (was_found) {  
  68.                 fprintf(stderr, " -nuked by expire");  
  69.             }  
  70.         } else {  
  71.             it->it_flags |= ITEM_FETCHED;//item的标识修改为已经读取  
  72.             DEBUG_REFCNT(it, '+');  
  73.         }  
  74.     }  
  75.   
  76.     if (settings.verbose > 2)  
  77.         fprintf(stderr, "\n");  
  78.   
  79.     return it;  
  80. }  
  81.   
  82. //移除item  
  83. void do_item_remove(item *it) {  
  84.     MEMCACHED_ITEM_REMOVE(ITEM_key(it), it->nkey, it->nbytes);  
  85.     assert((it->it_flags & ITEM_SLABBED) == 0);//判断item的状态是否正确  
  86.   
  87.     if (refcount_decr(&it->refcount) == 0) {//修改item的引用次数  
  88.         item_free(it);//释放item  
  89.     }  
  90. }  
  91. //释放item  
  92. void item_free(item *it) {  
  93.     size_t ntotal = ITEM_ntotal(it);//获得item的大小  
  94.     unsigned int clsid;  
  95.     assert((it->it_flags & ITEM_LINKED) == 0);//判断item的状态是否正确  
  96.     assert(it != heads[it->slabs_clsid]);//item不能为LRU的头指针  
  97.     assert(it != tails[it->slabs_clsid]);//item不能为LRU的尾指针  
  98.     assert(it->refcount == 0);//释放时,需保证引用次数为0  
  99.   
  100.     /* so slab size changer can tell later if item is already free or not */  
  101.     clsid = it->slabs_clsid;  
  102.     it->slabs_clsid = 0;//断开slabclass的链接  
  103.     DEBUG_REFCNT(it, 'F');  
  104.     slabs_free(it, ntotal, clsid);//slabclass结构执行释放  
  105. }   
  106. //slabclass结构释放  
  107. void slabs_free(void *ptr, size_t size, unsigned int id) {  
  108.     pthread_mutex_lock(&slabs_lock);//保持同步  
  109.     do_slabs_free(ptr, size, id);//执行释放  
  110.     pthread_mutex_unlock(&slabs_lock);  
  111. }  
  112. //slabclass结构释放  
  113. static void do_slabs_free(void *ptr, const size_t size, unsigned int id) {  
  114.     slabclass_t *p;  
  115.     item *it;  
  116.   
  117.     assert(((item *)ptr)->slabs_clsid == 0);//判断数据是否正确  
  118.     assert(id >= POWER_SMALLEST && id <= power_largest);//判断id合法性  
  119.     if (id < POWER_SMALLEST || id > power_largest)//判断id合法性  
  120.         return;  
  121.   
  122.     MEMCACHED_SLABS_FREE(size, id, ptr);  
  123.     p = &slabclass[id];  
  124.   
  125.     it = (item *)ptr;  
  126.     it->it_flags |= ITEM_SLABBED;//修改item的状态标识,修改为空闲  
  127.     it->prev = 0;//断开数据链表  
  128.     it->next = p->slots;  
  129.     if (it->next) it->next->prev = it;  
  130.     p->slots = it;  
  131.   
  132.     p->sl_curr++;//空闲item个数+1  
  133.     p->requested -= size;//空间增加size  
  134.     return;  
  135. }  
  136. //将item从hashtable和LRU链中移除。是do_item_link的逆操作  
  137. void do_item_unlink(item *it, const uint32_t hv) {  
  138.     MEMCACHED_ITEM_UNLINK(ITEM_key(it), it->nkey, it->nbytes);  
  139.     mutex_lock(&cache_lock);//执行同步  
  140.     if ((it->it_flags & ITEM_LINKED) != 0) {//判断状态值,保证item还在LRU队列中  
  141.         it->it_flags &= ~ITEM_LINKED;//修改状态值  
  142.         STATS_LOCK();//更新统计信息  
  143.         stats.curr_bytes -= ITEM_ntotal(it);  
  144.         stats.curr_items -= 1;  
  145.         STATS_UNLOCK();  
  146.         assoc_delete(ITEM_key(it), it->nkey, hv);//从Hash表中删除  
  147.         item_unlink_q(it);//将item从slabclass对应的LRU队列摘除  
  148.         do_item_remove(it);//移除item  
  149.     }  
  150.     mutex_unlock(&cache_lock);  
  151. }  

Memcached的get操作在读取数据时,会判断数据的有效性,使得不用额外去处理过期数据,get操作牵涉到Slab结构,Hash表,LRU队列的更新,我们后面专门分析这些的变更,这里暂不分析。


 

Memcached源码分析之set操作


之前分析了Memcached的get操作,下面分析set操作的流程。

[cpp]  view plain  copy
 
  1. //存储item  
  2. enum store_item_type store_item(item *item, int comm, conn* c) {  
  3.     enum store_item_type ret;  
  4.     uint32_t hv;  
  5.   
  6.     hv = hash(ITEM_key(item), item->nkey, 0);//获取Hash表的分段锁  
  7.     item_lock(hv);//执行数据同步  
  8.     ret = do_store_item(item, comm, c, hv);//存储item  
  9.     item_unlock(hv);  
  10.     return ret;  
  11. }  
  12. //存储item  
  13. enum store_item_type do_store_item(item *it, int comm, conn *c,const uint32_t hv)  
  14. {  
  15.     char *key = ITEM_key(it);//读取item对应的key  
  16.     item *old_it = do_item_get(key, it->nkey, hv);//读取相应的item,如果没有相关的数据,old_it为NULL  
  17.     enum store_item_type stored = NOT_STORED;//item状态标记  
  18.   
  19.     item *new_it = NULL;  
  20.     int flags;  
  21.   
  22.     if (old_it != NULL && comm == NREAD_ADD)//如果old_it不为NULL,且操作为add操作  
  23.     {  
  24.         do_item_update(old_it);//更新数据  
  25.     }  
  26.     else if (!old_it  
  27.             && (comm == NREAD_REPLACE || comm == NREAD_APPEND  
  28.                     || comm == NREAD_PREPEND)) //old_it为空,且操作为REPLACE,则什么都不做  
  29.     {  
  30.           //memcached的Replace操作是替换已有的数据,如果没有相关数据,则不做任何操作  
  31.     }  
  32.     else if (comm == NREAD_CAS)//以cas方式读取  
  33.     {  
  34.         if (old_it == NULL) //为空  
  35.         {  
  36.             // LRU expired  
  37.             stored = NOT_FOUND;//修改状态  
  38.             pthread_mutex_lock(&c->thread->stats.mutex);//更新Worker线程统计数据  
  39.             c->thread->stats.cas_misses++;  
  40.             pthread_mutex_unlock(&c->thread->stats.mutex);  
  41.         }  
  42.         else if (ITEM_get_cas(it) == ITEM_get_cas(old_it))//old_it不为NULL,且cas属性一致  
  43.         {  
  44.             pthread_mutex_lock(&c->thread->stats.mutex);  
  45.             c->thread->stats.slab_stats[old_it->slabs_clsid].cas_hits++;//更新Worker线程统计信息  
  46.             pthread_mutex_unlock(&c->thread->stats.mutex);  
  47.   
  48.             item_replace(old_it, it, hv);//执行item的替换操作,用新的item替换老的item  
  49.             stored = STORED;//修改状态值  
  50.         }  
  51.         else//old_it不为NULL,且cas属性不一致  
  52.         {  
  53.             pthread_mutex_lock(&c->thread->stats.mutex);  
  54.             c->thread->stats.slab_stats[old_it->slabs_clsid].cas_badval++;//更新Worker线程统计信息  
  55.             pthread_mutex_unlock(&c->thread->stats.mutex);  
  56.   
  57.             if (settings.verbose > 1)  
  58.             {  
  59.                 fprintf(stderr, "CAS:  failure: expected %llu, got %llu\n",  
  60.                         (unsigned long long) ITEM_get_cas(old_it),  
  61.                         (unsigned long long) ITEM_get_cas(it));  
  62.             }  
  63.             stored = EXISTS;//修改状态值,修改状态值为已经存在,且不存储最新的数据  
  64.         }  
  65.     }  
  66.     else //执行其他操作的写  
  67.     {  
  68.         if (comm == NREAD_APPEND || comm == NREAD_PREPEND)//以追加的方式执行写  
  69.         {  
  70.               
  71.             if (ITEM_get_cas(it) != 0)//验证cas有效性  
  72.             {  
  73.                 if (ITEM_get_cas(it) != ITEM_get_cas(old_it))//cas验证不通过  
  74.                 {  
  75.                     stored = EXISTS;//修改状态值为已存在  
  76.                 }  
  77.             }  
  78.               
  79.             if (stored == NOT_STORED)//状态值为没有存储,也就是cas验证通过,则执行写操作  
  80.             {  
  81.                 flags = (int) strtol(ITEM_suffix(old_it), (char **) NULL, 10);  
  82.                 //申请新的空间  
  83.                 new_it = do_item_alloc(key, it->nkey, flags, old_it->exptime,it->nbytes + old_it->nbytes - 2 , hv);  
  84.   
  85.                 if (new_it == NULL)  
  86.                 {  
  87.                     //空间不足  
  88.                     if (old_it != NULL)  
  89.                         do_item_remove(old_it);//删除老的item  
  90.   
  91.                     return NOT_STORED;  
  92.                 }  
  93.                   
  94.                 if (comm == NREAD_APPEND)//追加方式  
  95.                 {     
  96.                     memcpy(ITEM_data(new_it), ITEM_data(old_it),old_it->nbytes);//老数据拷贝到新数据中  
  97.                     memcpy(ITEM_data(new_it) + old_it->nbytes - 2,ITEM_data(it), it->nbytes);//同时拷贝最近缓冲区已有的数据  
  98.                 }  
  99.                 else  
  100.                 {  
  101.                     //这里和具体协议相关  
  102.                     memcpy(ITEM_data(new_it), ITEM_data(it), it->nbytes);//拷贝it的数据到new_it中  
  103.                     memcpy(ITEM_data(new_it) + it->nbytes - 2 ,ITEM_data(old_it), old_it->nbytes);//同时拷贝最近缓冲区已有的数据  
  104.                 }  
  105.   
  106.                 it = new_it;  
  107.             }  
  108.         }  
  109.   
  110.         if (stored == NOT_STORED)  
  111.         {  
  112.             if (old_it != NULL)//如果old_it不为空  
  113.                 item_replace(old_it, it, hv);//替换老的值  
  114.             else  
  115.                 do_item_link(it, hv);//重新存储数据  
  116.   
  117.             c->cas = ITEM_get_cas(it);//获取cas值  
  118.   
  119.             stored = STORED;  
  120.         }  
  121.     }  
  122.   
  123.     if (old_it != NULL)  
  124.         do_item_remove(old_it);//释放空间  
  125.     if (new_it != NULL)  
  126.         do_item_remove(new_it);//释放空间  
  127.   
  128.     if (stored == STORED)//如果已经存储了  
  129.     {  
  130.         c->cas = ITEM_get_cas(it);//获取cas属性  
  131.     }  
  132.   
  133.     return stored;  
  134. }  
  135. //更新item,这个只更新时间  
  136. void do_item_update(item *it) {  
  137.     MEMCACHED_ITEM_UPDATE(ITEM_key(it), it->nkey, it->nbytes);  
  138.     if (it->time < current_time - ITEM_UPDATE_INTERVAL) {//更新有时间限制  
  139.         assert((it->it_flags & ITEM_SLABBED) == 0);  
  140.   
  141.         mutex_lock(&cache_lock);//保持同步  
  142.         if ((it->it_flags & ITEM_LINKED) != 0) {//更新LRU队列的Item  
  143.             item_unlink_q(it);//断开连接  
  144.             it->time = current_time;//更新item的时间  
  145.             item_link_q(it);//重新添加  
  146.         }  
  147.         mutex_unlock(&cache_lock);  
  148.     }  
  149. }   
  150. //用新的item替换老的item  
  151. int do_item_replace(item *it, item *new_it, const uint32_t hv) {  
  152.     MEMCACHED_ITEM_REPLACE(ITEM_key(it), it->nkey, it->nbytes,  
  153.                            ITEM_key(new_it), new_it->nkey, new_it->nbytes);  
  154.     assert((it->it_flags & ITEM_SLABBED) == 0);//判断it是已经分配过的,如果未分配,则断言失败  
  155.   
  156.     do_item_unlink(it, hv);//断开连接  
  157.     return do_item_link(new_it, hv);//重新添加  
  158. }  
有些item的操作已经在get操作中有分析,我们此处不做分析,我们下一篇分析下Memcached内部如何选择合适的空间来存放item.


上文来自:http://blog.csdn.net/lcli2009?viewmode=contents

你可能感兴趣的:(C++,源码,memcached,C++11)