Redis源码解析——内存管理
来源:互联网 发布:淘宝贷款网站 编辑:程序博客网 时间:2024/05/21 14:00
在《Redis源码解析——源码工程结构》一文中,我们介绍了Redis可能会根据环境或用户指定选择不同的内存管理库。在linux系统中,Redis默认使用jemalloc库。当然用户可以指定使用tcmalloc或者libc的原生内存管理库。本文介绍的内容是在这些库的基础上,Redis封装的功能。(转载请指明出于breaksoftware的csdn博客)
统一函数名
首先Redis需要判断最终选择的内存管理库是否可以满足它的基础需求。比如Redis需要能够通过一个堆上分配的指针知晓其空间大小。但是并不是所有内存管理库的每个版本都有这个方法。于是对于不满足的就报错
#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#ifndef HAVE_MALLOC_SIZEsize_t zmalloc_size(void *ptr) { void *realptr = (char*)ptr-PREFIX_SIZE; size_t size = *((size_t*)realptr); /* Assume at least that all the allocations are padded at sizeof(long) by * the underlying allocator. */ if (size&(sizeof(long)-1)) size += sizeof(long)-(size&(sizeof(long)-1)); return size+PREFIX_SIZE;}#endif
上面这段代码除了判断内存库的支持能力,还顺带统一zmalloc_size方法的实现。其实需要统一的方法不止这一个。比如libc的malloc方法在jemalloc中叫做je_malloc,而在tcmalloc中叫tc_malloc。这些基础方法并不多,它们分别是单片内存分配的malloc方法、多片内存分配calloc方法、内存重分配的realloc方法和内存释放函数free。经过统一命令后,之后使用这些方法的地方就不用考虑基础库不同的问题了。
#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)#endif
记录堆空间申请大小
Redis内存管理模块需要实时知道已经申请了多少空间,它通过一个全局变量保存:
static size_t used_memory = 0;由于内存分配可能发生在各个线程中,所以对这个数据的管理要做到原子性。但是不同平台原子性操作的方法不同,有的甚至不支持原子操作,这个时候Redis就要统一它们的行为
pthread_mutex_t used_memory_mutex = PTHREAD_MUTEX_INITIALIZER;……#if defined(__ATOMIC_RELAXED)#define update_zmalloc_stat_add(__n) __atomic_add_fetch(&used_memory, (__n), __ATOMIC_RELAXED)#define update_zmalloc_stat_sub(__n) __atomic_sub_fetch(&used_memory, (__n), __ATOMIC_RELAXED)#elif defined(HAVE_ATOMIC)#define update_zmalloc_stat_add(__n) __sync_add_and_fetch(&used_memory, (__n))#define update_zmalloc_stat_sub(__n) __sync_sub_and_fetch(&used_memory, (__n))#else#define update_zmalloc_stat_add(__n) do { \ pthread_mutex_lock(&used_memory_mutex); \ used_memory += (__n); \ pthread_mutex_unlock(&used_memory_mutex); \} while(0)#define update_zmalloc_stat_sub(__n) do { \ pthread_mutex_lock(&used_memory_mutex); \ used_memory -= (__n); \ pthread_mutex_unlock(&used_memory_mutex); \} while(0)#endif一般来说,锁操作比原子操作慢。但是在不支持原子操作的系统上只能使用锁机制了。
但是作为一个基础库,它不能仅仅考虑到多线程的问题。比如用户系统上不支持原子操作,而用户也不希望拥有多线程安全特性(可能它只有一个线程在运行),那么上述接口在计算时就必须使用锁机制,这样对于性能有苛刻要求的场景是不能接受的。于是Redis暴露了一个方法用于让用户指定是否需要启用线程安全特性
static int zmalloc_thread_safe = 0;void zmalloc_enable_thread_safeness(void) { zmalloc_thread_safe = 1;}相应的,线程安全的方法update_zmalloc_stat_add和update_zmalloc_stat_free需要被封装,以满足不同模式:
#define update_zmalloc_stat_alloc(__n) do { \ size_t _n = (__n); \ if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \ if (zmalloc_thread_safe) { \ update_zmalloc_stat_add(_n); \ } else { \ used_memory += _n; \ } \} while(0)#define update_zmalloc_stat_free(__n) do { \ size_t _n = (__n); \ if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1)); \ if (zmalloc_thread_safe) { \ update_zmalloc_stat_sub(_n); \ } else { \ used_memory -= _n; \ } \} while(0)
之后我们在堆上分配释放空间时,就需要使用update_zmalloc_stat_alloc和update_zmalloc_stat_free方法实时更新堆空间申请的情况。而获取其值则需要下面的方法:
size_t zmalloc_used_memory(void) { size_t um; if (zmalloc_thread_safe) {#if defined(__ATOMIC_RELAXED) || defined(HAVE_ATOMIC) um = update_zmalloc_stat_add(0);#else pthread_mutex_lock(&used_memory_mutex); um = used_memory; pthread_mutex_unlock(&used_memory_mutex);#endif } else { um = used_memory; } return um;}
内存分配和释放
之前我们讲过,Redis的内存分配库需要底层库支持通过堆上指针获取该空间大小的功能,但是一些低版本的内存管理库并不支持。针对这种场景Redis还是做了兼容,它设计的内存结构是Header+Body。在Header中保存了该堆空间Body的大小信息,而Body则用于返回给内存申请者。我们看下malloc的例子:
void *zmalloc(size_t size) { void *ptr = malloc(size+PREFIX_SIZE); if (!ptr) zmalloc_oom_handler(size);#ifdef HAVE_MALLOC_SIZE update_zmalloc_stat_alloc(zmalloc_size(ptr)); return ptr;#else *((size_t*)ptr) = size; update_zmalloc_stat_alloc(size+PREFIX_SIZE); return (char*)ptr+PREFIX_SIZE;#endif}一开始时,zmalloc直接分配了一个比申请空间大的空间,这就意味着无论是否支持获取申请空间大小的内存库,它都一视同仁了——实际申请比用户要求大一点。
如果内存库支持,则通过zmalloc_size获取刚分配的空间大小,并累计到记录整个程序申请的堆空间大小上,然后返回申请了的地址。此时虽然用户申请的只是size的大小,但是实际给了size+PREFIX_SIZE的大小。
如果内存库不支持,则在申请的内存前sizeof(size_t)大小的空间里保存用户需要申请的空间大小size。累计到记录整个程序申请堆空间大小上的也是实际申请的大小。最后返回的是偏移了头大小的内存地址。此时用户拿到的空间就是自己要求申请的空间大小。
多片分配空间的zcalloc函数实现也是类似的,稍微有点区别的是重新分配空间的zrealloc方法,它需要在统计程序以申请堆空间大小的数据上减去以前该块的大小,再加上新申请的空间大小
void *zrealloc(void *ptr, size_t size) {#ifndef HAVE_MALLOC_SIZE void *realptr;#endif size_t oldsize; void *newptr; if (ptr == NULL) return zmalloc(size);#ifdef HAVE_MALLOC_SIZE oldsize = zmalloc_size(ptr); newptr = realloc(ptr,size); if (!newptr) zmalloc_oom_handler(size); update_zmalloc_stat_free(oldsize); update_zmalloc_stat_alloc(zmalloc_size(newptr)); return newptr;#else realptr = (char*)ptr-PREFIX_SIZE; oldsize = *((size_t*)realptr); newptr = realloc(realptr,size+PREFIX_SIZE); if (!newptr) zmalloc_oom_handler(size); *((size_t*)newptr) = size; update_zmalloc_stat_free(oldsize); update_zmalloc_stat_alloc(size); return (char*)newptr+PREFIX_SIZE;#endif}还有就是zfree函数的实现,它需要释放的空间起始地址要视库的支持能力决定。如果库不支持获取区块大小,则需要将传入的指针前移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);#else realptr = (char*)ptr-PREFIX_SIZE; oldsize = *((size_t*)realptr); update_zmalloc_stat_free(oldsize+PREFIX_SIZE); free(realptr);#endif}最后我们看下Redis在内存分配时处理内存溢出的处理。它提供了一个接口,让用户处理内存溢出问题。当然它也有自己默认的处理逻辑:
static void (*zmalloc_oom_handler)(size_t) = zmalloc_default_oom;static void zmalloc_default_oom(size_t size) { fprintf(stderr, "zmalloc: Out of memory trying to allocate %zu bytes\n", size); fflush(stderr); abort();}void zmalloc_set_oom_handler(void (*oom_handler)(size_t)) { zmalloc_oom_handler = oom_handler;}
获取进程内存信息
Redis不仅在代码层面要统计已申请的堆空间,还要通过其他方法获取本进程中一些内存信息。比如它要通过zmalloc_get_rss方法获取当前进程的实际使用物理内存。这个也要按系统支持来区分实现,比如支持/proc/%pid%/stat的使用:
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;}如果支持使用task_for_pid方法的则使用:
size_t zmalloc_get_rss(void) { task_t task = MACH_PORT_NULL; struct task_basic_info t_info; mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT; if (task_for_pid(current_task(), getpid(), &task) != KERN_SUCCESS) return 0; task_info(task, TASK_BASIC_INFO, (task_info_t)&t_info, &t_info_count); return t_info.resident_size;}
获取完物理内存数据后,可以通过和累计的分配内存大小相除,算出内存使用效率:
float zmalloc_get_fragmentation_ratio(size_t rss) { return (float)rss/zmalloc_used_memory();}Redis源码说明上指出上述获取RSS信息的方法是不高效的。可以通过RedisEstimateRSS()方法高效获取。
除了上面这些方法,Redis还有获取已被修改的私有页面大小函数zmalloc_get_private_dirty以及获取物理内存((RAM))大小的zmalloc_get_memory_size方法。这些方法都是些系统性方法,我就不在这儿做说明了。
- Redis源码解析——内存管理
- Redis源码分析:内存管理
- Redis源码分析:内存管理
- Redis源码分析:内存管理
- redis源码分析 -- 内存管理
- leveldb源码解析1——内存管理类Arena
- 内存管理pbuf.c源码解析——LwIP学习
- redis源码分析之redis内存管理
- Redis源码解析—源码目录介绍
- mongodb 源码解析内存管理
- Redis源码学习2-内存管理
- redis 源码分析(一) 内存管理
- Redis源码学习2-内存管理
- redis源码分析(1)内存管理
- Redis源码学习2-内存管理
- redis 源码分析(一) 内存管理
- Redis源码学习(一)内存管理
- Redis源码解析——前言
- Leetcode1.Two Sum+LeetCode15.3Sum+LeetCode18. 4Sum【K-Sum问题】
- 剑指offer---对称的二叉树
- react native (二)‘电影列表demo’
- JavaScript放大镜效果
- struts配置文件
- Redis源码解析——内存管理
- DataTime.Now.Ticks
- 理解MVC,MVP和MVVM设计模式
- 自我反省
- BeagleBone Black Eclipse and GDB
- andriod客户端
- Cassandra学习笔记 --- cassandra-driver使用
- 应用Bootstrap必备的参考资源
- echart常用记录