关于redis:Redis原码阅读Object101

50次阅读

共计 3241 个字符,预计需要花费 9 分钟才能阅读完成。

对象

Redis 基于后面的那些数据结构,创立了一个对象零碎用来实现键值对数据库。

那么就造成了五大根底对象

  1. 字符串对象 String
  2. 列表对象 List
  3. 哈希对象 Hash
  4. 汇合对象 Set
  5. 有序汇合对象 ZSet

因为引入了对象,所有 Redis 实现了基于援用计数技术的内存回收机制,当程序不在应用某个对象的时候,对象所占用的内存就会被主动开释,另外还实现了对象共享机制,在某些条件下多个数据库能够共享一个对象来节约内存。

类型与编码

Redis 应用对象示意数据库中的键值,那么咱们创立一个键值对,咱们至多会创立两个对象

/*
 * Redis 对象
 */
#define REDIS_LRU_BITS 24
#define REDIS_LRU_CLOCK_MAX ((1<<REDIS_LRU_BITS)-1) /* Max value of obj->lru */
#define REDIS_LRU_CLOCK_RESOLUTION 1000 /* LRU clock resolution in ms */
typedef struct redisObject {

    // 类型
    unsigned type:4;

    // 编码
    unsigned encoding:4;

    // 对象最初一次被拜访的工夫
    unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */

    // 援用计数
    int refcount;

    // 指向理论值的指针
    void *ptr;

} robj;

对象的 ptr 指针指向对象的底层实现数据结构,它是由 encoding 决定的

TYPE 键名 能够查看键对应的值对象类型
set msg "hello"
type msg    string

rpush numbers 1 3 5
type numbers   list

字符串对象

字符串对象的编码能够是 int,raw,embstr

如果一个字符串对象保留的是整数值没并且这个整数值能够用 long 示意,那么字符串对象会将整数值保留在字符串对象构造的 ptr 里(将 void* 转换为 long)并且将字符串编码设置为 int

如果字符串对象保留一个字符串值,并且字符串长度大于 32 字节,那么应用的 SDS,编码设置为 raw

如果字符串对象保留一个字符串值,并且字符串长度小于 32 字节,编码设置为 embstr

embstr 是专门保留短字符串的一种优化编码,它和 raw 一样都是应用 redisObject 构造和 sdshdr 构造来示意字符串对象,过后 raw 会调用两次内存调配函数来别离创立 redisObject 和 sdshdr,而 embstr 会通过一次内存调配来创立一块间断的空间,外面放 redisObject 和 sdshdr 成果时一样的只是过程简略了。

那么 embstr 的劣势是

  1. 创建对象只须要调配一次内存
  2. 开释对象也只须要调用一次开释函数
  3. embstr 调配的内存是间断的

如果你要存一个浮点数

那么程序会先将浮点数转换为字符串,而后保留的是字符串的值

编码转换

int 和 embstr 对象在某些条件下会被转换为 raw

对应 int,如果咱们向对象执行了一些命令,使得之歌对象保留的不再是整数,而是一个字符串,那么 int 会变为 raw

比方向一个整数追加一个字符串

set number 10086
append number "hello"
object encoding number
raw

redis 并没有定义对 embstr 对象的 API,所有其实上 embstr 字符串是只读的,当咱们理论对 embstr 字符串操作时,就会主动变 raw

列表对象

编码能够时 ziplist 或者 linkedlist

编码转换

什么时候应用 ziplist 同时满足

  1. 列表对象保留的所有字符串元素长度都小于 64 字节
  2. 列表对象保留的元素数量小于 512,

不满足这两个就用 linkedlist

哈希对象

编码能够是 ziplist 或者 hashtable

1、如果是 ziplist 会先 push 入键再 push 入值 都是在表尾 他们总是挨在一起的

2、如果是 hashtable,那么 hash 对象中的每个键值对都是一个字典键值对保留的,

字典中键和值都是一个字符串对象

什么时候应用 ziplist 同时满足才行

  1. 哈希对象保留的所有键和值的字符串元素长度都小于 64 字节
  2. 哈希对象保留的元素数量小于 512,

不满足这两个就用 hashtable

汇合对象

汇合对象能够是 intset 或者 hashtable

如果汇合里的值都是整数就用 intset

什么时候应用 intset 同时满足才行

  1. 汇合对象保留的所有元素都是整数
  2. 汇合对象保留的元素数量小于 512,

有序汇合

ziplist 或者 skiplist

1、ziplist,每个汇合元素应用两个紧挨着的压缩列表节点保留,第一节点保留成员,第二个元素保留分值

2、skiplist 应用的是一个字典和跳跃表,跳跃表按分值从小到大保留所有汇合元素,每个跳跃表节点保留一个汇合元素,dict 字典为有序汇合创立了一个从成员到分值的映射,字典键保留元素成员,字典值保留元素分值。

/*
 * 有序汇合
 */
typedef struct zset {

    // 字典,键为成员,值为分值
    // 用于反对 O(1) 复杂度的按成员取分值操作
    dict *dict;

    // 跳跃表,按分值排序成员
    // 用于反对均匀复杂度为 O(log N) 的按分值定位成员操作
    // 以及范畴操作
    zskiplist *zsl;

} zset;

什么时候用 ziplist 同时满足

  1. 有序汇合保留的元素数量小于 128 个
  2. 有序汇合保留的所有元素成员的长度都小于 64 个字节

类型检查和命令多态

Redis 中用于操作键的命令能够分为两种

1、能够对任意类型的键执行,DEL,TYPE ,OBJECT 等

2、对象的专用命令,set(字符串),hset(哈希),rpush(列表),sadd(汇合),zadd(有序汇合)。

那么问题就来了,在执行一个类型特定命令之前,redis 必须要先查看类型,而后再决定是否执行

多态命令的实现

redis 还会依据值对象的编码没抉择正确的命令去执行。

 当初,思考这样一个状况,如果咱们对一个键执行 LLEN 命令,那么服务器除了要确保执行命令的是列表键之外,还须要依据键的值对象所应用的编码来抉择正确的 LLEN 命令实现:

如果列表对象的编码为 ziplist,那么阐明列表对象的实现为压缩列表,程序将使
用 ziplistLen 函数来返回列表的长度;

如果列表对象的编码为 linkedlist,那么阐明列表对象的实现为双端链表,程序将应用 listLength 函数来返回双端链表的长度;

内存回收

总所周知 C 不没有垃圾回收器的,redis 用一个援用技术技术实现的内存回收,程序跟踪对象的援用技术信息,再适合的时候主动开释内存

对象的援用计数信息会随着对象的应用状态而一直变动:

  • 在创立一个新对象时,援用计数的值会被初始化为 1;
  • 当对象被一个新程序应用时,它的援用计数值会被增一;
  • 当对象不再被一个程序应用时,它的援用计数值会被减一;
  • 当对象的援用计数值变为 0 时,对象所占用的内存会被开释。

对象共享

援用计数属性还带来了对象共享的作用,因为当对象被一个新程序应用时,它的援用计数值会被增一。

redis 让多个键共享一个值对象

  1. 将数据库键的值指针指向一个现有的值对象
  2. 将被共享的值对象的援用计数加一

留神

Rdis 再初始化服务器时,创立了一万个字符串对象,这些对象蕴含了 0 到 9999 的所有整数值,当须要用到时服务器就会应用共享对象,而不是新创建对象。

实践上所有对象都时能够共享的,过后再共享之前须要确认两个须要共享的元素是不是类型雷同,时很好 cup 工夫的,所有 redis 只蕴含了整数和字符串对象的共享。

对象的空转时长

// 对象最初一次被拜访的工夫
    unsigned lru:REDIS_LRU_BITS; /* lru time (relative to server.lruclock) */
object tdletime 键 能够打印 lru

如果服务器开启了 maxmemory 选项,并且内存回收算法是 volatile-iru 或者 allkeys-lru,那么服务器内存占用超过 maxmemory 时,空转工夫长的会先被开释回收。

正文完
 0