redis没有直接使用数据结构来实现键值对数据库,而是基于这些数据结构创建了一个对象系统,这个系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型的对象。

1、字符串对象

1.1 字符串对象编码

int类型

        如果一个字符串对象保存的是整数值,并且这个整数值可以用long类型来表示。那么字符串对象会将整数值保存字符串对象结构的ptr属性里面(将void *转换成long),并且将字符串对象的编码设置为int。

raw类型

       如果一个字符串对象保存的是一个字符串值,并且这个字符串值的长度大于39字节,那么字符串对象将使用一个简单动态字符串(SDS)来保存这个字符串值,并且将字符串对象的编码设置为raw。

embstr类型

        如果一个字符串对象保存的是一个字符串值,并且这个字符串值的长度小于等于39字节,那么字符串对象将使用embstr编码的方式来保存这个字符串值。

        long double 类型表示的浮点数在redis中也是作为字符串值来保存的。保存时,程序会先将这个浮点数转换成字符串值,然后再保存转换得到的字符串值。使用时,程序会将保存在字符串对象里面的字符串值转换回浮点数值,执行某些操作,然后再将执行操作所得到的浮点数值转换回字符串值,并继续保存在字符串对象里面。

1.2 编码转换

        int编码的字符串对象和embstr编码的字符串对象在条件满足的情况下,会被转换为raw编码的字符串对象。

        因为redis没有为embstr编码的字符串对象编写任何相应的修改程序,所以embstr编码的字符串对象实际上是只读的。当我们对embstr编码的字符串对象执行任何修改命令时,程序会先将对象的编码从embstr转换为raw,然后再执行修改指令。因为这个原因,embstr编码的字符串对象,在执行修改命令之后,总会变成一个raw编码的字符串对象。

1.3 字符串类型命令



命令

int 编码的实现方法

embstr 编码的实现方法

raw 编码的实现方法

set

使用 int 编码保存值。

使用 embstr 编码保存值。

使用 raw 编码保存值。

get

拷贝对象所保存的整数值,将这个拷贝转换成字符串值,然后向客户端返回这个字符串值。

直接向客户端返回字符串值。

直接向客户端返回字符串值。

append

将对象转换成 raw 编码,然后按 raw编码的方式执行此操作。

将对象转换成 raw 编码,然后按 raw编码的方式执行此操作。

调用 sdscatlen 函数,将给定字符串追加到现有字符串的末尾。

incrbyfloat

取出整数值并将其转换成 longdouble 类型的浮点数,对这个浮点数进行加法计算,然后将得出的浮点数结果保存起来。

取出字符串值并尝试将其转换成long double 类型的浮点数,对这个浮点数进行加法计算,然后将得出的浮点数结果保存起来。如果字符串值不能被转换成浮点数,那么向客户端返回一个错误。

取出字符串值并尝试将其转换成 longdouble 类型的浮点数,对这个浮点数进行加法计算,然后将得出的浮点数结果保存起来。 如果字符串值不能被转换成浮点数,那么向客户端返回一个错误。

incrby

对整数值进行加法计算,得出的计算结果会作为整数被保存起来。

embstr 编码不能执行此命令,向客户端返回一个错误。

raw 编码不能执行此命令,向客户端返回一个错误。

decrby

对整数值进行减法计算,得出的计算结果会作为整数被保存起来。

embstr 编码不能执行此命令,向客户端返回一个错误。

raw 编码不能执行此命令,向客户端返回一个错误。

strlen

拷贝对象所保存的整数值,将这个拷贝转换成字符串值,计算并返回这个字符串值的长度。

调用 sdslen 函数,返回字符串的长度。

调用 sdslen 函数,返回字符串的长度。

setrange

将对象转换成 raw 编码,然后按 raw编码的方式执行此命令。

将对象转换成 raw 编码,然后按 raw编码的方式执行此命令。

将字符串特定索引上的值设置为给定的字符。

getrange

拷贝对象所保存的整数值,将这个拷贝转换成字符串值,然后取出并返回字符串指定索引上的字符。

直接取出并返回字符串指定索引上的字符。

直接取出并返回字符串指定索引上的字符。



2、列表对象

2.1 列表对象编码

    ziplist编码的列表对象使用压缩列表作为底层实现,每个压缩列表节点(entry)保存了一个列表元素。

    linkedlist编码的列表对象使用双端链表作为底层实现,每个双端链表节点都保存了一个字符串对象,而每个字符串对象保存了一个列表元素。

2.2 编码转换

    当列表对象可以同时满足一下两个条件时,列表对象使用ziplist编码(不能满足这两个条件的列表对象使用linkedlist对象编码):

    列表对象保存的所有字符串元素的长度都小于64字节;

    列表对象保存的元素数量小于512个;

2.3列表类型命令

命令

ziplist 编码的实现方法

linkedlist 编码的实现方法

lpush

调用 ziplistPush 函数,将新元素推入到压缩列表的表头。

调用 listAddNodeHead 函数,将新元素推入到双端链表的表头。

rpush

调用 ziplistPush 函数,将新元素推入到压缩列表的表尾。

调用 listAddNodeTail 函数,将新元素推入到双端链表的表尾。

lpop

调用 ziplistIndex 函数定位压缩列表的表头节点,在向用户返回节点所保存的元素之后,调用 ziplistDelete 函数删除表头节点。

调用 listFirst 函数定位双端链表的表头节点,在向用户返回节点所保存的元素之后,调用 listDelNode 函数删除表头节点。

rpop

调用 ziplistIndex 函数定位压缩列表的表尾节点,在向用户返回节点所保存的元素之后,调用 ziplistDelete 函数删除表尾节点。

调用 listLast 函数定位双端链表的表尾节点,在向用户返回节点所保存的元素之后,调用 listDelNode 函数删除表尾节点。

lindex

调用 ziplistIndex 函数定位压缩列表中的指定节点,然后返回节点所保存的元素。

调用 listIndex 函数定位双端链表中的指定节点,然后返回节点所保存的元素。

llen

调用 ziplistLen 函数返回压缩列表的长度。

调用 listLength 函数返回双端链表的长度。

linsert

插入新节点到压缩列表的表头或者表尾时,使用 ziplistPush 函数; 插入新节点到压缩列表的其他位置时,使用 ziplistInsert 函数。

调用 listInsertNode 函数,将新节点插入到双端链表的指定位置。

lrem

遍历压缩列表节点,并调用 ziplistDelete 函数删除包含了给定元素的节点。

遍历双端链表节点,并调用 listDelNode 函数删除包含了给定元素的节点。

ltrim

调用 ziplistDeleteRange 函数,删除压缩列表中所有不在指定索引范围内的节点。

遍历双端链表节点,并调用 listDelNode 函数删除链表中所有不在指定索引范围内的节点。

lset

调用 ziplistDelete 函数,先删除压缩列表指定索引上的现有节点,然后调用 ziplistInsert 函数,将一个包含给定元素的新节点插入到相同索引上面。

调用 listIndex 函数,定位到双端链表指定索引上的节点,然后通过赋值操作更新节点的值。


3、哈希对象

3.1 编码类型

ziplist类型

        ziplist编码的哈希对象使用压缩列表作为底层实现,每当有新的键值对要加入到哈希对象时,程序会先将保存了键的压缩列表节点推入到压缩列表表尾,然后再将保存了值的压缩列表节点推入到压缩列表表尾,因此:

        保存了同一个键值对的两个节点总是紧挨在一起,保存键的节点在前,保存值的节点在后;

先添加到哈希对象中的键值对会被放在压缩列表的表头方向,而后来添加到哈希对象的键值对会被放在压缩列表的表尾方向。

hashtable类型

        hashtable编码的哈希对象使用字典作为底层实现,哈希对象中的每个键值对都使用一个字典键值对来保存:

        字典的每隔键都是一个字符串对象,对象中保存了键值对的键;

        字典的每个值都是一个字符串对象,对象中保存了键值对的值。

3.2 类型转换

        当哈希对象同时满足以下两个条件时,哈希对象使用ziplist编码(不能满足这两个条件的哈希对象使用hashtable编码):

        哈希对象保存的所有键值对的键和值的字符串长度都小于64字节;

        哈希对象保存的键值对数量小于512个;

        该两个条件的上限值可以通过配置文件redis.conf中hash-max-ziplist-value选项和hash-max-ziplist-entries选项修改。

3.3 哈希对象命令

命令

ziplist 编码实现方法

hashtable 编码的实现方法

hset

首先调用 ziplistPush 函数,将键推入到压缩列表的表尾,然后再次调用 ziplistPush 函数,将值推入到压缩列表的表尾。

调用 dictAdd 函数,将新节点添加到字典里面。

hget

首先调用 ziplistFind 函数,在压缩列表中查找指定键所对应的节点,然后调用 ziplistNext 函数,将指针移动到键节点旁边的值节点,最后返回值节点。

调用 dictFind 函数,在字典中查找给定键,然后调用 dictGetVal 函数,返回该键所对应的值。

hexists

调用 ziplistFind 函数,在压缩列表中查找指定键所对应的节点,如果找到的话说明键值对存在,没找到的话就说明键值对不存在。

调用 dictFind 函数,在字典中查找给定键,如果找到的话说明键值对存在,没找到的话就说明键值对不存在。

hdel

调用 ziplistFind 函数,在压缩列表中查找指定键所对应的节点,然后将相应的键节点、 以及键节点旁边的值节点都删除掉。

调用 dictDelete 函数,将指定键所对应的键值对从字典中删除掉。

hlen

调用 ziplistLen 函数,取得压缩列表包含节点的总数量,将这个数量除以 2 ,得出的结果就是压缩列表保存的键值对的数量。

调用 dictSize 函数,返回字典包含的键值对数量,这个数量就是哈希对象包含的键值对数量。

hgetall

遍历整个压缩列表,用 ziplistGet 函数返回所有键和值(都是节点)。

遍历整个字典,用 dictGetKey 函数返回字典的键,用 dictGetVal 函数返回字典的值。

4、集合对象

4.1 编码类型

intset类型

        intset编码的集合对象使用整数集合作为底层实现,集合对象包含的所有对象都被保存在整数集合里面。

hashtable类型

        hashtable编码的集合对象使用字典作为底层实现,字典的每个键都是一个字符串对象,每个字符串对象包含了一个集合元素,而字典的值全部被设置为NULL。

4.2 类型转换

        当集合对象同时满足一下两个条件时,对象使用intset编码(不能满足这两个条件的集合对象需要使用hashtable编码):

        集合对象保存的所有元素都是整数值;

        集合对象保存的元素数量不超过512个。

        该两个条件的上限值可以通过配置文件redis.conf中set-max-intset-entries选项修改。

4.3 哈希类型命令

命令

intset 编码的实现方法

hashtable 编码的实现方法

sadd

调用 intsetAdd 函数,将所有新元素添加到整数集合里面。

调用 dictAdd ,以新元素为键,NULL 为值,将键值对添加到字典里面。

scard

调用 intsetLen 函数,返回整数集合所包含的元素数量,这个数量就是集合对象所包含的元素数量。

调用 dictSize 函数,返回字典所包含的键值对数量,这个数量就是集合对象所包含的元素数量。

sismember

调用 intsetFind 函数,在整数集合中查找给定的元素,如果找到了说明元素存在于集合,没找到则说明元素不存在于集合。

调用 dictFind 函数,在字典的键中查找给定的元素,如果找到了说明元素存在于集合,没找到则说明元素不存在于集合。

smembers

遍历整个整数集合,使用 intsetGet 函数返回集合元素。

遍历整个字典,使用 dictGetKey 函数返回字典的键作为集合元素。

srandmember

调用 intsetRandom 函数,从整数集合中随机返回一个元素。

调用 dictGetRandomKey 函数,从字典中随机返回一个字典键。

spop

调用 intsetRandom 函数,从整数集合中随机取出一个元素,在将这个随机元素返回给客户端之后,调用 intsetRemove 函数,将随机元素从整数集合中删除掉。

调用 dictGetRandomKey 函数,从字典中随机取出一个字典键,在将这个随机字典键的值返回给客户端之后,调用 dictDelete 函数,从字典中删除随机字典键所对应的键值对。

srem

调用 intsetRemove 函数,从整数集合中删除所有给定的元素。

调用 dictDelete 函数,从字典中删除所有键为给定元素的键值对。

5、有序集合对象

5.1 编码类型

ziplist类型

        ziplist编码的有序集合对象使用压缩列表作为底层实现,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员(member),而第二个元素则保存元素的分值(scope)。压缩列表内的集合元素按分值从小到大进行排列。

skiplist类型

        skiplist编码的有序集合对象使用zset结果作为底层实现,一个zset结构同时包含一个字典和一个跳跃表

        zset结构中的zsl跳跃表按照分支从小到大保存了所有的集合元素。通过这个跳跃表,程序可以对有序集合进行范围型操作,比如ZRANK、ZRANGE等。

        除此之外,zset结构中的dict字典,为有序集合创建了一个从成员到分值的映射,字典的每个键值对都保存了一个集合元素:字典的键保存了元素的成员,而字典的值保存了元素的分值。通过这个字典,程序可以用O(1)的复杂度查找给定成员的分值,比如ZSCORE。

5.2 编码转换

        当有序集合对象可以同时满足一下两个条件时,对象使用ziplist编码(否则使用skiplist编码):

        有序集合保存的元素数量小于128个;

        有序集合保存的所有元素成员的长度都小于64字节。

5.3 有序集合对象命令

命令

ziplist 编码的实现方法

zset 编码的实现方法

zadd

调用 ziplistInsert 函数,将成员和分值作为两个节点分别插入到压缩列表。

先调用 zslInsert 函数,将新元素添加到跳跃表,然后调用 dictAdd 函数,将新元素关联到字典。

zcard

调用 ziplistLen 函数,获得压缩列表包含节点的数量,将这个数量除以 2 得出集合元素的数量。

访问跳跃表数据结构的 length 属性,直接返回集合元素的数量。

zcount

遍历压缩列表,统计分值在给定范围内的节点的数量。

遍历跳跃表,统计分值在给定范围内的节点的数量。

zrange

从表头向表尾遍历压缩列表,返回给定索引范围内的所有元素。

从表头向表尾遍历跳跃表,返回给定索引范围内的所有元素。

zrevrange

从表尾向表头遍历压缩列表,返回给定索引范围内的所有元素。

从表尾向表头遍历跳跃表,返回给定索引范围内的所有元素。

zrank

从表头向表尾遍历压缩列表,查找给定的成员,沿途记录经过节点的数量,当找到给定成员之后,途经节点的数量就是该成员所对应元素的排名。

从表头向表尾遍历跳跃表,查找给定的成员,沿途记录经过节点的数量,当找到给定成员之后,途经节点的数量就是该成员所对应元素的排名。

zrevrank

从表尾向表头遍历压缩列表,查找给定的成员,沿途记录经过节点的数量,当找到给定成员之后,途经节点的数量就是该成员所对应元素的排名。

从表尾向表头遍历跳跃表,查找给定的成员,沿途记录经过节点的数量,当找到给定成员之后,途经节点的数量就是该成员所对应元素的排名。

zrem

遍历压缩列表,删除所有包含给定成员的节点,以及被删除成员节点旁边的分值节点。

遍历跳跃表,删除所有包含了给定成员的跳跃表节点。 并在字典中解除被删除元素的成员和分值的关联。

zscore

遍历压缩列表,查找包含了给定成员的节点,然后取出成员节点旁边的分值节点保存的元素分值。

直接从字典中取出给定成员的分值。