注意:本系列文章分析的 Redis 源码版本:github.com/Sidfate/red… ,是文章发布时间的最新版。git
前面在讲字典的时候咱们曾提到过整个 redis 的 db 中 key-value 结构也是一个 dict,dict 的 key 咱们知道是一个字符串 SDS(参照我以前的文章),那么 value 的话能够对应多种类型,为了统一管理 value 的多种类型,redis 提供了一个通用的对象结构 redisObject。github
redisObject 的源码结构以下:redis
typedef struct redisObject {
unsigned type:4;
unsigned encoding:4;
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). */
int refcount;
void *ptr;
} robj;
复制代码
属性 | 含义 |
---|---|
type | 对象的数据类型。占 4 个位。 |
encoding | 对象的编码。占 4 个位,它的全部取值在上面也给出了。 |
lru | 对象的空转时间。占 24 个位。 |
refcount | 引用计数。它容许 robj 对象在某些状况下被共享。 |
ptr | 数据指针,指向真正的数据。好比,一个表明 string 的 robj,它的 ptr 可能指向一个 sds 结构;一个表明 list 的 robj,它的 ptr 可能指向一个 quicklist。 |
数据类型 type 其实咱们以前有提到的 5 种经常使用结构,它的取值以下:算法
/* The actual Redis Object */
#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. */
复制代码
那么有了 type 为何还要 encoding 呢,这是由于一种 type 下可能存在多个 encoding 方式。就像咱们在 字符串章节中提到的当 type = OBJ_STRING 的时候,表示这个 robj 存储的是一个 string,这时 encoding 能够是下面3种中的一种:shell
经过 encoding 也能够看出来目前 redis 中针对数据类型使用的数据结构,本系列结构篇的源码分析也是从这个角度出发的。如下是 redis 源码中定义的 encoding,你们能够参照以前的文章对应一下 type 和 encoding 的关系 :服务器
/* Objects encoding. Some kind of objects like Strings and Hashes can be * internally represented in multiple ways. The 'encoding' field of the object * is set to one of this fields for this object. */
#define OBJ_ENCODING_RAW 0 /* Raw representation */
#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 */
#define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */
#define OBJ_ENCODING_STREAM 10 /* Encoded as a radix tree of listpacks */
复制代码
关于 lru 字段,我简单介绍下,就是用 LRU 算法算出来的对象的空闲时间,至于 LRU 算法是什么以及这个字段的具体使用请关注我以后的文章,敬请期待。数据结构
接下来我要重点说明下 refcount 引用计数。源码分析
若是你们看了以前的文章能够发现 redis 在内存使用上作了太多优化,而不少语言内部针对内存优化都会有自动的内存回收机制,可是 c 语言没有,因此 redis 经过对象的引用计数 refcount 管理本身实现了一套回收机制。简单来讲就是判断对象的 refcount 变化来判断是否是须要自动释放对象并进行内存回收。优化
对象的引用计数信息会随着对象的使用状态而不断变化:ui
关于 refcount 这个词其实已经在以前的文章中出现过了,不知道你们发现没,尝试下如下命令:
> set test_str "abc"
> debug object test_str
Value at:0x7fb47bc09150 refcount:1 encoding:embstr serializedlength:4 lru:3567105 lru_seconds_idle:3
> set test_str 100
OK
> debug object test_str
Value at:0x7fb47bd06320 refcount:2147483647 encoding:int serializedlength:2 lru:3567067 lru_seconds_idle:754
复制代码
有没有发现奇怪的地方?将 test_str 设置成 “abc” 时咱们看到的 refcount 是 1,由于咱们新建了这个对象,因此他的 refcount 初始化为 1。可是为何咱们从新将 test_str 设置成 100 后,refcount 的值忽然变成了 2147483647,这么大一个数字,缘由就在于 100 这个数字。
默认状况下, Redis 会在初始化服务器时,建立一万个字符串对象, 这些对象包含了从 0 到 9999 的全部整数值, 当服务器须要用到值为 0 到 9999 的字符串对象时, 服务器就会使用这些共享对象, 而不是新建立对象。
共享对象带来的就是不须要为这些小整数对象频繁的分配内存,并且不只能够用到字符串的值,也能用在 dict 中的值,等等。这些小整数对象的引用计数恒等于 INT_MAX,也就是咱们所看到的 2147483647。
为何 Redis 不共享包含字符串的对象?
试想一下,若是咱们为一个字符串 “abc” 也建立共享对象,那么若是我想用到它,我就须要在建立新字符串对象的时候比较值是否是与 “abc” 相等,最坏的状况是 O(N),那就会消耗必定的 cpu 时间,若是字符串共享多了就跟不用说了。而整数咱们只要比较大小就好了,复杂度只须要 O(1)。
虽然共享对象能够减小内存分配,方便管理,可是权衡下来,只共享了整数,何况很差肯定须要共享哪些字符串或者非数字对象有哪些。