编辑
2023-11-30
Redis源码阅读
00
请注意,本文编写于 527 天前,最后修改于 527 天前,其中某些信息可能已经过时。

Redis的内存分配策略是通过封装标准的内存分配函数来实现的,具体的实现代码在zmalloc.czmalloc.h文件中。下面我将详细解释Redis的内存分配策略,并结合源码分析进行讲解。

Redis的内存分配策略主要包括以下几个方面:

  1. 内存分配库的选择:Redis支持多种内存分配库,如jemalloc、tcmalloc等。在zmalloc.h文件中,通过条件编译和宏定义来选择使用哪个内存分配库。根据不同的平台和编译选项,Redis会选择合适的内存分配库。
c
/* 当使用 libc 分配器时,确保最小分配大小与 jemalloc 的行为一致。如果传入的大小 `x` 大于 0,则返回 `x`,否则返回 `sizeof(long)`。 */ #define MALLOC_MIN_SIZE(x) ((x) > 0 ? (x) : sizeof(long)) /* 当定义了 `USE_TCMALLOC` 时,使用 tcmalloc 分配器,对应的 `malloc`、`calloc`、`realloc` 和 `free` 分别被宏定义为 `tc_malloc`、`tc_calloc`、`tc_realloc` 和 `tc_free`。 当定义了 `USE_JEMALLOC` 时,使用 jemalloc 分配器,对应的 `malloc`、`calloc`、`realloc` 和 `free` 分别被宏定义为 `je_malloc`、`je_calloc`、`je_realloc` 和 `je_free`,同时定义了 `mallocx` 和 `dallocx`。 这样的宏定义和函数重定义允许在编译时选择不同的内存分配器。 */ #if defined(USE_TCMALLOC) #define malloc(size) tc_malloc(size) #define calloc(count,size) tc_calloc(count,size) #define realloc(ptr,size) tc_realloc(ptr,size) #define free(ptr) tc_free(ptr) #elif defined(USE_JEMALLOC) #define malloc(size) je_malloc(size) #define calloc(count,size) je_calloc(count,size) #define realloc(ptr,size) je_realloc(ptr,size) #define free(ptr) je_free(ptr) #define mallocx(size,flags) je_mallocx(size,flags) #define dallocx(ptr,flags) je_dallocx(ptr,flags) #endif /* 用于更新全局变量 `used_memory`,分别用于增加和减少已使用内存统计。 */ #define update_zmalloc_stat_alloc(__n) atomicIncr(used_memory,(__n)) #define update_zmalloc_stat_free(__n) atomicDecr(used_memory,(__n)) /* `used_memory` 是一个原子类型的全局变量,用于记录 Redis 服务器当前已使用的内存大小。 */ static redisAtomic size_t used_memory = 0; /* 当内存分配失败时的默认处理函数,打印错误信息,并终止程序。该函数被设置为默认的内存不足处理函数。 */ static void zmalloc_default_oom(size_t size) { fprintf(stderr, "zmalloc: Out of memory trying to allocate %zu bytes\n", size); fflush(stderr); abort(); } /* 用于指向内存不足时的处理函数,默认为 `zmalloc_default_oom`。 */ static void (*zmalloc_oom_handler)(size_t) = zmalloc_default_oom; /* 当 `HAVE_MALLOC_SIZE` 定义时,用于处理 `malloc_size` 的情况。这个函数在当前的代码中没有被使用。 */ #ifdef HAVE_MALLOC_SIZE void *extend_to_usable(void *ptr, size_t size) { UNUSED(size); return ptr; } #endif
  1. 内存分配函数的封装:Redis通过封装标准的内存分配函数(如malloc、calloc、realloc、free)来实现自己的内存管理函数。在zmalloc.h文件中,定义了一系列的内存分配函数,如zmalloczcalloczrealloczfree等。这些函数会根据选择的内存分配库来调用相应的函数进行内存分配和释放。
c
// zmalloc *************************************************8 /* Allocate memory or panic */ void *zmalloc(size_t size) { void *ptr = ztrymalloc_usable_internal(size, NULL); if (!ptr) zmalloc_oom_handler(size); return ptr; } /* Try allocating memory, and return NULL if failed. */ void *ztrymalloc(size_t size) { void *ptr = ztrymalloc_usable_internal(size, NULL); return ptr; } // zcalloc ****************************************** /* Allocate memory and zero it or panic */ void *zcalloc(size_t size) { void *ptr = ztrycalloc_usable_internal(size, NULL); if (!ptr) zmalloc_oom_handler(size); return ptr; } /* Try allocating memory, and return NULL if failed. */ void *ztrycalloc(size_t size) { void *ptr = ztrycalloc_usable_internal(size, NULL); return ptr; } // zrealloc *************************************************** /* Reallocate memory and zero it or panic */ void *zrealloc(void *ptr, size_t size) { ptr = ztryrealloc_usable_internal(ptr, size, NULL); if (!ptr && size != 0) zmalloc_oom_handler(size); return ptr; } /* Try Reallocating memory, and return NULL if failed. */ void *ztryrealloc(void *ptr, size_t size) { ptr = ztryrealloc_usable_internal(ptr, size, NULL); return ptr; } // zfree ***************************************************************** /* 释放给定指针指向的内存。 如果未定义 HAVE_MALLOC_SIZE,则使用 PREFIX_SIZE 来获取实际分配的内存大小。 */ void zfree(void *ptr) { #ifndef HAVE_MALLOC_SIZE void *realptr; /* 实际分配的内存地址 */ size_t oldsize; /* 实际分配的内存大小 */ #endif if (ptr == NULL) return; /* 如果指针为空,直接返回,不进行释放。*/ #ifdef HAVE_MALLOC_SIZE update_zmalloc_stat_free(zmalloc_size(ptr)); /* 更新已使用内存统计,减去释放的内存大小。 */ free(ptr); /* 使用标准的 free 函数释放内存。 */ #else realptr = (char*)ptr-PREFIX_SIZE; /* 获取实际分配的内存起始地址。 */ oldsize = *((size_t*)realptr); /* 读取实际分配的内存大小。 */ update_zmalloc_stat_free(oldsize+PREFIX_SIZE); /* 更新已使用内存统计,减去释放的内存大小。 */ free(realptr); /* 使用标准的 free 函数释放实际分配的内存。 */ #endif }
  1. 获取内存块大小:Redis还提供了获取内存块大小的函数。在zmalloc.h文件中,定义了zmalloc_sizezmalloc_usable_size函数,用于获取内存块的大小。这些函数会根据选择的内存分配库来调用相应的函数来获取内存块的大小。

  2. 内存碎片整理:Redis在使用支持内存碎片整理的内存分配库时,会进行内存碎片整理操作。在zmalloc.h文件中,通过宏定义和条件编译来判断是否支持内存碎片整理,并定义了相应的函数zfree_no_tcachezmalloc_no_tcache来进行内存分配和释放。

c
/* Allocation and free functions that bypass the thread cache * and go straight to the allocator arena bins. * Currently implemented only for jemalloc. Used for online defragmentation. */ #ifdef HAVE_DEFRAG void *zmalloc_no_tcache(size_t size) { if (size >= SIZE_MAX/2) zmalloc_oom_handler(size); void *ptr = mallocx(size+PREFIX_SIZE, MALLOCX_TCACHE_NONE); if (!ptr) zmalloc_oom_handler(size); update_zmalloc_stat_alloc(zmalloc_size(ptr)); return ptr; }

Redis的内存分配策略是通过选择合适的内存分配库,并封装标准的内存分配函数来实现的。同时,Redis还提供了获取内存块大小和内存碎片整理的功能。


参考:

  1. redis源码阅读--内存分配完全解析 - 掘金
  2. Redis源码解析2 - 内存管理 :: Yeefea的记事本
  3. Redis源码解析--内存分配 - shinerio's blog

本文作者:yowayimono

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!