营销型网站模板免费下载,苏州建设工程公司网站,婚纱摄影网站模板之家,高邮市建设局网站首页前言
Redis 写入键值对时#xff0c;首先会先创建一个 RedisObject 对象来存储 Value。 如果写入的 Value 是字符串#xff0c;那么 Redis 会再根据写入的字符串长度#xff0c;来创建对应的 sdshdr 来存储字符串#xff0c;最后把 RedisObject 的 ptr 指针指向 sdshdr。 …前言
Redis 写入键值对时首先会先创建一个 RedisObject 对象来存储 Value。 如果写入的 Value 是字符串那么 Redis 会再根据写入的字符串长度来创建对应的 sdshdr 来存储字符串最后把 RedisObject 的 ptr 指针指向 sdshdr。 我们来分析下这个过程首先创建 RedisObject 需要分配一次内存创建 sdshdr 又需要再分配一次内存。 由此可见如果 RedisObject 和 sds 分开存储的话需要多分配一次内存内存碎片化的概率也会增加。 Redis 本着节省内存的原则还可以做出哪些优化呢
EmbeddedString
先回顾一下 RedisObject 结构前三个属性合计占用 4 字节refcount 占用 4 字节ptr 指针占用 8 字节合计 16 字节。
typedef struct redisObject {unsigned type:4;unsigned encoding:4;unsigned lru:LRU_BITS;int refcount;void *ptr;
} robj;Redis 默认使用 jemalloc 内存分配器分配的内存必须是 2 的幂次方大小比如你要申请 5 字节jemalloc 会给你分配 8 字节你要申请 10 字节jemalloc 会分配 16 字节。 基于这个规则Redis 就想能不能创建 RedisObject 的同时就分配多一点内存好存储接下来的字符串呢当然可以那申请多大合适呢首先肯定要是 2 的幂次方数32 字节有点太小了因为 sdshdr8 头部就占用了 3 字节再加上一个 ‘\0’ 结尾符真正留给字符串的空间就剩 12 字节了显然不实用很容易溢出。 32 不够那只能再往上加了64 字节可以存储 44 字节的字符串基本够用了。恰巧在 x86 架构下CPU 缓存行的大小一般也是 64 字节刚好可以完整加载。
所以现在我们得出一个结论如果写入的字符串长度在 44 以内那么就可以在创建 RedisObject 时直接申请 64 字节然后把 sds 直接挨着 RedisObject 末尾写入这样就可以避免再分配一次内存内存的碎片率也能得到优化。
我们看看 Redis 具体是怎么做的创建字符串对象的方法是createStringObject()
#define OBJ_ENCODING_EMBSTR_SIZE_LIMIT 44
robj *createStringObject(const char *ptr, size_t len) {if (len OBJ_ENCODING_EMBSTR_SIZE_LIMIT)return createEmbeddedStringObject(ptr,len);elsereturn createRawStringObject(ptr,len);
}常量 OBJ_ENCODING_EMBSTR_SIZE_LIMIT 的值刚好就是 44这证明了我们的猜想。如果字符串长度超过了 44Redis 也只能分配 sds 空间单独存储字符串了对应的方法是createRawStringObject()。
这种和 RedisObject 存储在一起的字符串Redis 给它取名叫 EmbeddedString创建的方法是createEmbeddedStringObject()
robj *createEmbeddedStringObject(const char *ptr, size_t len) {robj *o zmalloc(sizeof(robj)sizeof(struct sdshdr8)len1);struct sdshdr8 *sh (void*)(o1); // sh 指向 RedisObject末尾 即sdshdr开始位置o-type OBJ_STRING; // 对外类型还是 stringo-encoding OBJ_ENCODING_EMBSTR; // 区别于普通sds这里的编码类型是8o-ptr sh1; // ptr 指向sdshdr末尾 即字符串开始位置o-refcount 1;// 设置lru时钟if (server.maxmemory_policy MAXMEMORY_FLAG_LFU) {o-lru (LFUGetTimeInMinutes()8) | LFU_INIT_VAL;} else {o-lru LRU_CLOCK();}// 设置sdshdr头sh-len len;sh-alloc len;sh-flags SDS_TYPE_8;if (ptr SDS_NOINIT)sh-buf[len] \0;else if (ptr) {memcpy(sh-buf,ptr,len);sh-buf[len] \0;} else {memset(sh-buf,0,len1);}return o;
}创建 EmbeddedString 的步骤如下
先分配内存大小是 RedisObject 大小 sdshdr8 大小 字符串长度 1个’\0’字符的长度sh 指针指向 sdshdr 的起始位置RedisObject-ptr 指针指向字符数组的起始位置在介绍 sds 的说过了指针左移一位就能读到 flags给 RedisObject 对象设置 lru 时间戳设置 sdshdr 头数据
尾巴
当我们向 Redis 写入 string 数据时Redis 首先要创建 RedisObject 分配一次内存然后再创建 sds 时又要二次分配内存这样不仅浪费内存还会增加碎片化率。Redis 结合 jemalloc 的分配策略以及 x86 架构下的缓存行大小决定如果写入的字符串长度较小就一次直接申请 64 字节的内存剩下 44 字节的长度用来存储字符串这种字符串的存储方式也被称作 嵌入式字符串。