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");


数据运维技术 » Redis源码分析内存管理之道(redis 源码 内存)