Redis默认的内存分配器采用jemalloc,可选的分配器还有:glibc、tcmalloc。内存分配器为了更好地管理和重复利用内存,分配内存策略一般采用固定范围的内存块进行分配。例如jemalloc在64位系统中将内存空间划分为:小、大、巨大三个范围。每个范围内又划分为多个小的内存单元,如下所示:

  • 小:[8 byte],[16 byte, 32 byte, 48 byte, …, 128 byte], [192 byte, 256 byte, …, 512 btye], [768 byte, 1024 byte, …, 3849 byte]
  • 大:[4KB, 8KB, 12KB, …, 4072KB]
  • 巨大:[4MB, 8MB, 12MB, …]

zmalloc.h分析:

源文件zmalloc.h的宏定义显示了对内存分配器的选择:

Redis 3.0源码分析-内存分配zmalloc_sed


我们可以看到对USE_TCMALLOC的宏定义,其中__xstr是宏值字符串化。如果定义了USE_TCMALLOC,也就是使用TCMALLOC,则定义ZMALLOC_LIB为tcmalloc-主版本号.次版本号,并且引入google/tcmalloc.h文件。如果版本是在次版本大于1.6或主版本号大于1的情况下,定义HAVE_MALLOC_SIZE为1,并定义zmalloc_size§为tc_malloc_size§。否则会输出需要更新的版本。

HAVE_MALLOC_SIZE宏定义是对zmalloc.c提供函数是否有zmalloc_size函数的控制。

Redis 3.0源码分析-内存分配zmalloc_宏定义_02


可以看到对USE_JEMALLOC的宏定义。如果定义了USE_JEMALLOC,也就是使用JEMALLOC,则定义JEMALLOC_LIB为jemalloc-主版本号.次版本号,并且引入jemalloc/jemalloc.h文件。如果版本是在次版本大于2.1或主版本号大于2的情况下,定义HAVE_MALLOC_SIZE为1,并定义zmalloc_size§为je_malloc_usable_size§。否则会输出需要更新的版本。

Redis 3.0源码分析-内存分配zmalloc_版本号_03


如果jemalloc和tcmalloc都没有使用就用glibc的库函数

Redis 3.0源码分析-内存分配zmalloc_Redis_04

zmalloc.h包含的函数列表:

void *zmalloc(size_t size);
void *zcalloc(size_t size);
void *zrealloc(void *ptr, size_t size);
void zfree(void *ptr);
char *zstrdup(const char *s);
size_t zmalloc_used_memory(void);
void zmalloc_enable_thread_safeness(void);
void zmalloc_set_oom_handler(void (*oom_handler)(size_t));
#ifndef HAVE_MALLOC_SIZE
size_t zmalloc_size(void *ptr);
#endif


float zmalloc_get_fragmentation_ratio(size_t rss);
size_t zmalloc_get_rss(void);
size_t zmalloc_get_private_dirty(void);

void zlibc_free(void *ptr);

zmalloc.c分析:

这是在源文件中对函数调用进行替换,以保证zmalloc是对内存函数的包装。

Redis 3.0源码分析-内存分配zmalloc_宏定义_05

zmalloc针对used_memory的多线程保护的处理:used_memory是使用的内存量,而used_memory_mutex是针对used_memory变量的保护锁。zmalloc_thread_safe是对zmalloc线程安全性的开关。

Redis 3.0源码分析-内存分配zmalloc_sed_06


当使用原子操作时,就使用__sync_add_and_fetch和__sync_sub_and_fetch。否则就使用线程锁,update_zmalloc_stat_add和update_zmalloc_stat_sub就是提供的对加锁解锁操作的封装。

Redis 3.0源码分析-内存分配zmalloc_宏定义_07


如果zmalloc_thread_safe开启就使用线程安全的操作,如果不开启就是一般操作。而90行的代码是确保内存对齐,就是保证对齐边缘是long的大小。

Redis 3.0源码分析-内存分配zmalloc_sed_08

对函数的简要分析:如果无法得到需要的内存,将会调用zmalloc_oom_handler函数,该函数将打印出错信息。

Redis 3.0源码分析-内存分配zmalloc_宏定义_09


通过HAVE_MALLOC_SIZE来决定是否提供zmalloc_size函数,如果没有提供,那么在其他函数实现时,需要写提取内存大小的代码。内存的大小是存放在分配的内存的首地址中。

Redis 3.0源码分析-内存分配zmalloc_Redis_10


在源文件中对HAVE_MALLOC_SIZE的处理:如果定义了HAVE_MALLOC_SIZE宏定义,则PREFIX_SIZE定义为0。否则根据处理器架构来定义PREFIX_SIZE。

Redis 3.0源码分析-内存分配zmalloc_宏定义_11

源文件中还有一部分比较重要的函数是获取RSS信息
这部分函数有多种实现方式:提取/proc中的信息、taskinfo函数和zmalloc实现的统计方法zmalloc_used_memory()

size_t zmalloc_get_rss(void) {
int page = sysconf(_SC_PAGESIZE);
size_t rss;
char buf[4096];
char filename[256];
int fd, count;
char *p, *x;

snprintf(filename,256,"/proc/%d/stat",getpid());
if ((fd = open(filename,O_RDONLY)) == -1) return 0;
if (read(fd,buf,4096) <= 0) {
close(fd);
return 0;
}
close(fd);

p = buf;
count = 23; /* RSS is the 24th field in /proc/<pid>/stat */
while(p && count--) {
p = strchr(p,' ');
if (p) p++;
}
if (!p) return 0;
x = strchr(p,' ');
if (!x) return 0;
*x = '\0';

rss = strtoll(p,NULL,10);
rss *= page;
return rss;
}

这是从/proc文件系统中提取内存rss,使用页面数乘页大小。

Redis 3.0源码分析-内存分配zmalloc_sed_12


task_info函数

Redis 3.0源码分析-内存分配zmalloc_宏定义_13


zmalloc实现的统计方法

Redis 3.0源码分析-内存分配zmalloc_Redis_14

zmalloc_get_private_dirty的实现:使用/poc/self/smaps的统计信息

Redis 3.0源码分析-内存分配zmalloc_宏定义_15

关于tcmalloc的参考
​​​ https://yq.aliyun.com/articles/6045​​​​ https://zhuanlan.zhihu.com/p/29216091​