Redis设计了多种数据结构,并以此为基础构建了多种对象,每种对象(除了新出的 stream 以外)都有超过一种的实现。

redisObject 这个结构体反应了 Redis 对象的内存布局

typedef struct redisObject {
unsigned type:;//对象类型 4bit
unsigned encoding:;//底层数据结构 4 bit
unsigned lru:LRU_BITS; /* LRU time (relative to global lru_clock) or
* LFU data (least significant 8 bits frequency
* and most significant 16 bits access time). */ // 24 bit
int refcount; // 4 byte
void *ptr;//指向数据结构的指针 // 8 byte
} robj;

可以看出,robj 用4个 bit 存储对象类型,4个 bit 存储对象的底层数据结构

以及 robj 的固定大小为 16 byte

其中对象类型有下面几种:

#define OBJ_STRING 0    /* String object. *///字符串类型
#define OBJ_LIST 1 /* List object. *///列表类型
#define OBJ_SET 2 /* Set object. *///集合对象
#define OBJ_ZSET 3 /* Sorted set object. *///有序集合对象
#define OBJ_HASH 4 /* Hash object. *///哈希对象
#define OBJ_MODULE 5 /* Module object. *///模块对象
#define OBJ_STREAM 6 /* Stream object. *///流对象,redis 5中新增

数据结构有下面几种:

#define OBJ_ENCODING_RAW 0     /* Raw representation *///基本 sds
#define OBJ_ENCODING_INT 1 /* Encoded as integer *///整数表示的字符串
#define OBJ_ENCODING_HT 2 /* Encoded as hash table *///字典
#define OBJ_ENCODING_ZIPMAP 3 /* Encoded as zipmap */ //废弃
#define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. *//废弃
#define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist *///压缩列表
#define OBJ_ENCODING_INTSET 6 /* Encoded as intset *///整数集合
#define OBJ_ENCODING_SKIPLIST 7 /* Encoded as skiplist *///跳跃表
#define OBJ_ENCODING_EMBSTR 8 /* Embedded sds string encoding *///embstr
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */

其实观察 objectComputeSize 这个方法就看出对象与数据结构的关联关系

OBJ_STRING = OBJ_ENCODING_RAW + OBJ_ENCODING_INT + OBJ_ENCODING_EMBSTR

OBJ_LIST = OBJ_ENCODING_QUICKLIST + OBJ_ENCODING_ZIPLIST

OBJ_SET = OBJ_ENCODING_INTSET + OBJ_ENCODING_HT

OBJ_ZSET = OBJ_ENCODING_SKIPLIST + OBJ_ENCODING_ZIPLIST

OBJ_HASH = OBJ_ENCODING_HT + OBJ_ENCODING_ZIPLIST

OBJ_STREAM = OBJ_ENCODING_STREAM

为什么要设置这么复杂的对象系统呢,主要还是为了压缩内存。

以最最常见的字符串对象为例,它对应的数据结构是最多的,有三种,其目的在一个名为 tryObjectEncoding 的函数中可见一斑:

//尝试压缩 string
//1. 检查是否可以直接用 INT 存储,最好能用 shared.integers 来存
//2. 检查是否可以用 embstr 来存储
//3. 如果 sds 有1/10的空间空闲,则压缩空闲空间
/* Try to encode a string object in order to save space */
robj *tryObjectEncoding(robj *o) {
long value;
sds s = o->ptr;
size_t len; ...... /* Check if we can represent this string as a long integer.
* Note that we are sure that a string larger than 20 chars is not
* representable as a 32 nor 64 bit integer. */
len = sdslen(s);
if (len <= && string2l(s,len,&value)) {//检查是否为长度<=20的整数
/* This object is encodable as a long. Try to use a shared object.
* Note that we avoid using shared integers when maxmemory is used
* because every object needs to have a private LRU field for the LRU
* algorithm to work well. */
//检查 value 是否落在 [0, OBJ_SHARED_INTEGERS)这个区间里
if ((server.maxmemory == ||
!(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&
value >= &&
value < OBJ_SHARED_INTEGERS)
{
decrRefCount(o);
incrRefCount(shared.integers[value]);
return shared.integers[value];
} else {
if (o->encoding == OBJ_ENCODING_RAW) sdsfree(o->ptr);
o->encoding = OBJ_ENCODING_INT;
o->ptr = (void*) value;
return o;
}
} /* If the string is small and is still RAW encoded,
* try the EMBSTR encoding which is more efficient.
* In this representation the object and the SDS string are allocated
* in the same chunk of memory to save space and cache misses. */
//是否可以用 embstr 来存储:检查string 的长度是否 <= 44
if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) {
robj *emb; if (o->encoding == OBJ_ENCODING_EMBSTR) return o;
emb = createEmbeddedStringObject(s,sdslen(s));
decrRefCount(o);
return emb;
} /* We can't encode the object...
*
* Do the last try, and at least optimize the SDS string inside
* the string object to require little space, in case there
* is more than 10% of free space at the end of the SDS string.
*
* We do that only for relatively large strings as this branch
* is only entered if the length of the string is greater than
* OBJ_ENCODING_EMBSTR_SIZE_LIMIT. */
//尝试压缩 sds 的空间
if (o->encoding == OBJ_ENCODING_RAW &&
sdsavail(s) > len/)
{
o->ptr = sdsRemoveFreeSpace(o->ptr);
} /* Return the original object. */
return o;
}

可以看出 Redis 对内存的使用是非常克制的。

分析一个很有意思的细节:为什么 embstr 与 raw sds 的分界线在 44 这个长度呢?
看一下sdshdr8这个结构体

struct __attribute__ ((__packed__)) sdshdr8 {
uint8_t len; /* used */ // 1 byte
uint8_t alloc; /* excluding the header and null terminator */ // 1 byte
unsigned char flags; /* 3 lsb of type, 5 unused bits */ // 1 byte
char buf[];
};

可以看出len + alloc + flags = 3 byte

然后Redis 会默认在存储的字符串尾部加一个 '\0',这个也会占据一个1 byte 的空间

也就是说一个 sdshdr8 除去内容以外至少要占 4个 byte 的空间

再加上 robj 头的大小 16 byte,那就是20 byte

而 jemalloc 会固定分配8/16/32/64 等大小的内存, 所以以 44 为embstr 与 raw sds 的分界线,是有深意的(是否可以再细一点,将 12 作为另外一种更小的字符串的分界线呢?)

更有趣的是,如果往前翻几个版本,可以发现这个分界线是在 39 byte,这是因为老版本的 sds 只有一种:

struct sdshdr {
unsigned int len;//4 byte
unsigned int free;//4 byte
char buf[];
};

可以看出sdshdr 的固定开销是4+4+1 = 9 byte,再加上 robj 的16byte就是25byte,所以分界线就只能定为39byte 了

新版本的sdshdr8 与之相比,硬是抠出了5个 byte 的空间,真的非常了不起

Redis 源码走读(二)对象系统的更多相关文章

  1. jdk源码剖析二: 对象内存布局、synchronized终极原理

    很多人一提到锁,自然第一个想到了synchronized,但一直不懂源码实现,现特地追踪到C++层来剥开synchronized的面纱. 网上的很多描述大都不全,让人看了不够爽,看完本章,你将彻底了解 ...

  2. redis源码分析(二)-rio(读写抽象层)

    Redis io抽象层 Redis中涉及到多种io,如socket与file,为了统一对它们的操作,redis设计了一个抽象层,即rio,使用rio可以实现将数据写入到不同的底层io,但是接口相同.r ...

  3. Redis 源码走读(一)事件驱动机制与命令处理

    eventloop 从 server.c 的 main 方法看起 int main(int argc, char **argv) { ....... aeSetBeforeSleepProc(serv ...

  4. Redis源码漂流记(二)-搭建Redis调试环境

    Redis源码漂流记(二)-搭建Redis调试环境 一.目标 搭建Redis调试环境 简要理解Redis命令运转流程 二.前提 1.有一些c知识简单基础(变量命名.常用数据类型.指针等) 可以参考这篇 ...

  5. Redis源码阅读(二)高可用设计——复制

    Redis源码阅读(二)高可用设计-复制 复制的概念:Redis的复制简单理解就是一个Redis服务器从另一台Redis服务器复制所有的Redis数据库数据,能保持两台Redis服务器的数据库数据一致 ...

  6. Redis源码剖析

    Redis源码剖析和注释(一)---链表结构 Redis源码剖析和注释(二)--- 简单动态字符串 Redis源码剖析和注释(三)--- Redis 字典结构 Redis源码剖析和注释(四)--- 跳 ...

  7. spring-data-redis-cache 使用及源码走读

    预期读者 准备使用 spring 的 data-redis-cache 的同学 了解 @CacheConfig,@Cacheable,@CachePut,@CacheEvict,@Caching 的使 ...

  8. redis源码笔记(一) —— 从redis的启动到command的分发

    本作品采用知识共享署名 4.0 国际许可协议进行许可.转载联系作者并保留声明头部与原文链接https://luzeshu.com/blog/redis1 本博客同步在http://www.cnblog ...

  9. Redis源码学习:字符串

    Redis源码学习:字符串 1.初识SDS 1.1 SDS定义 Redis定义了一个叫做sdshdr(SDS or simple dynamic string)的数据结构.SDS不仅用于 保存字符串, ...

随机推荐

  1. Socket常见错误代码与描述

    最近程序 出现 几次 Socket 错误, 为了便于 差错.. 搜了一些 贴在这里.. 出现网络联机错误Socket error #11001表示您的计算机无法连上服务器,请检查您的Proxy设定以及 ...

  2. Luogu3952 NOIP2017时间复杂度

    搞一个栈模拟即可.对比一下和一年前考场上的代码233 //2018.11.8 #include<iostream> #include<cstdio> #include<c ...

  3. hdu 1853 Cyclic Tour (二分匹配KM最小权值 或 最小费用最大流)

    Cyclic Tour Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/65535 K (Java/Others)Total ...

  4. JSP语法,运行机理等

    JSP是几年前就接触了,但是用归用,很多实际的意义含义等还是不太明白,借此机会,梳理一下. 1.JSP运行原理:当浏览器web应用服务器请求一个JSP页面时,Web应用服务器将其转换成一个Servle ...

  5. [CF1019A]Elections

    题目大意:有$n$个人,$m$个政党,每个人都想投一个政党,但可以用一定的钱让他选你想让他选的政党. 现在要$1$号政党获胜,获胜的条件是:票数严格大于其他所有政党.求最小代价 题解:暴力枚举其他政党 ...

  6. HDU 多校对抗赛 A Maximum Multiple

    Maximum Multiple Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others) ...

  7. java常用的时间格式

    年月日时分秒毫秒:yyyyMMddHHmmssSSS    毫秒用SSS表示.

  8. Install the Active Directory Administration Tools on Windows Server

    安装 Active Directory 管理工具 To manage your directory from an EC2 Windows instance, you need to install ...

  9. 通过设置nginx的client_max_body_size解决nginx+php上传大文件的问题

    通过设置nginx的client_max_body_size解决nginx+php上传大文件的问题:用nginx来做webserver的时,上传大文件时需要特别注意client_max_body_si ...

  10. bzoj3382 [Usaco2004 Open]Cave Cows 3 洞穴里的牛之三

    传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=3382 [题解] 套路题. 首先我们会发现曼哈顿距离不好处理,难道要写kdtree??? (k ...