Redis源码分析-内存分配

标签: Redis zmalloc 内存 | 发表时间:2011-08-15 23:42 | 作者:nosqlfan gOODiDEA
出处:http://blog.nosqlfan.com

本文转载自Day Day Up博客,文章对Redis内存分配封装zmalloc库进行了分析,描述了Redis在内存分配和使用统计方面的各种细节和技巧。

原文链接:blog.ddup.us

Redis中到处都会进行内存分配操作。为了屏蔽不同平台之间的差异,以及统计内存占用量等,Redis对内存分配函数进行了一层封装,程序中统一使用zmalloc,zfree一系列函数,位于zmalloc.h,zmalloc.c文中。

上边说过,封装就是为了屏蔽底层平台的差异,同时方便自己实现相关的统计函数。具体来说就是:

  • 若系统中存在Google的TC_MALLOC库,则使用tc_malloc一族函数代替原本的malloc一族函数。
  • 若当前系统是Mac系统,则使用<malloc/malloc.h>中的内存分配函数。
  • 其他情况,在每一段分配好的空间前头,同时多分配一个定长的字段,用来记录分配的空间大小。

源代码分别在 config.h 和 zmalloc.c 中:

/* config.h */
#if defined(USE_TCMALLOC)
#include <google/tcmalloc.h>
#if TC_VERSION_MAJOR >= 1 && TC_VERSION_MINOR >= 6
#define HAVE_MALLOC_SIZE 1
#define redis_malloc_size(p) tc_malloc_size(p)
#endif
#elif defined(__APPLE__)
#include <malloc/malloc.h>
#define HAVE_MALLOC_SIZE 1
#define redis_malloc_size(p) malloc_size(p)
#endif
/* zmalloc.c */
#ifdef HAVE_MALLOC_SIZE
#define PREFIX_SIZE (0)
#else
#if defined(__sun)
#define PREFIX_SIZE (sizeof(long long))
#else
#define PREFIX_SIZE (sizeof(size_t))
#endif
#endif

因为 tc_malloc 和 Mac平台下的 malloc 函数族提供了计算已分配空间大小的函数(分别是tc_malloc_size和malloc_size),所以就不需要单独分配一段空间记录大小了。而针对linux和sun平台则要记录分配空间大小。对于linux,使用sizeof(size_t)定长字段记录;对于sun os,使用sizeof(long long)定长字段记录。也就是上边源码中的 PREFIX_SIZE 宏。

那么这个记录有什么用呢?答案是,为了统计当前进程到底占用了多少内存。在 zmalloc.c 中,有这样一个静态变量:

static size_t used_memory = 0;

它记录了进程当前占用的内存总数。每当要分配内存或是释放内存的时候,都要更新这个变量。因为分配内存的时候,可以明确知道要分配多少内存。但是释放内存的时候,(对于未提供malloc_size函数的平台)仅通过指向要释放内存的指针是不能知道释放的空间到底有多大的。这个时候,上边提到的PREFIX_SIZE定长字段就起作用了,可以通过其中记录的内容得到空间的大小。zmalloc函数如下(去掉无关代码):

void *zmalloc(size_t size) {
    void *ptr = malloc(size+PREFIX_SIZE);

    if (!ptr) zmalloc_oom(size);
    *((size_t*)ptr) = size;
    update_zmalloc_stat_alloc(size+PREFIX_SIZE,size);
    return (char*)ptr+PREFIX_SIZE;
#endif
}

看到在分配空间的时候,空间大小是size+PREFIX_SIZE。对于mac系统或是使用tc_malloc的情况,PREFIX_SIZE 为0。之后将ptr指针指向的空间前size_t中记录分配空间的大小。最后返回的是越过记录区的指针。zfree函数类似(去掉无关代码):

void zfree(void *ptr) {
    void *realptr;
    size_t oldsize;

    if (ptr == NULL) return;
    realptr = (char*)ptr-PREFIX_SIZE;
    oldsize = *((size_t*)realptr);
    update_zmalloc_stat_free(oldsize+PREFIX_SIZE);
    free(realptr);
#endif
}

先将指针向前移动PREFIX_SIZE,然后取出分配空间时保存的空间长度。最后free整个空间。

update_zmalloc_stat_alloc(__n,__size) 和 update_zmalloc_stat_free(__n) 这两个宏负责在分配内存或是释放内存的时候更新used_memory变量。定义成宏主要是出于效率上的考虑。将其还原为函数,就是下边这个样子:

void update_zmalloc_stat_alloc(__n,__size)
{
    do {
        size_t _n = (__n);
        size_t _stat_slot = (__size < ZMALLOC_MAX_ALLOC_STAT) ? __size : ZMALLOC_MAX_ALLOC_STAT;
        if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1));
        if (zmalloc_thread_safe) {
            pthread_mutex_lock(&used_memory_mutex);
            used_memory += _n;
            zmalloc_allocations[_stat_slot]++;
            pthread_mutex_unlock(&used_memory_mutex);
        } else {
            used_memory += _n;
            zmalloc_allocations[_stat_slot]++;
        }
    } while(0)
}

void 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) {
            pthread_mutex_lock(&used_memory_mutex);
            used_memory -= _n;
            pthread_mutex_unlock(&used_memory_mutex);
        } else {
            used_memory -= _n;
        }
    } while(0)
}

代码中除了更新used_memory变量外,还有几个要关注的地方:

  1. 先对_n的低位向上取整,最后_n变为sizeof(long)的倍数,比如对于32位系统,sizeof(long) == 100(二进制),_n向上取整之后,低两位都变为0。
  2. 如果进程中有多个线程存在,则在更新变量的时候要加锁。
  3. 在zmalloc函数中还有一个统计量要更新:zmalloc_allocations[]。

在 zmalloc.c 中,zmalloc_allocations是这样定义的:

size_t zmalloc_allocations[ZMALLOC_MAX_ALLOC_STAT+1];

其作用是统计程序分配内存时,对不同大小空间的请求次数。统计的空间范围从1字节到256字节,大于256字节的算为256。统计结果通过调用 zmalloc_allocations_for_size 函数返回:

size_t zmalloc_allocations_for_size(size_t size) {
    if (size > ZMALLOC_MAX_ALLOC_STAT) return 0;
    return zmalloc_allocations[size];
}

另一个对内存使用量的统计通过调用 zmalloc_used_memory 函数返回:

size_t zmalloc_used_memory(void) {
    size_t um;

    if (zmalloc_thread_safe) pthread_mutex_lock(&used_memory_mutex);
    um = used_memory;
    if (zmalloc_thread_safe) pthread_mutex_unlock(&used_memory_mutex);
    return um;
}

另外 zmalloc.c 中,还针对不同的系统实现了 zmalloc_get_rss 函数,在linux系统中是通过读取/proc/$pid/stat文件获得系统统计的内存占用量。关于/proc虚拟文件系统可以看之前的文章

技术传播,需要你我共同努力!    

相关文章:

Redis事件驱动库结构

Redis与CouchDB的对比

Redis4You:Redis云存储服务商

redis_warp、redis_graph、redis_queue - Python下的Redis二次开发

Redis内存存储结构分析
无觅

相关 [redis 源码 分析] 推荐:

Redis源码分析-内存分配

- gOODiDEA - NoSQLFan
本文转载自Day Day Up博客,文章对Redis的内存分配封装zmalloc库进行了分析,描述了Redis在内存分配和使用统计方面的各种细节和技巧. 原文链接:blog.ddup.us. Redis中到处都会进行内存分配操作. 为了屏蔽不同平台之间的差异,以及统计内存占用量等,Redis对内存分配函数进行了一层封装,程序中统一使用zmalloc,zfree一系列函数,位于zmalloc.h,zmalloc.c文中.

Redis源码分析系列文章

- gOODiDEA - NoSQLFan
Redis 的源码只有2万来行,个人觉得是一个非常合适的学习Unix 环境下C语言编程的实例教材. 而读源码,也对了解Redis内部结构很有帮助. 下面推荐的几篇文章,来自阿里巴巴云计算运维部的 hoterran 同学的个人博客,分别对Redis几个重要流程的源码进行了分析研究,对了解Redis内部结构很有帮助.

redis超时问题分析

- - 搜索技术博客-淘宝
Redis在分布式应用中占据着越来越重要的地位,短短的几万行代码,实现了一个高性能的数据存储服务. 最近dump中心的cm8集群出现过几次redis超时的情况,但是查看redis机器的相关内存都没有发现内存不够,或者内存发生交换的情况,查看redis源码之后,发现在某些情况下redis会出现超时的状况,相关细节如下.

Redis事件综合分析

- - FreeBuf.COM | 关注黑客与极客
redis未授权访问一直未被大家重视,直到11月4号,在. 这篇blog 上被爆出:redis可以通过写入SSH Key进而控制服务器,安全人员开始大量关注这一事件. 暴露在公网的redis如果没有启用认证服务或者采用弱口令密钥对时,可被攻击者恶意登录,通过写入SSH公钥或者写入crontab执行命令的方式进而控制服务器.

redis-faina:一个Redis的query分析小工具

- - NoSQLFan
redis-faina 是由Instagram 开发并开源的一个 Redis 查询分析小工具. Instagram团队曾经使用 PGFouine 来作为其PostgreSQL的查询分析工具,他们觉得Redis也需要一个类似的工具来进行query分析工作,于是开发了 redis-faina. redis-faina 是通过Redis的 MONITOR命令来实现的,通过对在Redis上执行的query进行监控,统计出一段时间的query特性.

Redis内存存储结构分析

- 小丑鱼 - 搜索技术博客-淘宝
Redis 内存存储结构. 本文是基于 Redis-v2.2.4 版本进行分析.. 1.1 Redis 内存存储总体结构. Redis 是支持多key-value数据库(表)的,并用 RedisDb 来表示一个key-value数据库(表). redisServer 中有一个 redisDb *db; 成员变量, RedisServer 在初始化时,会根据配置文件的 db 数量来创建一个 redisDb 数组.

Redis Cluster的FailOver失败案例分析

- - ITeye博客
      使用redis clusterRC1部署集群,6台机器,每台部署16个实例,每个master使用一个slave,node_timeout为默认值(15s). kill掉其中一个master发现failover完成不了. 通过cluster nodes观察,该节点一直处于pfail状态. 问题出在失败判定上,一直处于PFail,说明完成不了PFail->Fail的转换.

Redis AOF刷新策略分析(转载)

- - 互联网 - ITeye博客
此文为转载,原文:  http://afei2.sinaapp.com/?p=536.    redis支持使用aof来进行持久化,防止数据丢失,aof的刷新策略通过参数appendfsync控制,有三个值:always、everysec、no,默认是everysec.    下面从源码的角度剖析一下aof的刷新策略.

Redis时延问题分析及应对

- - 博客 - 伯乐在线
Redis的事件循环在一个线程中处理,作为一个单线程程序,重要的是要保证事件处理的时延短,这样,事件循环中的后续任务才不会阻塞;. 当redis的数据量达到一定级别后(比如20G),阻塞操作对性能的影响尤为严重;. 下面我们总结下在redis中有哪些耗时的场景及应对方法;. keys、sort等命令.

一个 redis 异常访问引发 oom 的案例分析

- - mindwind
「推断的前提是以事实为依据. 这两天碰到一个线上系统的偶尔出现突然堆内存暴涨,这倒不是个什么疑难杂症, 只是过程中有些思路觉得可以借鉴参考,故总结下并写下来. 内存情况可以看看下面这张监控图. 一天偶尔出现几次,持续时间一般几分钟不等. 当这种情况出现时,我们检查错误日志,发现有下面两几种 OOM 错误.