Redis数据库对象大概介绍

Redis 笔记 对象

对象的类型与编码

  • Redis使用对象来表示数据库中的键和值。每次在Redis的数据库中新常见键值对时,至少会创建两个对象,一个对象用作键值对的键,另一个用作键值对的值。
  • Redis中每个对象都会由一个redisObject结构表示:
    1
    2
    3
    4
    5
    6
    7
    typedef struct redisObject {
    unsigned type:4; //类型
    unsigned encoding:4; //编码
    unsigned lru:LRU_BITS; //对象最后一次被命令程序访问的时间
    int refcount; //引用计数
    void *ptr; //指向底层实现数据结构的指针
    } robj;

类型

  • 对象的type属性记录的对象的类型

    1
    2
    3
    4
    5
    #define OBJ_STRING 0        //字符串对象 string
    #define OBJ_LIST 1 //列表对象 list
    #define OBJ_SET 2 //哈希对象 hash
    #define OBJ_ZSET 3 //集合对象 set
    #define OBJ_HASH 4 //有序集合对象 zset
  • 对于redis数据库保存的键值对来说,键总是一个字符串对象,而值可以是字符串对象、列表对象、哈希对象、集合对象或者有序集合对象的其中一种。

  • TYPE命令返回的结果为数据库键队形的值对象的类型,而不是键对应的类型。

编码和底层实现

  • 对象的ptr指针指向对象的底层实现数据结构,数据结构由对象的encoding属性决定
  • 使用OBJECT ENCODING命令可以查看一个数据库键的值的编码。
  • 通过encoding属性设定对象所使用的编码,而不是为特定类型的对象关联一种固定的编码,极大的提高了Redis的灵活性和效率,因为Redis可以根据不同的使用场景来为一个对象设置不同的编码,从而优化在某个场景下的效率。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    #define OBJ_ENCODING_RAW 0     /* 简单动态字符串 Raw representation */
    #define OBJ_ENCODING_INT 1 /* long类型的整数 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 /* 双端链表 Encoded as regular linked list */
    #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 /* embstr编码的简单动态字符串
    Embedded sds string encoding */
    #define OBJ_ENCODING_QUICKLIST 9 /* Encoded as linked list of ziplists */

字符串对象

  • 字符串对象的编码可以是int,raw或者embstr
  • 如果一个字符串对象保存的是整数值,并且这个整数值可以用long类型来表示,那么字符串对象会将整数值保存在字符串结构的pr属相里面(将void*转换为long),并将字符串对象的编码设置为int。
  • 如果字符串对象保存的是一个字符串值,并且这个字符串值的长度大于32字节,那么字符串对象将使用一个简单动态字符串来保存这个字符串值,变鬼将对象的编码设置为raw
  • 如果字符串对象保存的是一个字符串值,并且这个字符串值的长度小于等于32字节,那么字符串数组将使用embstr编码的方式来保存这个字符串值。embstr是专门用来保存短字符串的一种优化编码方式,它和raw编码一样都是用redisObjct结构和sdshdr结构,而embstr编码则通过调用一次内存分配函数来得到一块连续的空间,空间中依次包含redisObjec和sdshdr两个结构。

    1
    2
    3
    4
    |-------------------------------|------------------|
    |---------redisObject-----------|-------sdshdr-----|
    | type | encoding | ptr | ... | free | len | buf |
    |-------------------------------|------------------|
  • 使用embstr编码的字符串对象保存短字符串的好处:

    1. 分配内存次数由两次降低为1次
    2. 释放内存次数有两次降低为1次
    3. 数据在一个连续的内存里面,可以更改好地利用缓存带来的优势。
  • 用long double类型表示的浮点数在Redis中做为字符串值来保存。保存一个浮点数到字符串对象里面,先把浮点数转化为字符串值,在保存转化的字符串。如果需要对保存的浮点数进行某种操作,先将其转化为浮点数,操作后再转化为字符串值,再保存到字符串对象中。

编码的转化

  • int编码和embstr编码的字符串对象在条件满足的条件下会被转化为raw编码的字符串对象。
  • embstr编码的字符串是只读的,对embstr编码的字符串进行任何修改命令时,程序先将编码从embstr转为raw,再执行修改命令。因此embstr编码的字符串对象在执行修改命令后,会变成一个raw编码的字符串对象。

    字符串命令的实现

  • 因为字符串键的值为字符串对象,所有用于字符串键的所有命令都是针对字符串对象来构建的。

列表对象

  • 列表对象的编码可以是ziplist或者linkedlist
  • ziplist编码的列表对象使用压缩列表做为底层实现,每个压缩列表节点保存一个列表元素。
  • linkedlist编码的列表对象使用双端链表做为底层实现,每个双端链表节点都保存了一个字符串对象,而每个字符串对象都保存了一个列表对象。
  • 字符串对象是Redis五种类型的对象中唯一一个会被其他四种类型对象嵌套的对象。

    编码转换

  • 当列表对象同时满足以下两个条件时,列表对象使用ziplist编码:
    1. 列表对象保存的所有字符串元素的长度都小于64字节;
    2. 列表对象保存的元素数量小于512个;不能满足这两个条件的列表对象需要使用linkedlist编码。
  • 对于使用ziplist编码的列表对象,以上两个条件任意一个不被满足就会进行编码转化。

    列表命令的实现

  • 因为列表键的值为列表对象,所以用于列表键的所有命令都是针对列表对象来构建的。

哈希对象

  • 哈希对象的编码可以是ziplist或者hashtable
  • ziplist编码的哈希对象使用压缩列表做为底层实现,每当有新的键值对要加入到哈希对象时,程序会先将保存了键的压缩列表节点推入到压缩列表表尾,然后再将保存了之的压缩列表节点推入到压缩列表表尾,因此:保存了同一键值对的两个节点总是紧挨在一起,保存键的节点在前,保存值的节点在后;先添加到哈希对象的键值对会被放在压缩列表的表头方向,而后面添加的哈希对象会被放在压缩列表的表尾方向。
  • hashtable编码的哈希对象使用字典做为底层实现,哈希对象中的每个键值对都使用一个字典键值对来保存:字典的每个键都是一个字符串对象,对象中保存了键值对的键;字典中的每个值都是一个字符串对象,对象中保存了键值对的值。

    编码转化

  • 当哈希对象同事满足以下两个条件时,哈希对象使用ziplist编码:
    1. 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节;
    2. 哈希对象保存的键值对数量小于512个; 不能满足这两个条件的哈希对象需要使用hashtable编码。
  • 对使用ziplist编码的哈希对象来说,当以上两个条件任意一个不满足时,对象的编码转换操作就会被执行。

    哈希命令的实现

  • 因为哈希键的值为哈希对象,所有用户哈希键的所有命令

集合对象

  • 集合对象的编码可以是intset或者hashtable
  • intset编码的集合对象使用整数集合做为底层实现,集合对象包含的所有元素都被保存在整数集合里面。
  • hashtable编码的集合对象使用字典做为底层实现,字典的每个键都是一个字符串对象,每个字符串对象包含了一个集合元素,而字典的值则全部被设置为NULL

    编码转换

  • 当集合对象同事满足以下两个条件时,对象使用intset编码:
    1. 集合对象保存的所有元素都是整数值
    2. 集合对象保存的额元素数量不超过512个。不满足这两个条件的集合对象需要使用hashtable编码
  • 对使用intset编码的集合对象来说,当以上两个条件任意一个不满足时,对象的编码转换操作就会被执行。

    集合命令的实现

  • 因为集合键的值为集合对象,所有用于集合键的所有命令都是针对集合对象来构建的。

有序集合对象

  • 有序集合的编码可以是ziplist或者skiplist
  • ziplist编码的压缩列表对象使用压缩列表做为底层实现,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员, 第二个元素保存元素的分值。压缩列表内的集合元素按分值从小到大进行排序,分值较小的元素被放置在靠近表头的方向,而分值较大的元素则被放置在靠近表尾的方向。
  • skiplist编码的有序集合对象使用zset结构做为底层实现,一个zset结构同事包含一个字典和一个跳跃表

    1
    2
    3
    4
    typedef struct zset {
    dict *dict;
    zskiplist *zsl;
    } zset;
  • zset结构中的zsl跳跃表按照分值从小到大保存了所有结合元素,每个跳跃表节点都保存了一个集合元素:跳跃表节点的object属性保存了元素的成员,而跳跃表的score属性则保存了元素的分值。通过跳跃表可以对有序集合进行范围型操作。

  • zset结构镇南关的dict字典为有序集合创建了一个从成员到分值的映射,字典中的每个键值对都保存了一个集合元素:字典的键保存了元素的成员,而字典的值则保存了元素的分值。通过字典。可以用O(1)复杂度查找给定成员的分值。
  • 有序集合每个元素的成员都是一个字符串对象,而每个元素的分值都是一个double类型的浮点数。虽然zset结构同事使用跳跃表和字典保存有序集合元素,但这两种数据结构都会通过指针共享相同元素的成员和分值,所以同时使用跳跃表和字典来保存集合元素不会产生任何重复成员或者分值,也不会浪费额外内存。

编码转换

  • 当有序集合对象同事满足以下两个条件时。对象使用ziplist编码:
    1. 有序集合保存的元素数量小雨128个;
    2. 有序集合保存的所有元素成员的长度小于64字节;
  • 不满足以上两个条件的有序集合对象使用skiplist编码。对于使用ziplist编码的有序集合对象,不满足以上任意一条时就会执行对象的编码转化操作。

    有序集合实现

  • 因为有序集合键的值为哈希对象,所以对于有序集合键的所有命令都是针对哈希对象来构建的。

类型检查与命令多态

  • Redis中用于操作键的命令基本分为两种类型。
    1. 对任何类型的键操作 如:del、expire、type等
    2. 对特定类型的键操作 如:set、sadd、hdel等

      类型检查的实现

  • 为了确保支队特定类型的键执行某些特定命令,在执行一个类型特定命令前,Redis会对键的类型进行检查。
  • 类型特定命令进行的类型检查是通过redisObject结构的type属性来实现的。在执行一个类型特定命令前,服务器先检查输入数据库键的值对象是否为执行命令所需的类型,如果是的话服务器就对键执行指点的命令,否则服务器拒绝执行命令,并向客户端返回类型错误。

    多态命令的实现

  • Redis会根据值对象的编码方式,选择正确的命令实现代码来执行命令。

内存回收

  • Redis构建了一个引用计数技术实现的内存回收机制,在适当的时候自动释放对象并进行内存回收。
  • 每个对象的引用计数信息由redisObject结构的refcount属性记录。
  • 对象引用计数信息随着对象的使用状态而不断变化:
    1. 创建一个新对象,引用计数初始化为1
    2. 被新引用,引用计数新增1
    3. 不再被引用, 引用计数减1
    4. 引用计数为0时,对象被释放

对象共享

  • Redis只会对包含整数值的字符串对象进行共享。因为要共享一个对象先要检查给定对象和新创建的对象是否完全相同,而一个对象保存的值约复杂。验证共享对象与目标对象相同所需的复杂度就越高,消耗的CPU时间也就越多。

对象的空转时长

  • redisObject结构lru属性记录了对象最后一次被命令程序访问的时间。
  • OBJECT IDLETIME命令可以打印出给定键的空转时长。空转时长是通过当前时间减去对象的lru时间计算得出的。
  • OBJECT IDLETIME命令不会修改值对象的lru属性。
  • 当服务器打开了maxmemory选项,并且服务器用于回收内存的算法为volatile-lru或者allkeys-lru,当服务器占用的内存超过maxmemory选项设置的上限值时,空转时长较高的那部分键会有限被服务器释放。