博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Redis 内存分配分析
阅读量:6983 次
发布时间:2019-06-27

本文共 7566 字,大约阅读时间需要 25 分钟。

为什么要分析

之前业务反应,数据导入到Redis 中,内存是原来文件占用的几倍。所以这里来介绍一下Redis是如何分配内存的。并且在我们日常去评估一个新上线的业务redis内存使用也是非常有帮助的。

需要了解的

这里以简单的Redis String数据类型作为例子,其他数据类型可以作为参考,只要不是采用压缩数据类型存储的。全文会介绍到涉及到内存分配的地方。并且会以此来计算Redis使用的内存,最终与Redis info 中统计的内存使用进行比较。

在介绍之前需要简单介绍一下Redis中是如何存储Key以及Value的。

其实在Redis中,并不是单纯将key 与value保存到内存中就可以的。它需要依赖一些结构对其进行管理。

图片描述

如上图所示,在Redis中,一个DB对应上面绿色的一个 dict结构体:

typedef struct dict {    dictType *type;    void *privdata;    dictht ht[2];                                                                                                                                                                                                                               long rehashidx; /* rehashing not in progress if rehashidx == -1 */    int iterators; /* number of iterators currently running */} dict;

该结构体包含两个dictht结构体,dictht结构体如下:

typedef struct dictht {    dictEntry **table;    unsigned long size;    unsigned long sizemask;    unsigned long used;} dictht;

dictht结构体中又包含指向多个dictEntry 结构体的指针,dictEntry结构体如下:

typedef struct dictEntry {    void *key;                 union {                       void *val;                 uint64_t u64;             int64_t s64;               double d;             } v;                       struct dictEntry *next;} dictEntry;

所以最终key及value是存储在dictEntry中(准确说是key和val指向对应的key及value对象)。

开始计算

这里Redis为什么要这么设计就不重点介绍了,这里重点讨论在Redis存储一个 键值对(key/value)的时候,这些结构体中涉及到需要分配内存的地方。

我们先看在我们执行一条 set jingbo test 命令的时候,Redis是怎么分配内存的。

在Redis 服务器端接收到 set jingbo test这条命令的时候,会在processMultibulkBuffer 方法中调用createStringObject方法分别为set/jingbo/test 创建三个字符串对象。

创建字符串对象的时候又区分是否是EMBSTR 编码,这里就不讨论了。因为不管是否是采用EMBSTR编码,所占的内存是没有变化的,只是影响效率。
由于这里 set/jingbo/test 字符都没有超过39个,所以Redis会采用EMBSTR编码,那么创建对象方法如下:

/* Create a string object with encoding REDIS_ENCODING_EMBSTR, that is * an object where the sds string is actually an unmodifiable string                                                                                                                                                                        * allocated in the same chunk as the object itself. */robj *createEmbeddedStringObject(char *ptr, size_t len) {    robj *o = zmalloc(sizeof(robj)+sizeof(struct sdshdr)+len+1);    struct sdshdr *sh = (void*)(o+1);    o->type = REDIS_STRING;    o->encoding = REDIS_ENCODING_EMBSTR;    o->ptr = sh+1;    o->refcount = 1;    o->lru = LRU_CLOCK();     sh->len = len;    sh->free = 0;    if (ptr) {        memcpy(sh->buf,ptr,len);        sh->buf[len] = '\0';    } else {        memset(sh->buf,0,len+1);    }       return o;}

那么我们可以看到在第一行中,Redis为其分配了sizeof(robj)+sizeof(struct sdshdr)+len+1这么大的内存。

这里计算 sizeof(robj)= 16 +sizeof(struct sdshdr) = 8 + len(字符串本身长度) + 1
所以jingbo这个字符串在这里就需要16+8+6+1=31b,但是Redis采用的内存分配器实际为其分配32b,同理test这个字符串内存分配器为其分配32b

由于是set命令,接着就到dbAdd方法下,dbAdd方法如下:

/* Add the key to the DB. It's up to the caller to increment the reference * counter of the value if needed. * * The program is aborted if the key already exists. */void dbAdd(redisDb *db, robj *key, robj *val) {                                                                                                                                                                                                 sds copy = sdsdup(key->ptr);    int retval = dictAdd(db->dict, copy, val);     redisAssertWithInfo(NULL,key,retval == REDIS_OK);    if (val->type == REDIS_LIST) signalListAsReady(db, key);    if (server.cluster_enabled) slotToKeyAdd(key); }

这里的robj key, robj val 传入的对象就是刚刚Redis创建的字符串对象。我们可以看到在方法的第一行,其实最终创建了一个sds字符串对象,就是调用以下方法:

sds sdsnewlen(const void *init, size_t initlen) {    struct sdshdr *sh;     if (init) {        sh = zmalloc(sizeof(struct sdshdr)+initlen+1);    } else {        sh = zcalloc(sizeof(struct sdshdr)+initlen+1);    }    if (sh == NULL) return NULL;    sh->len = initlen;    sh->free = 0;    if (initlen && init)        memcpy(sh->buf, init, initlen);    sh->buf[initlen] = '\0';    return (char*)sh->buf;}

所以这里需要的内存大小就是 sizeof(struct sdshdr)+initlen+1,即 sizeof(struct sdshdr) = 8 + initlen(字符串本身长度) + 1 ,那么key在这里就需要8+6+1=15b,内存分配器实际分配16b

所以其实最终Redis 的 key存储是用的上面的创建的这个SDS对象。value 就是之前创建的字符串对象。最后dictEntry结构体中的key和 value会分别指向key和value对象,那之前创建的字符串对象会在客户端释放或者其他情况下进行释放。
这里还有一个涉及到内存分配的地方就是为 dictEntry结构体分配内存,dictEntry结构体需要24b,Redis内存分配器实际为其分配32b

那么目前为止Redis分配的内存为 16b(jingbo)+ 32b(test) + 32b(dictEntry 结构体) = 78b

验证结果

那实际情况是否是这样呢?

我们这里采用Redis 官方自带压测工具benchmark压测。

/usr/local/rc-redis-3.0.7/src/redis-benchmark -p 6379 -t set -n 1000000 -r 1000000

压测结果:

[jingbo8@poseidon54 ~]$ redis-cli -p 6379 info|grep keysexpired_keys:0evicted_keys:0keyspace_hits:0keyspace_misses:0db0:keys=632147,expires=0,avg_ttl=0[jingbo8@poseidon54 ~]$ redis-cli -p 6379 info|grep memused_memory:69423144used_memory_human:66.21Mused_memory_rss:72884224used_memory_peak:72148656used_memory_peak_human:68.81Mused_memory_lua:36864mem_fragmentation_ratio:1.05mem_allocator:jemalloc-3.6.0

我们可以看到共有632147 个key,占用66.21M内存。benchmark所有的key如下所示:

set key:000000166802 xxx

key 为 16个字符,value 为三个字符,那么key需要 8+16+1=25b,实际分配 32b,value需要 16+8+3+1=28b,实际分配 32b,dictEntry结构体需要24b,实际分配32b

所以这里单个key需要 32b(key)+32b(value)+32b(dictEntry结构体 )=96b

那么总共内存是:

96b*632147=6068611260686112/1024/1024 = 57.87MB

我们可以看到这里离66.21M还差一些内存。这里我们并没有去考虑Redis在初始为一些元数据结构分配的内存(比如创建的共享对象等),我们离实际使用的内存还差66.21-57.87=8.34MB

那其实我们少算了dictht结构体所占用的内存。上图中的ht[0]和ht[1]为两个dictht结构体。ht[1]主要是为了在ht[0]需要扩容的时候使用。平常不占用内存,这里主要看ht[0]中占用的内存。

/* Expand or create the hash table */int dictExpand(dict *d, unsigned long size){    dictht n; /* the new hash table */    unsigned long realsize = _dictNextPower(size);     /* the size is invalid if it is smaller than the number of     * elements already inside the hash table */    if (dictIsRehashing(d) || d->ht[0].used > size)        return DICT_ERR;     /* Rehashing to the same table size is not useful. */    if (realsize == d->ht[0].size) return DICT_ERR;     /* Allocate the new hash table and initialize all pointers to NULL */    n.size = realsize;    n.sizemask = realsize-1;    n.table = zcalloc(realsize*sizeof(dictEntry*));    n.used = 0;     /* Is this the first initialization? If so it's not really a rehashing     * we just set the first hash table so that it can accept keys. */    if (d->ht[0].table == NULL) {        d->ht[0] = n;        return DICT_OK;    }        /* Prepare a second hash table for incremental rehashing */    d->ht[1] = n;    d->rehashidx = 0;    return DICT_OK;}

在系统最开始初始化的时候会初始化ht[0],并且为realsize 分配为4,那么这里分配的内存是 realsizesizeof(dictEntry)=4*8 = 32b,这只是最开始的时候,并且只能存储4个dictEntry指针,也就是对应4个key,在key增长的时候,会对ht[0]进行扩容,这时候会先将ht[1]扩容至ht[0]的两倍,然后将ht[0]中对应的dictEntry全部迁移到ht[1],然后他们再相互交换一下。那么ht[1]又变成ht[0]了,之前的ht[0]变为ht[1]并且释放内存。所以在每次ht[0]满了之后都会扩容至以前的2倍。

那目前我们key的数量是632147,那么realsize是多少呢

realsize=4realsize=8realsize=16realsize=32realsize=64realsize=128realsize=256realsize=512realsize=1024realsize=2048realsize=4096realsize=8192realsize=16384realsize=32768realsize=65536realsize=131072realsize=262144realsize=524288realsize=1048576

524288 <632147 <1048576,所以目前realsize是1048576,那么总共需要分配的内存就是1048576*8= 8388608,8388608/1024/1024=8MB

那么之前8.34-8=0.34 MB,所以目前我们只相差0.34MB。那么我们把所有key清空看一下Redis本身使用了多少内存呢。

[jingbo8@poseidon54 ~]$ redis-cli -p 6379 info|grep mem                                                             used_memory:349256used_memory_human:341.07Kused_memory_rss:2015232used_memory_peak:349256used_memory_peak_human:341.07Kused_memory_lua:36864mem_fragmentation_ratio:5.77mem_allocator:jemalloc-3.6.0

0.34*1024=348.16k,这样与目前的内存非常接近了。

那么从上面这个分析结果来看,Redis 本身的结构体占据了大部分的内存。所以最终造成导入到Redis中的内存与之前文件中所占用的空间差距较大,内存是宝贵的资源,所以大家在往Redis存储数据的时候一定要设计好。

至此整个内存如何分配分析完成,有问题可以随时联系。

转载地址:http://eltpl.baihongyu.com/

你可能感兴趣的文章
魅族 C++ 微服务框架技术内幕揭秘
查看>>
flask 学习笔记 mvc ,sqlalchemy(insert,update)
查看>>
HTML基础(一)
查看>>
EGOImageView 解析
查看>>
我的友情链接
查看>>
我的友情链接
查看>>
linux日常管理3
查看>>
JQ 实现点击按钮显示弹窗,点击非弹窗和按钮区域隐藏弹窗
查看>>
我的友情链接
查看>>
我的友情链接
查看>>
1.解读REST和JAX-RS
查看>>
将RHEL7/centos7系统网卡名称eno16777736改为eth0
查看>>
Nginx域名跳转
查看>>
NTP时间服务器安装
查看>>
Thinkphp3.23 关联模型relation方法不存在解决方法
查看>>
bash脚本编程之一 条件判断及算术运算
查看>>
工作中非常逆天的shell命令
查看>>
我的友情链接
查看>>
[IOS]clang diagnostic、Wprotocol ..
查看>>
HTML4.01规范-HTML文档的顶层结构(3)
查看>>