写在前面:因为目前的工作需要长期跟redis打交道,然后自己也想把这块吃透,方便技术提升,所以写博客。
文章目录
Redis源码阅读(一)
第一阶段阅读Redis的数据结构部分,基本位于如下文件中:
内存分配 zmalloc.c和zmalloc.h
动态字符串 sds.h和sds.c
双端链表 adlist.c和adlist.h
字典 dict.h和dict.c
跳跃表 server.h文件
里面关于zskiplist结构和zskiplistNode结构,以及t_zset.c中所有zsl开头的函数,比如 zslCreate、zslInsert、zslDeleteNode等等
基数统计 hyperloglog.c
其中的 hllhdr 结构,以及所有以 hll 开头的函数
zmalloc.h
Redis在这个版本使用三种选择作为allocator,所以先介绍部分宏定义
allocator
a) tcmalloc:由google用于优化C++多线程应用而开发。Redis 需要1.6以上的版本。
b) jemalloc:第一次用在FreeBSD 的allocator,于2005年释出的版本。强调降低碎片化,可扩展的并行支持。Redis需要2.1以上版本。
c) libc:最常使用的libc库。GNU libc,默认使用此allocator。
#if defined(USE_TCMALLOC)
#define ZMALLOC_LIB ("tcmalloc-" __xstr(TC_VERSION_MAJOR) "." __xstr(TC_VERSION_MINOR))
#include <google/tcmalloc.h>
#if (TC_VERSION_MAJOR == 1 && TC_VERSION_MINOR >= 6) || (TC_VERSION_MAJOR > 1)
#define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) tc_malloc_size(p)
#else
#error "Newer version of tcmalloc required"
#endif
#elif defined(USE_JEMALLOC)
#define ZMALLOC_LIB ("jemalloc-" __xstr(JEMALLOC_VERSION_MAJOR) "." __xstr(JEMALLOC_VERSION_MINOR) "." __xstr(JEMALLOC_VERSION_BUGFIX))
#include <jemalloc/jemalloc.h>
#if (JEMALLOC_VERSION_MAJOR == 2 && JEMALLOC_VERSION_MINOR >= 1) || (JEMALLOC_VERSION_MAJOR > 2)
#define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) je_malloc_usable_size(p)
#else
#error "Newer version of jemalloc required"
#endif
#elif defined(__APPLE__)
#include <malloc/malloc.h>
#define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) malloc_size(p)
#endif
/* On native libc implementations, we should still do our best to provide a
* HAVE_MALLOC_SIZE capability. This can be set explicitly as well:
*
* NO_MALLOC_USABLE_SIZE disables it on all platforms, even if they are
* known to support it.
* USE_MALLOC_USABLE_SIZE forces use of malloc_usable_size() regardless
* of platform.
*/
#ifndef ZMALLOC_LIB
#define ZMALLOC_LIB "libc"
#if !defined(NO_MALLOC_USABLE_SIZE) && \
(defined(__GLIBC__) || defined(__FreeBSD__) || \
defined(USE_MALLOC_USABLE_SIZE))
/* Includes for malloc_usable_size() */
#ifdef __FreeBSD__
#include <malloc_np.h>
#else
#include <malloc.h>
#endif
#define HAVE_MALLOC_SIZE 1
#define zmalloc_size(p) malloc_usable_size(p)
#endif
#endif
由宏USE_TCMALLOC,USE_JEMALLOC和__APPLE__控制要编译进Redis的allocator。 前两个宏从make 传入,后面一个是操作系统宏,若是Apple,则可以提供一个 malloc_size (),用于查看指针指向内存的大小。此函数在jemalloc和tcmalloc中都有提供,但glibc中不提供此函数,宏HAVE_MALLOC_SIZE即是用于控制此函数。
src/zmalloc.c
zmalloc.c 读完后感觉是解决字长和字节对齐的问题,方便跨平台
void *zmalloc(size_t size);
void *zcalloc(size_t size);
void *zrealloc(void *ptr, size_t size);
void zfree(void *ptr);
size_t zmalloc_used_memory(void);
void zmalloc_enable_thread_safeness(void);
float zmalloc_get_fragmentation_ratio(size_t rss);
size_t zmalloc_get_rss(void);
PREFIX_SIZE
根据机器的不同,定义为一个字长大小
#ifdef HAVE_MALLOC_SIZE
#define PREFIX_SIZE (0)
#else
#if defined(__sun) || defined(__sparc) || defined(__sparc__)
#define PREFIX_SIZE (sizeof(long long))
#else
#define PREFIX_SIZE (sizeof(size_t))
#endif
#endif
update_zmalloc_stat_alloc和atomicIncr
update_zmalloc_stat_alloc,因为sizeof(long) == 8 [64位系统中],所以其实第一个if的代码等价于if(_n&7) _n += 8 - (_n&7); 这段代码就是判断分配的内存空间的大小是不是8的倍数。如果内存大小不是8的倍数,就加上相应的偏移量使之变成8的倍数。
这是为了精确计算系统分配的空间,因为malloc会自动做内存对齐,分配空间可能会比实际需要数值略多一点。
#define update_zmalloc_stat_alloc(__n) do { \
size_t _n = (__n); \
if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \
atomicIncr(used_memory,__n); \ //注4:atomicIncr
} while(0)
atomicIncr是Increment the atomic counter 增加原子计数器
#define atomicIncr(var,count) do { \
pthread_mutex_lock(&var ## _mutex); \ //线程安全
var += (count); \
pthread_mutex_unlock(&var ## _mutex); \
} while(0)
ZMALLOC
/* Try allocating memory, and return NULL if failed.
* '*usable' is set to the usable size if non NULL. */
void *ztrymalloc_usable(size_t size, size_t *usable) {
/* Possible overflow, return NULL, so that the caller can panic or handle a failed allocation. */
if (size >= SIZE_MAX/2) return NULL;//大于可用空间的一半直接返回空
void *ptr = malloc(MALLOC_MIN_SIZE(size)+PREFIX_SIZE);
if (!ptr) return NULL;
#ifdef HAVE_MALLOC_SIZE
size = zmalloc_size(ptr); //zmalloc_size()是为了给系统本身没有malloc函数的分配空间,第一个字节存储信息,信息存储在prefix_size里面
update_zmalloc_stat_alloc(size);//对齐8字节
if (usable) *usable = size;
return ptr;
#else
*((size_t*)ptr) = size;
update_zmalloc_stat_alloc(size+PREFIX_SIZE);
if (usable) *usable = size;
return (char*)ptr+PREFIX_SIZE;
#endif
}
/* Allocate memory or panic */
void *zmalloc(size_t size) {
void *ptr = ztrymalloc_usable(size, NULL); //可用空间的申请
if (!ptr) zmalloc_oom_handler(size); /*out of memory的处理*/
return ptr;
}
zcalloc
同理malloc,为没有自带calloc的平台提供分配空间并赋值为0的操作
/* Allocate memory and zero it or panic */
void *zcalloc(size_t size) {
void *ptr = ztrycalloc_usable(size, NULL);
if (!ptr) zmalloc_oom_handler(size);
return ptr;
}
/* Try allocating memory and zero it, and return NULL if failed.
* '*usable' is set to the usable size if non NULL. */
void *ztrycalloc_usable(size_t size, size_t *usable) {
/* Possible overflow, return NULL, so that the caller can panic or handle a failed allocation. */
if (size >= SIZE_MAX/2) return NULL;
void *ptr = calloc(1, MALLOC_MIN_SIZE(size)+PREFIX_SIZE);
if (ptr == NULL) return NULL;
#ifdef HAVE_MALLOC_SIZE
size = zmalloc_size(ptr);
update_zmalloc_stat_alloc(size);
if (usable) *usable = size;
return ptr;
#else
*((size_t*)ptr) = size;
update_zmalloc_stat_alloc(size+PREFIX_SIZE);
if (usable) *usable = size;
return (char*)ptr+PREFIX_SIZE;
#endif
}
ZREALLOC
zrealloc函数是修改所指向空间的大小。这里首先获取原空间的大小oldsize,再分配尺寸为size的新空间。如果成功,则老空间所指内容会被复制进新空间,且老空间指针失效,新空间指针为newptr。函数会根据HAVE_MALLOC_SIZE宏的定义确定是否需要额外分配PREFIX_SIZE大小设置空间的长度。
首先了解下
update_zmalloc_stat_free
update_zmalloc_stat_free会首先根据long型对齐占用的空间大小,然后对used_memery减去计算好的尺寸
#define update_zmalloc_stat_free(__n) do { \
size_t _n = (__n); \
if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \
atomicDecr(used_memory,__n); \
} while(0)
/*原子操作 减*/
#define atomicDecr(var,count) do { \
pthread_mutex_lock(&var ## _mutex); \
var -= (count); \
pthread_mutex_unlock(&var ## _mutex); \
} while(0)
然后再看ZREALLOC
/* Try allocating memory, and return NULL if failed. */
void *ztrycalloc(size_t size) {
void *ptr = ztrycalloc_usable(size, NULL);
return ptr;
}
/* Try reallocating memory, and return NULL if failed.
* '*usable' is set to the usable size if non NULL. */
void *ztryrealloc_usable(void *ptr, size_t size, size_t *usable) {
#ifndef HAVE_MALLOC_SIZE
void *realptr;
#endif
size_t oldsize;
void *newptr;
//size为0 直接释放后返回
/* not allocating anything, just redirect to free. */
if (size == 0 && ptr != NULL) {
zfree(ptr);
if (usable) *usable = 0;
return NULL;
}
//原空间为0 直接分配
/* Not freeing anything, just redirect to malloc. */
if (ptr == NULL)
return ztrymalloc_usable(size, usable);
/* Possible overflow, return NULL, so that the caller can panic or handle a failed allocation. */
//可能溢出,所以返回为空
if (size >= SIZE_MAX/2) {
zfree(ptr);
if (usable) *usable = 0;
return NULL;
}
#ifdef HAVE_MALLOC_SIZE
//本身有realloc函数,update_zmalloc_stat_free减去旧的空间,再加上申请的新空间
oldsize = zmalloc_size(ptr);
newptr = realloc(ptr,size);
if (newptr == NULL) {
if (usable) *usable = 0;
return NULL;
}
//先释放旧的空间
update_zmalloc_stat_free(oldsize);
size = zmalloc_size(newptr);
//再申请新空间
update_zmalloc_stat_alloc(size);
if (usable) *usable = size;
return newptr;
//本身没有realloc函数,update_zmalloc_stat_free减去旧的空间
#else
//减去prefix
realptr = (char*)ptr-PREFIX_SIZE;
oldsize = *((size_t*)realptr);
//重新申请空间
newptr = realloc(realptr,size+PREFIX_SIZE);
if (newptr == NULL) {
if (usable) *usable = 0;
return NULL;
}
*((size_t*)newptr) = size;
//释放
update_zmalloc_stat_free(oldsize);
//申请
update_zmalloc_stat_alloc(size);
if (usable) *usable = size;
return (char*)newptr+PREFIX_SIZE;
#endif
}
}
zstrdup
zstrdup函数是把一份空间的内容,分配并拷贝内容至新空间,并返回新空间的指针。
char *zstrdup(const char *s) {
size_t l = strlen(s)+1;
char *p = zmalloc(l);
memcpy(p,s,l);
return p;
}
zmalloc_used_memory
zmalloc_used_memory 函数用来获取当前使用的内存总量,其中__sync_add_and_fetch就是宏update_zmalloc_stat_add。
size_t zmalloc_used_memory(void) {
size_t um;
atomicGet(used_memory,um);
return um;
}
#define atomicGet(var,dstvar) do { \
pthread_mutex_lock(&var ## _mutex); \
dstvar = var; \
pthread_mutex_unlock(&var ## _mutex); \
} while(0)
zmalloc_get_rss
这个函数可以获取当前进程实际所驻留在内存中的空间大小,即不包括被交换(swap)出去的空间。该函数大致的操作就是在当前进程的 /proc//stat 【表示当前进程id】文件中进行检索。该文件的第24个字段是RSS的信息,它的单位是pages(内存页的数目)。如果没从操作系统的层面获取驻留内存大小,那就只能绌劣的返回已经分配出去的内存大小。
根据不同的宏定义,win10下面高亮的源码已展示,就是
#if defined(HAVE_PROC_STAT)
#elif defined(HAVE_TASKINFO)
#elif defined(__FreeBSD__) || defined(__DragonFly__)
#elif defined(__NetBSD__) || defined(__OpenBSD__)
#elif defined(__HAIKU__)
#elif defined(HAVE_PSINFO)
#else
size_t zmalloc_get_rss(void) {
/* If we can't get the RSS in an OS-specific way for this system just
* return the memory usage we estimated in zmalloc()..
*
* Fragmentation will appear to be always 1 (no fragmentation)
* of course... */
//如果我们不能以特定于操作系统的方式为这个系统获取RSS,那么就返回我们在zmalloc()中估计的内存使用量。
//碎片总是1(没有碎片)
return zmalloc_used_memory();
}