redis 源代码分析(一) 内存管理

时间:2022-12-31 22:43:47

一,redis内存管理介绍

redis是一个基于内存的key-value的数据库,其内存管理是很重要的,为了屏蔽不同平台之间的差异,以及统计内存占用量等,redis对内存分配函数进行了一层封装,程序中统一使用zmalloc,zfree一系列函数,其相应的源代码在src/zmalloc.h和src/zmalloc.c两个文件里,源代码点这里

二,redis内存管理源代码分析

redis封装是为了屏蔽底层平台的差异,同一时候方便自己实现相关的函数,我们能够通过src/zmalloc.h 文件里的相关宏定义来分析redis是怎么实现底层平台差异的屏蔽的,zmalloc.h 中相关宏声明例如以下:

#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 ZMALLOC_LIB
#define ZMALLOC_LIB "libc"
#endif ...
#ifndef HAVE_MALLOC_SIZE
size_t zmalloc_size(void *ptr);
#endif

通过上面的宏的预处理我们能够发现redis为了屏蔽不同系统(库)的差异进行了例如以下预处理:

A,若系统中存在Google的TC_MALLOC库,则使用tc_malloc一族函数取代原本的malloc一族函数。

B,若系统中存在FaceBook的JEMALLOC库,则使用je_malloc一族函数取代原本的malloc一族函数。

C,若当前系统是Mac系统,则使用<malloc/malloc.h>中的内存分配函数。

D,其它情况,在每一段分配好的空间前头,同一时候多分配一个定长的字段,用来记录分配的空间大小。

tc_malloc是google开源处理的一套内存管理库,是用C++实现的,主页在这里。TCMalloc给每一个线程分配了一个线程局部缓存。小分配能够直接由线程局部缓存来满足。须要的话,会将对象从*数据结构移动到线程局部缓存中,同一时候定期的垃圾收集将用于把内存从线程局部缓存迁移回*数据结构中。这篇文章里对TCMalloc有个具体的介绍。

jemalloc
也是一个内存创管理库,其创始人Jason Evans也是在FreeBSD非常有名的开发者,參见这里。Jemalloc聚集了malloc的使用过程中所验证的非常多技术。忽略细节,从架构着眼,最出色的部分仍是arena和thread cache。

读者一定会有疑问系统不是有了malloc 吗,为什么还有这种内存管理库?? 因为经典的libc的分配器碎片率为较高,能够查看这篇文章的分析,关于内存碎片不太了解的童鞋请參考这里, malloc
和free 怎么工作的參考这里。 关于ptmalloc,tcmalloc和jemalloc内存分配策略的一篇总结不错的文章,请点这里

以下介绍redis封装的内存管理相关函数,src/zmalloc.h有相关声明。

void *zmalloc(size_t size);//malloc
void *zcalloc(size_t size);//calloc
void *zrealloc(void *ptr, size_t size);/realloc
void zfree(void *ptr);//free
char *zstrdup(const char *s);
size_t zmalloc_used_memory(void);
void zmalloc_enable_thread_safeness(void);
void zmalloc_set_oom_handler(void (*oom_handler)(size_t));
float zmalloc_get_fragmentation_ratio(void);
size_t zmalloc_get_rss(void);
size_t zmalloc_get_private_dirty(void);
void zlibc_free(void *ptr);

如今主要介绍下redis内存分配函数 void *zmalloc(size_t size),其相应的声明形式例如以下:

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
}

阅读源代码我们发现有个PREFIX_SIZE 宏,其宏定义形式例如以下:

/* 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

结合src/zmalloc.h有相关宏声明,我们发现,由于 tc_malloc 、je_malloc 和 Mac平台下的 malloc 函数族提供了计算已分配空间大小的函数(各自是tc_malloc_size, je_malloc_usable_size和malloc_size),所以就不须要单独分配一段空间记录大小了。在linux和sun平台则要记录分配空间大小。对于linux,使用sizeof(size_t)定长字段记录;对于sun 系统,使用sizeof(long
long)定长字段记录,其相应源代码中的 PREFIX_SIZE 宏。

PREFIX_SIZE 有什么用呢?

为了统计当前进程究竟占用了多少内存。在 zmalloc.c 中,有一个静态变量:

static size_t used_memory = 0;

这个变量它记录了进程当前占用的内存总数。每当要分配内存或是释放内存的时候,都要更新这个变量(当然能够是线程安全的)。由于分配内存的时候,须要指定分配多少内存。可是释放内存的时候,(对于未提供malloc_size函数的内存库)通过指向要释放内存的指针是不能知道释放的空间究竟有多大的。这时候,上面提到的PREFIX_SIZE就起作用了,能够通过当中记录的内容得到空间的大小。(只是在linux系统上也有对应的函数获得分配内存空间的大小,參见这里)。

通过zmalloc的源代码我们能够发现,其分配空间代码为void *ptr = malloc(size+PREFIX_SIZE); 显然其分配空间大小为:size+PREFIX_SIZE ,对于使用tc_malloc或je_malloc的情况或mac系统,其 PREFIX_SIZE 为0。当分配失败时有对应的出错处理 。

前面我们已经说过redis通过使用used_memory 的变量来统计当前进程究竟占用了多少内存,因此在分配和释放内存时我们须要紧接着更新used_memory 的相应值,相应到redis源代码中为:

#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

上面的代码有事宏预处理 #ifdef HAVE_MALLOC_SIZE 显然是上面我们说过的利用的tc_malloc je_malloc Mac等提供malloc_size函数的情形,我们能够非常easy得知分配内存的大小通过统一化的malloc_size函数就可以。可是对于没有提供malloc_size功能的函数,redis是怎么处理的呢?看上面的源代码 #else以下的代码即是事实上现,其相应的内存结构例如以下:

prefix-size memory size

分配的内存前加一个固定大小的prefis-size空间,用于记录该段内存的大小,size所占领的内存大小是已知的,为size_t类型的长度,因此通过*((size_t*)ptr) = size; 就可以对当前内存块大小进行指定。每次分配内存后,返回的实际地址指针为指向memorysize的地址( (char*)ptr+PREFIX_SIZE; ),通过该指针,能够非常easy的计算出实际内存的头地址,从而释放内存。

redis通过update_zmalloc_stat_alloc(__n,__size) 和 update_zmalloc_stat_free(__n) 这两个宏负责在分配内存或是释放内存的时候更新used_memory变量。update_zmalloc_stat_alloc定义例如以下:

#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)

redis把这个更新操作写成宏的形式主要是处于效率的考虑。

上面的代码中

A,if (_n&(sizeof(long)-1)) _n += sizeof(long)-(_n&(sizeof(long)-1));

  主要是考虑对齐问题,保证新增的_n 是 sizeof(long)的倍数。

B,   if (zmalloc_thread_safe) { \

        update_zmalloc_stat_add(_n); \

}

假设进程中有多个线程存在,并保证线程安全zmalloc_thread_safe,则在更新变量的时候要加锁。  通过宏HAVE_ATOMIC选择对应的同步机制。

zmalloc_calloc、zmalloc_free等的实现就不细致介绍了详情參见源代码

最后解说下 zmalloc_get_rss()函数。

   这个函数用来获取进程的RSS。神马是RSS?全称为Resident Set Size,指实际使用物理内存(包括共享库占用的内存)。在linux系统中,能够通过读取/proc/pid/stat文件系统获取,pid为当前进程的进程号。读取到的不是byte数,而是内存页数。通过系统调用sysconf(_SC_PAGESIZE)能够获得当前系统的内存页大小。 获得进程的RSS后,能够计算眼下数据的内存碎片大小,直接用rss除以used_memory。rss包括进程的全部内存使用,包括代码,共享库,堆栈等。 哪来的内存碎片?上面我们已经说明了通常考虑到效率,往往有内存对齐等方面的考虑,所以,碎片就在这里产生了。相比传统glibc中的malloc的内存利用率不是非常高通常会使用别的内存库系统。在redis中默认的已经不使用简单的malloc了而是使用
jemalloc, 在源文件src/Makefile下有这样一段代码:

ifeq ($(uname_S),Linux)
MALLOC=jemalloc

能够知道在linux系统上默认使用jemalloc, 在redis公布的源代码中有相关的库 deps/jemalloc 。

总的来说 redis则全然自主分配内存,在请求到的时候实时依据内建的算法分配内存,全然自主控制内存的管理。简单即是没吧,只是功能确实强大。

參考:

http://blog.ddup.us/?p=136

redis 源代码分析(一) 内存管理的更多相关文章

  1. redis源代码解读之内存管理————zmalloc文件

    本文章主要记录本人在看redis源代码的一些理解和想法.由于功力有限,肯定会出现故障,所以.希望高手给出指正. 第一篇就是内存相关的介绍.由于我喜欢先看一些组件的东西,再看总体的流程. 先上一下代码吧 ...

  2. cocos2d-x 源代码分析 : Ref (CCObject) 源代码分析 cocos2d-x内存管理策略

    从源代码版本号3.x.转载请注明 cocos2d-x 总的文件夹的源代码分析: http://blog.csdn.net/u011225840/article/details/31743129 1.R ...

  3. MySQL系列:innodb源代码分析之内存管理

    在innodb中实现了自己的内存池系统和内存堆分配系统,在innodb的内存管理系统中,大致分为三个部分:基础的内存块分配管理.内存伙伴分配器和内存堆分配器.innodb定义和实现内存池的主要目的是提 ...

  4. Memcached源码分析之内存管理

    先再说明一下,我本次分析的memcached版本是1.4.20,有些旧的版本关于内存管理的机制和数据结构与1.4.20有一定的差异(本文中会提到). 一)模型分析在开始解剖memcached关于内存管 ...

  5. iOS的内存分析和内存管理

    iOS的内存分析和内存管理 [内存管理]一直是iOS开发中的一个重点. 本文就带你从内存分析开始一步步了解内存的占用情况,从真实的情况中领悟真正项目开发过程中的内存的使用情况. 注:本文默认你熟悉 M ...

  6. TestNG源代码分析:依赖管理的实现

    TestNG源代码分析:依赖管理的实现 2018-03-19 1 背景 当case之间有依赖关系,有依赖关系的case,它们的执行顺序是有限制的.TestNG提供了依赖管理功能 2 基础理论 这个执行 ...

  7. Redis源代码分析(十一年)--- memtest内存测试

    今天,我们继续redis源代码test下测试在封装中的其它文件.今天读数memtest档,翻译了,那是,memory test 存储器测试工具..可是里面的提及了非常多东西,也给我涨了非常多见识,网上 ...

  8. linux 内核源代码情景分析——linux 内存管理的基本框架

    386 CPU中的页式存管的基本思路是:通过页面目录和页面表分两个层次实现从线性地址到物理地址的映射.这种映射模式在大多数情况下可以节省页面表所占用的空间.因为大多数进程不会用到整个虚存空间,在虚存空 ...

  9. Redis源代码分析(一)--Redis结构解析

    从今天起,本人将会展开对Redis源代码的学习,Redis的代码规模比較小,很适合学习,是一份很不错的学习资料,数了一下大概100个文件左右的样子,用的是C语言写的.希望终于能把他啃完吧,C语言好久不 ...

随机推荐

  1. ueditor编辑器和at&period;js集成

    源码: <%@ page language="java" import="java.util.*" pageEncoding="UTF-8&qu ...

  2. 固定某一元素在某一位置 jquery

    方法: $(window).scroll(function (){}) 属性: 滚动变化值 $(window).scrollTop();$(window).scrollLeft(); 需要结合的样式: ...

  3. Android ScrollView与ViewPager滑动冲突

    前段时间做项目碰到在ScrollView里添加ViewPager,但是发现ViewPager的左右滑动和ScrollView的滑动冲突了,解决这个问题的方法是重写ScrollView. 代码: pub ...

  4. web config数据库连接字符串加密

    ASP.NET web.config中,数据库连接字符串的加密与解密 ASP.NET web.config中,数据库连接字符串的加密与解密. 开始--->运行,输入cmd,接着输入以下内容 加密 ...

  5. WIN10 &plus; VS2015 &plus; WDK10 &plus; SDK10 &plus; VM虚拟机驱动开发调试环境搭建

    http://blog.csdn.net/qing666888/article/details/50858272#comments

  6. 2机器学习实践笔记&lpar;k-最近邻&rpar;

    1:算法是简单的叙述说明 由于训练数据样本和标签,为测试数据的示例,从最近的距离k训练样本,此k练样本中所属类别最多的类即为该測试样本的预測标签. 简称kNN.通常k是不大于20的整数,这里的距离通常 ...

  7. MarkDown 常用语法教程

    MarkDown 语法说明 [TOC] 标题 标题1 ====== 标题2 ----- ## 大标题 ### 小标题 #### 小标题 列表 无序列表 + 列表文本前使用 [减号+空格] * 列表文本 ...

  8. 手动导入xmpp后,再使用cocoapods的时候出现的问题

    最新的cocoapod导入xmpp的时候,会出现循环依赖,所以撸主选择了手动导入. 一开始还用的挺开心的,后来,使用cocoapods导入其他的框架,发现调用的时候总是报错. Undefined sy ...

  9. Linux系统网络文件配置

    /etc  一.修改配置文档(需要重启网络配置,永远生效) 1.修改IP地址[MariaDB@db1]$ vi /etc/sysconfig/network-scripts/ifcfg-eth0DEV ...

  10. mysql 1267 error

    CREATE TABLE a (id VARCHAR(32)) DEFAULT CHARSET = utf8 COLLATE utf8_general_ci:INSERT INTO a(id) VAL ...