感谢支持
我们一直在努力

Redis源代码分析

首先Zmalloc的接口定义在头文件Zmalloc.h里:


[cpp]


  1. void *zmalloc(size_t size);  

  2. void *zcalloc(size_t size);  

  3. void *zrealloc(void *ptr, size_t size);  

  4. void zfree(void *ptr);  

  5. char *zstrdup(const char *s);  

  6. size_t zmalloc_used_memory(void);  

  7. void zmalloc_enable_thread_safeness(void);  

  8. float zmalloc_get_fragmentation_ratio(void);  

  9. size_t zmalloc_get_rss(void);  
前五个函数对应于C标准库函数。zmalloc_used_memory用来返回当前已用内存。


举例分析zmalloc函数:


[cpp]


  1. void *zmalloc(size_t size) {  

  2.     void *ptr = malloc(size+PREFIX_SIZE);  

  3.   

  4.     if (!ptr) zmalloc_oom(size);  

  5. #ifdef HAVE_MALLOC_SIZE   

  6.     update_zmalloc_stat_alloc(zmalloc_size(ptr),size);  

  7.     return ptr;  

  8. #else   

  9.     *((size_t*)ptr) = size;  

  10.     update_zmalloc_stat_alloc(size+PREFIX_SIZE,size);  

  11.     return (char*)ptr+PREFIX_SIZE;  

  12. #endif   

  13. }  
和标准库的malloc函数的区别在于,zmalloc还在内存块头部保存了内存块的大小。


PREFIX_SIZE定义为系统中一个标准的size_t的大小:


update_zmalloc_stat_alloc是一个宏,用来更新内存占用量的统计,定义为:

[cpp]


  1. #define update_zmalloc_stat_alloc(__n,__size) do { \   

  2.     size_t _n = (__n); \  

  3.     if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \  

  4.     if (zmalloc_thread_safe) { \  

  5.         pthread_mutex_lock(&used_memory_mutex);  \  

  6.         used_memory += _n; \  

  7.         pthread_mutex_unlock(&used_memory_mutex); \  

  8.     } else { \  

  9.         used_memory += _n; \  

  10.     } \  

  11. while(0)  
如果启用了线程安全选项,将在加锁之后才对临界变量used_memory_mutex进行修改。相关变量定义在:

[cpp]


  1. static size_t used_memory = 0;  

  2. static int zmalloc_thread_safe = 0;  

  3. pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER;  
在函数size_t zmalloc_used_memory(void)中,返回已占用内存量的大小,即静态变量used_memory。

函数zmalloc_get_rss()获取RSS(Resident Set Size) 的方式有三种:如果定义了PROC_FS,那么将从”/proc/[getpid()]/stat”中读取;如果定义了TASK_INFO,将从该进程id对应的task_info_t结构中读取resident_size变量;最后,如果前两者都没有定义,那么简单地返回used_memory。


zmalloc_get_fragmentation_ratio(void)计算碎片率的公式为:(float)zmalloc_get_rss()/zmalloc_used_memory()。

先介绍Redis散列表实现的几个重要数据结构:



字典项DictEntry:


[cpp]


  1. typedef struct dictEntry {  

  2.     void *key;  

  3.     void *val;  

  4.     struct dictEntry *next;  

  5. } dictEntry;  
字典类型DictType:


[cpp]


  1. typedef struct dictType {  

  2.     unsigned int (*hashFunction)(const void *key);  

  3.     void *(*keyDup)(void *privdata, const void *key);  

  4.     void *(*valDup)(void *privdata, const void *obj);  

  5.     int (*keyCompare)(void *privdata, const void *key1, const void *key2);  

  6.     void (*keyDestructor)(void *privdata, void *key);  

  7.     void (*valDestructor)(void *privdata, void *obj);  

  8. } dictType;  
哈希表结构dictht:


[cpp]


  1. /* This is our hash table structure. Every dictionary has two of this as we 

  2.  * implement incremental rehashing, for the old to the new table. */  

  3. typedef struct dictht {  

  4.     dictEntry **table;  

  5.     unsigned long size;  

  6.     unsigned long sizemask;  

  7.     unsigned long used;  

  8. } dictht;  


字典结构dict:



[cpp]


  1. typedef struct dict {  

  2.     dictType *type;  

  3.     void *privdata;  

  4.     dictht ht[2];  

  5.     int rehashidx; /* rehashing not in progress if rehashidx == -1 */  

  6.     int iterators; /* number of iterators currently running */  

  7. } dict;  
字典迭代器dictIterator:


[cpp]


  1. /* If safe is set to 1 this is a safe iteartor, that means, you can call 

  2.  * dictAdd, dictFind, and other functions against the dictionary even while 

  3.  * iterating. Otherwise it is a non safe iterator, and only dictNext() 

  4.  * should be called while iterating. */  

  5. typedef struct dictIterator {  

  6.     dict *d;  

  7.     int table, index, safe;  

  8.     dictEntry *entry, *nextEntry;  

  9. } dictIterator;  
然后介绍一下字典接口定义:


[cpp]


  1. dict *dictCreate(dictType *type, void *privDataPtr);  

  2. int dictExpand(dict *d, unsigned long size);  

  3. int dictAdd(dict *d, void *key, void *val);  

  4. int dictReplace(dict *d, void *key, void *val);  

  5. int dictDelete(dict *d, const void *key);  

  6. int dictDeleteNoFree(dict *d, const void *key);  

  7. void dictRelease(dict *d);  

  8. dictEntry * dictFind(dict *d, const void *key);  

  9. void *dictFetchValue(dict *d, const void *key);  

  10. int dictResize(dict *d);  

  11. dictIterator *dictGetIterator(dict *d);  

  12. dictIterator *dictGetSafeIterator(dict *d);  

  13. dictEntry *dictNext(dictIterator *iter);  

  14. void dictReleaseIterator(dictIterator *iter);  

  15. dictEntry *dictGetRandomKey(dict *d);  

  16. void dictPrintStats(dict *d);  

  17. unsigned int dictGenHashFunction(const unsigned char *buf, int len);  

  18. unsigned int dictGenCaseHashFunction(const unsigned char *buf, int len);  

  19. void dictEmpty(dict *d);  

  20. void dictEnableResize(void);  

  21. void dictDisableResize(void);  

  22. int dictRehash(dict *d, int n);  

  23. int dictRehashMilliseconds(dict *d, int ms);  

下面分析扩张或者创建哈希表的重要函数dictExpand:


[cpp]


  1. /* Expand or create the hashtable */  

  2. int dictExpand(dict *d, unsigned long size)  

  3. {  

  4.     dictht n; /* the new hashtable */  

  5.     unsigned long realsize = _dictNextPower(size);  

  6.   

  7.     /* the size is invalid if it is smaller than the number of 

  8.      * elements already inside the hashtable */  

  9.     if (dictIsRehashing(d) || d->ht[0].used > size)  

  10.         return DICT_ERR;  

  11.   

  12.     /* Allocate the new hashtable and initialize all pointers to NULL */  

  13.     n.size = realsize;  

  14.     n.sizemask = realsize-1;  

  15.     n.table = zcalloc(realsize*sizeof(dictEntry*));  

  16.     n.used = 0;  

  17.   

  18.     /* Is this the first initialization? If so it’s not really a rehashing 

  19.      * we just set the first hash table so that it can accept keys. */  

  20.     if (d->ht[0].table == NULL) {  

  21.         d->ht[0] = n;  

  22.         return DICT_OK;  

  23.     }  

  24.   

  25.     /* Prepare a second hash table for incremental rehashing */  

  26.     d->ht[1] = n;  

  27.     d->rehashidx = 0;  

  28.     return DICT_OK;  

  29. }  

对于传入的参数:新哈希表的大小size,首先调用内部函数_dictNextPower(size)取得大于size的最小2次幂整数,作为哈希表大小。掩码sizemask为size二进制表示长度减一的全1表示。调用内存管理函数zcalloc分配新哈希表的内存。


接下来,函数判断这是否是哈希表的首次初始化,这通过判断字典的哈希表数组ht的首个元素的dictEntry是否为空实现,如果为空,说明是首次初始化,则将该哈希表的size设为n,直接返回DICT_OK;否则,说明这是一次rehash,那么函数将准备第二个哈希表d->ht[1],并将d的rehashidx设为0,准备进行后续的增量哈希,然后返回DICT_OK。


下面分析再哈希的实现dictRehash函数:

[cpp]


  1. /* Performs N steps of incremental rehashing. Returns 1 if there are still 

  2.  * keys to move from the old to the new hash table, otherwise 0 is returned. 

  3.  * Note that a rehashing step consists in moving a bucket (that may have more 

  4.  * thank one key as we use chaining) from the old to the new hash table. */  

  5. int dictRehash(dict *d, int n) {  

  6.     if (!dictIsRehashing(d)) return 0;  

  7.   

  8.     while(n–) {  

  9.         dictEntry *de, *nextde;  

  10.   

  11.         /* Check if we already rehashed the whole table… */  

  12.         if (d->ht[0].used == 0) {  

  13.             zfree(d->ht[0].table);  

  14.             d->ht[0] = d->ht[1];  

  15.             _dictReset(&d->ht[1]);  

  16.             d->rehashidx = -1;  

  17.             return 0;  

  18.         }  

  19.   

  20.         /* Note that rehashidx can’t overflow as we are sure there are more 

  21.          * elements because ht[0].used != 0 */  

  22.         while(d->ht[0].table[d->rehashidx] == NULL) d->rehashidx++;  

  23.         de = d->ht[0].table[d->rehashidx];  

  24.         /* Move all the keys in this bucket from the old to the new hash HT */  

  25.         while(de) {  

  26.             unsigned int h;  

  27.   

  28.             nextde = de->next;  

  29.             /* Get the index in the new hash table */  

  30.             h = dictHashKey(d, de->key) & d->ht[1].sizemask;  

  31.             de->next = d->ht[1].table[h];  

  32.             d->ht[1].table[h] = de;  

  33.             d->ht[0].used–;  

  34.             d->ht[1].used++;  

  35.             de = nextde;  

  36.         }  

  37.         d->ht[0].table[d->rehashidx] = NULL;  

  38.         d->rehashidx++;  

  39.     }  

  40.     return 1;  

  41. }  
首先判断这是否是一次合法的rehash调用,通过判断(ht)->rehashidx!=-1实现。


然后,进行n步rehash。其中的每一步都重复如下步骤:


(1) 检查我们是否已经rehash了整个哈希表(此时d->ht[0].used为0),如果是,析构旧的哈希表,将d->rehashidx置为-1。


(2)  遍历哈希表d->ht[0],直到找到非空的字典项de,然后此后通过de->next继续遍历。在此之前,通过位操作dictHashKey(d, de->key) & d->ht[1].sizemask获得在新哈希表d->ht[1]中的索引h,并将该字典项复制到新哈希表d->ht[1],同时更新两个哈希表的d->ht[i].used计数。然后将最初de对应的rehashidx对应的字典项标记为NULL,将->rehashidx加1,然后重复(1)层的循环。

赞(0) 打赏
转载请注明出处:服务器评测 » Redis源代码分析
分享到: 更多 (0)

听说打赏我的人,都进福布斯排行榜啦!

支付宝扫一扫打赏

微信扫一扫打赏