Redis源码分析内存管理之道(redis 源码 内存)
Redis源码分析:内存管理之道
Redis是一款高性能、非阻塞的数据存储服务,被广泛应用于互联网中的各类应用场景。在Redis中,内存管理是至关重要的一个环节,对于Redis的性能表现也起到决定性的影响。本文将深入分析Redis源码中的内存管理实现方式,帮助我们更好地理解Redis的内存管理机制。
一、Redis的内存管理流程
Redis的内存管理主要分为三个阶段,分别是内存分配、内存释放和内存回收。其中,内存分配和释放能力在Redis运行过程中是比较稳定的,而内存回收则会随着Redis运行时间的增长而逐渐增强。
1. 内存分配
Redis在内存分配方面采用了一种称之为“对象池”的机制。对象池是指预分配一段内存空间,当需要进行内存分配时,直接从内存池中取出内存,避免了频繁的malloc/free操作,从而提高了内存分配效率。在Redis中,对象池中的内存块大小是固定的,Redis通过对象类型来判断需要预分配多大的内存空间,并将这些内存空间缓存在对象池中。同时,在Redis命令执行完成后,会把不再使用的对象放回到对象池中,以便下次使用。
2. 内存释放
Redis的内存释放机制也是在对象池中实现的,当需要释放内存时,直接将对象返回给对象池,不需要显式地调用free函数。这样可以减少对malloc/free的使用,同时避免了内存碎片问题。
3. 内存回收
Redis通过采用引用计数的方式进行内存回收。在Redis中,每个对象都有一个引用计数值,用来表示有多少个指针指向该对象。当引用计数值为0时,表示该对象已经没有任何指针指向,可以进行回收。当Redis执行delete命令时,会对该对象的引用计数减1,当引用计数为0时,会释放该对象所占用的内存空间。
二、Redis内存管理实现源码分析
1. 内存池的实现
Redis中的对象池采用了一种可扩展的方式,即初始时只分配一部分内存,当内存不足时,再根据需要自动扩展。下面是Redis中对象池的定义和相关代码实现(redisObject.h、zmalloc.c)。
/* redisObject.h */
typedef union _redisObject { struct string {
char *ptr; size_t len;
} str; /* 省略其他类型成员 */
} robj;
#define OBJ_SHARED_REFCOUNT INT_MAX /* 共享对象的引用计数值 */
/* zmalloc.c */
#define PREFIX_SIZE (sizeof(long long))
struct zmalloc_hdr { unsigned long size; /* 内存块大小 */
unsigned long used; /* 已使用空间 */ unsigned short free; /* 空间的可用状态 */
struct zmalloc_hdr *prev; /* 上一个内存块 */ struct zmalloc_hdr *next; /* 下一个内存块 */
};
typedef struct { pthread_mutex_t lock; /* 锁 */
size_t used_memory; /* 已使用内存 */ size_t max_memory; /* 最大可用内存 */
struct zmalloc_hdr *hdr; /* 对象池头节点 */} zpool;
static zpool zl = { PTHREAD_MUTEX_INITIALIZER, 0, ZMALLOC_MAX_MEMORY, NULL };
/* 分配内存 */void *zmalloc(size_t size) {
void *ptr = NULL; struct zmalloc_hdr *hdr = NULL;
pthread_mutex_lock(&zl.lock); /* 尝试在对象池中找到对应的内存链表 */
size_t avlable = zl.max_memory - zl.used_memory; if (size + PREFIX_SIZE
hdr = zl.hdr; while (hdr) {
if (hdr->free && hdr->size >= size + PREFIX_SIZE) { hdr->free = 0;
hdr->used += size + PREFIX_SIZE; zl.used_memory += size + PREFIX_SIZE;
ptr = (void *) ((char *) (hdr + 1) + PREFIX_SIZE); break;
} hdr = hdr->next;
} /* 如果没有找到对应的内存链表,则尝试扩展内存 */
if (!ptr) { size_t allocation_size = ZMALLOC_ALIGN(size + PREFIX_SIZE);
if (allocation_size + zl.used_memory hdr = (struct zmalloc_hdr *) malloc(allocation_size);
hdr->size = allocation_size; hdr->used = size + PREFIX_SIZE;
hdr->free = 0; hdr->prev = NULL;
if (zl.hdr) { hdr->next = zl.hdr;
zl.hdr->prev = hdr; } else {
hdr->next = NULL; }
zl.hdr = hdr; zl.used_memory += allocation_size;
ptr = (void *) ((char *) (hdr + 1) + PREFIX_SIZE); }
} }
pthread_mutex_unlock(&zl.lock); /* 返回分配的内存 */
return ptr;}
/* 释放内存 */void zfree(void *ptr) {
if (ptr) { struct zmalloc_hdr *hdr = (struct zmalloc_hdr *) ((char *) ptr - PREFIX_SIZE);
pthread_mutex_lock(&zl.lock); if (!hdr->free) {
hdr->free = 1; hdr->used -= PREFIX_SIZE;
zl.used_memory -= PREFIX_SIZE; }
if (!hdr->used) { /* 如果内存块已被释放,则从内存链表中移除 */
if (hdr->prev) { hdr->prev->next = hdr->next;
} else { zl.hdr = hdr->next;
} if (hdr->next) {
hdr->next->prev = hdr->prev; }
zl.used_memory -= hdr->size; free(hdr);
} pthread_mutex_unlock(&zl.lock);
}}
/* 扩展内存 */void *zrealloc(void *ptr, size_t size) {
size_t old_size; void *new_ptr;
if (ptr == NULL) { return zmalloc(size);
} if (size == 0) {
zfree(ptr); return NULL;
} struct zmalloc_hdr *hdr = (struct zmalloc_hdr *) ((char *) ptr - PREFIX_SIZE);
old_size = hdr->used - PREFIX_SIZE; new_ptr = zmalloc(size);
if (new_ptr) { memcpy(new_ptr, ptr, old_size
zfree(ptr); }
return new_ptr;}
在Redis中,所有的内存分配和释放都是通过zmalloc和zfree函数完成的。在zmalloc函数中,先尝试在对象池中找到对应的内存链表,如果找到,则分配内存,并将分配的内存块标记为已使用。如果对象池中没有找到对应的内存链表,则尝试扩展内存。而在zfree函数中,只需将已使用内存块的状态标记为未使用即可,如果发现该内存块未被使用,则将其从内存链表中移除,并彻底释放其占用的内存空间。在Redis中,zrealloc函数只是简单地调用了zmalloc和zfree函数。
2. 引用计数的实现
Redis中每个对象都有一个引用计数值,用来表示有多少个指针指向该对象。Redis对引用计数值的的操作主要是由incrRefCount和decrRefCount两个函数完成的(redisObject.c)。
/* redisObject.c */
/* 增加引用计数 */void incrRefCount(robj *o) {
o->refcount++;}
/* 减少引用计数 */void decrRefCount(robj *o) {
if (o->refcount printf("Error: refcount is negative.\n");