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

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

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

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

以及 robj 的固定大小为 16 byte

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

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

数据结构有下面几种:

  1. #define OBJ_ENCODING_RAW 0 /* Raw representation *///基本 sds
  2. #define OBJ_ENCODING_INT 1 /* Encoded as integer *///整数表示的字符串
  3. #define OBJ_ENCODING_HT 2 /* Encoded as hash table *///字典
  4. #define OBJ_ENCODING_ZIPMAP 3 /* Encoded as zipmap */ //废弃
  5. #define OBJ_ENCODING_LINKEDLIST 4 /* No longer used: old list encoding. *//废弃
  6. #define OBJ_ENCODING_ZIPLIST 5 /* Encoded as ziplist *///压缩列表
  7. #define OBJ_ENCODING_INTSET 6 /* Encoded as intset *///整数集合
  8. #define OBJ_ENCODING_SKIPLIST 7 /* Encoded as skiplist *///跳跃表
  9. #define OBJ_ENCODING_EMBSTR 8 /* Embedded sds string encoding *///embstr
  10. #define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
  11. #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 的函数中可见一斑:

  1. //尝试压缩 string
  2. //1. 检查是否可以直接用 INT 存储,最好能用 shared.integers 来存
  3. //2. 检查是否可以用 embstr 来存储
  4. //3. 如果 sds 有1/10的空间空闲,则压缩空闲空间
  5. /* Try to encode a string object in order to save space */
  6. robj *tryObjectEncoding(robj *o) {
  7. long value;
  8. sds s = o->ptr;
  9. size_t len;
  10.  
  11. ......
  12.  
  13. /* Check if we can represent this string as a long integer.
  14. * Note that we are sure that a string larger than 20 chars is not
  15. * representable as a 32 nor 64 bit integer. */
  16. len = sdslen(s);
  17. if (len <= && string2l(s,len,&value)) {//检查是否为长度<=20的整数
  18. /* This object is encodable as a long. Try to use a shared object.
  19. * Note that we avoid using shared integers when maxmemory is used
  20. * because every object needs to have a private LRU field for the LRU
  21. * algorithm to work well. */
  22. //检查 value 是否落在 [0, OBJ_SHARED_INTEGERS)这个区间里
  23. if ((server.maxmemory == ||
  24. !(server.maxmemory_policy & MAXMEMORY_FLAG_NO_SHARED_INTEGERS)) &&
  25. value >= &&
  26. value < OBJ_SHARED_INTEGERS)
  27. {
  28. decrRefCount(o);
  29. incrRefCount(shared.integers[value]);
  30. return shared.integers[value];
  31. } else {
  32. if (o->encoding == OBJ_ENCODING_RAW) sdsfree(o->ptr);
  33. o->encoding = OBJ_ENCODING_INT;
  34. o->ptr = (void*) value;
  35. return o;
  36. }
  37. }
  38.  
  39. /* If the string is small and is still RAW encoded,
  40. * try the EMBSTR encoding which is more efficient.
  41. * In this representation the object and the SDS string are allocated
  42. * in the same chunk of memory to save space and cache misses. */
  43. //是否可以用 embstr 来存储:检查string 的长度是否 <= 44
  44. if (len <= OBJ_ENCODING_EMBSTR_SIZE_LIMIT) {
  45. robj *emb;
  46.  
  47. if (o->encoding == OBJ_ENCODING_EMBSTR) return o;
  48. emb = createEmbeddedStringObject(s,sdslen(s));
  49. decrRefCount(o);
  50. return emb;
  51. }
  52.  
  53. /* We can't encode the object...
  54. *
  55. * Do the last try, and at least optimize the SDS string inside
  56. * the string object to require little space, in case there
  57. * is more than 10% of free space at the end of the SDS string.
  58. *
  59. * We do that only for relatively large strings as this branch
  60. * is only entered if the length of the string is greater than
  61. * OBJ_ENCODING_EMBSTR_SIZE_LIMIT. */
  62. //尝试压缩 sds 的空间
  63. if (o->encoding == OBJ_ENCODING_RAW &&
  64. sdsavail(s) > len/)
  65. {
  66. o->ptr = sdsRemoveFreeSpace(o->ptr);
  67. }
  68.  
  69. /* Return the original object. */
  70. return o;
  71. }

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

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

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

可以看出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 只有一种:

  1. struct sdshdr {
  2. unsigned int len;//4 byte
  3. unsigned int free;//4 byte
  4. char buf[];
  5. };

可以看出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. QueryHelper插件类(hql)

    package cn.itcast.core.util; import java.util.ArrayList; import java.util.List; public class QueryHe ...

  2. [POJ 1204]Word Puzzles(Trie树暴搜&amp;AC自己主动机)

    Description Word puzzles are usually simple and very entertaining for all ages. They are so entertai ...

  3. vector 基础

    http://classfoo.com/ccby/article/jnevK Vector的存储空间是连续的,list不是连续存储的 vector初始化 vector<int>v; //不 ...

  4. 如何使用Eclipse调试framework

    1.下载Eclipse EE(下载地址:http://www.eclipse.org/downloads/) 2.下载并安装JDK(下载地址:http://www.oracle.com/technet ...

  5. POJ1308:Is It A Tree?(并查集)

    Is It A Tree? 题目链接:http://poj.org/problem?id=1308 Description: A tree is a well-known data structure ...

  6. Idea 怎么远程debug

    注意的问题:远程debug别人的服务器只能开一个debug,所以当你的同事比你先远程debug同一台服务器时,你应该报Error running 某某ip地址 .unable to open debu ...

  7. <video>标签的特性

    以前的网页视频 过去还没有HTML5的时候,我们处理网页视频的时候,都是通过Flash插件来实现的,然而,并非所有浏览器都有同样的插件. 现在有了HTML5带来的video元素,开发者能够很方便地将视 ...

  8. python最简单发送邮件

    #!/usr/bin/env python #coding:utf8 #Author:lsp #Date:下午5:51:13 #Version:0.1 #Function: #导入smtplib和MI ...

  9. iBatis之Iterator的使用

    一:前言 现在这个项目使用的是iBatis,我刚刚开始的时候说是用MyBatis,因为我以前用过,觉得还是比较好用的啊,而且不像iBatis样,查什么一个字段不能多也不能少,觉得好无语啊. 二:内容 ...

  10. Spring - IoC(4): p-namespace & c-namespace

    p 命名空间 p 命名空间允许你使用 bean 元素的属性而不是 <property/>子元素来描述 Bean 实例的属性值.从 Spring2.0 开始,Spring 支持基于 XML ...