Redis的对象类型和编码

22 阅读4分钟

对象

Redis中的每个对象都由一个 redisObject 结构表示, 该结构中和保存数据有关的三个属性分别是 type 属性、encoding 属性和ptr 属性:

typedef struct redisObject {

    // 类型
    unsigned type:4;

    // 编码
    unsigned encoding:4;

    // 指向底层实现数据结构的指针
    void *ptr;

    // ...

} robj;

类型

对象中的type属性记录了对象的类型

类型常量对象的名称
REDIS_STRING字符串对象
REDIS_LIST列表对象
REDIS_HASH哈希对象
REDIS_SET集合对象
REDIS_ZSET有序集合对象

对于Redis数据库保存的键值对来说, 总是一个字符串对象, 而值则可以是字符串对象列表对象哈希对象集合对象或者有序集合对象的其中一种, 因此:

  • 当我们称呼一个数据库键为“字符串键”时, 我们指的是“这个数据库键所对应的值为字符串对象”;
  • 当我们称呼一个键为“列表键”时, 我们指的是“这个数据库键所对应的值为列表对象”

类型检查的实现

Redis中用于操作键的命令基本上可以分为两种类型:

  1. 命令对于任何类型的键都可以执行,比如delexpire
  2. 另一种只能对特定类型的键执行

为了确保只有指定类型的键可以执行某些特定的命令, 在执行一个类型特定的命令之前, Redis会先检查输入键的类型是否正确, 然后再决定是否执行给定的命令。

类型特定命令所进行的类型检查是通过 redisObject 结构的 type 属性来实现的:

  • 在执行一个类型特定命令之前, 服务器会先检查输入数据库键的值对象是否为执行命令所需的类型, 如果是的话, 服务器就对键执行指定的命令;
  • 否则, 服务器将拒绝执行命令, 并向客户端返回一个类型错误。

image.png

编码和底层实现

对象的 ptr 指针指向对象的底层实现数据结构, 而这些数据结构由对象的 encoding 属性决定。

encoding 属性记录了对象所使用的编码, 也就是说这个对象使用了什么数据结构作为对象的底层实现。

编码常量编码所对应的底层数据结构
REDIS_ENCODING_INTlong 类型的整数
REDIS_ENCODING_EMBSTRembstr 编码的简单动态字符串
REDIS_ENCODING_RAW简单动态字符串
REDIS_ENCODING_HT字典
REDIS_ENCODING_LINKEDLIST双端链表
REDIS_ENCODING_ZIPLIST压缩列表
REDIS_ENCODING_INTSET整数集合
REDIS_ENCODING_SKIPLIST跳跃表和字典

每种类型的对象都至少使用了两种不同的编码

类型编码对象
REDIS_STRINGREDIS_ENCODING_INT使用整数值实现的字符串对象。
REDIS_STRINGREDIS_ENCODING_EMBSTR使用 embstr 编码的简单动态字符串实现的字符串对象。
REDIS_STRINGREDIS_ENCODING_RAW使用简单动态字符串实现的字符串对象。
REDIS_LISTREDIS_ENCODING_ZIPLIST使用压缩列表实现的列表对象。
REDIS_LISTREDIS_ENCODING_LINKEDLIST使用双端链表实现的列表对象。
REDIS_HASHREDIS_ENCODING_ZIPLIST使用压缩列表实现的哈希对象。
REDIS_HASHREDIS_ENCODING_HT使用字典实现的哈希对象。
REDIS_SETREDIS_ENCODING_INTSET使用整数集合实现的集合对象。
REDIS_SETREDIS_ENCODING_HT使用字典实现的集合对象。
REDIS_ZSETREDIS_ENCODING_ZIPLIST使用压缩列表实现的有序集合对象。
REDIS_ZSETREDIS_ENCODING_SKIPLIST使用跳跃表和字典实现的有序集合对象。

通过 encoding 属性来设定对象所使用的编码, 而不是为特定类型的对象关联一种固定的编码, 极大地提升了Redis灵活性和效率, 因为Redis可以根据不同的使用场景来为一个对象设置不同的编码, 从而优化对象在某一场景下的效率。

多态命令的实现

Redis除了会根据值对象的类型来判断键是否能够执行指定命令之外, 还会根据值对象的编码方式, 选择正确的命令实现代码来执行命令。

考虑这样一个情况, 如果我们对一个键执行LLEN命令, 那么服务器除了要确保执行命令的是列表键之外, 还需要根据键的值对象所使用的编码来选择正确的LLEN命令实现:

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

借用面向对象方面的术语来说, 我们可以认为LLEN命令是多态的: 只要执行LLEN命令的是列表键, 那么无论值对象使用的是 ziplist 编码还是 linkedlist 编码, 命令都可以正常执行。

image.png