前言:redis没有直接使用前面介绍的几种数据类型
sds、双端链表、字典、压缩列表、整数集合等等。
而是基于这些数据结构创建了一个对象系统,这个对象系统包含字符串对象、列表对象、哈希对象、集合对象和有序集合对象这五种类型的对象。每种对象都用到了至少一种我们前面所介绍的数据结构。
redis的对象系统还实现了基于引用计数技术的内存回收机制(竟然redis也有),当程序不再使用某个对象的时候,这个对象所占有的内存就会被自动释放。redis还通过引用计数技术实现了对象共享机制。这一机制可以在适当的条件下,通过让多个数据库键共享同一个对象来节约内存。
redis的对象带有访问时间记录信息。该信可以用于计算数据库键的空转时长。服务器启用maxmemory功能的情况下。空转时长较大的那些键可能会优先被服务器删除。
8.1 对象的类型与编码
set操作时,redis至少会创建两个对象,一个键对象,一个值对象
redis中的每一个对象都由一个redisObject结构表示。
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;
8.1.1 类型
类型枚举
redis的键对象总是字符串对象,只有值对象会有区别。因此这样约定
●当我们称呼一个数据局键为“字符串键”时,我们指的是“这个数据库键对应的值为字符串对象”
执行type命令时,返回的是值的类型
SET msg “hello world”
TYPE msg
8.1.2 编码和底层实现
对象的ptr指针指向对象的底层实现数据结构,而这些数据结构由对象的encoding属性决定。
encoding属性记录了对象所使用的编码,即这个对象使用了什么数据结构,作为对象的底层实现。
每种类型的对象都至少使用了两种不同的编码。
object encoding命令可以查看一个数据库键的值对象的编码
OBJECT ENCODING msg
通过encoding属性来设定对象所使用的编码,而不是为特定类型的对象关联一种固定的编码。极大的提升了redis的灵活性和效率
举个🌰:
列表对象包含的元素比较少时,redis使用压缩列表作为底层实现
压缩列表比双端列表更节约内存,在元素数量较少时,会更快加入缓存
列表对象包含的元素越来越多时,会转为双端列表。
其他类型也会有类似的优化。
接下来会分别介绍redis的五种不同类型的对象。这些对象底层所使用的编码方式,
给出从一种编码转为另一种编码的条件,以及同一个命令在不同编码上的实现方法
8.2 字符串对象
字符串对象的编码可以是int、raw或者embstr
如果一个字符串对象保存的是整数值,并且这个正数值可以用long类型来表示。那么字符串对象会将整数值保存字符串对象结构的ptr属性里(将void*转换成long)。并将字符串对象的编码设置为int
127.0.0.1:6379> set number 10086
OK
127.0.0.1:6379> object enchoding number
(error) ERR Unknown subcommand or wrong number of arguments for ‘enchoding’. Try OBJECT HELP.
127.0.0.1:6379> object encoding number
“int”
127.0.0.1:6379>
●字符串对象保存的是一个字符串值,并且这个字符串长度大于32字节。那么字符串对象将使用一个简单动态字符串(sds)来保存这个字符串值,并将对象的编码设置为raw
127.0.0.1:6379> set story “the prince loved the rose because it’s his rose”
OK
127.0.0.1:6379> object encoding story
“raw”
127.0.0.1:6379>
指针ptr指向一个sds数据
●字符串长度小于等于32字节,字符串对象将使用embstr编码来保存
127.0.0.1:6379> object encoding number
“int”
127.0.0.1:6379> set story “i miss you”
OK
127.0.0.1:6379> object encoding story
“embstr”
embstr是专门用来保存短字符串的一种优化编码方式。
embstr的好处
●分配内存和回收内存相较于raw只需要一次
●保存在连续内存里,embstr能更好利用缓存带来的优势
brew services restart redis
redis-cli
127.0.0.1:6379> set msg “hello”
OK
127.0.0.1:6379> object encoding msg
“embstr”
127.0.0.1:6379>
可以用long double类型表示的浮点数在redis中也是作为字符串值来保存的。如果要保存一个浮点数到字符串对象里,程序会先将这个浮点数转换成字符串,再保存转换所得的字符串值。
why
127.0.0.1:6379> set pi 3.14
OK
127.0.0.1:6379> object ecoding pi
(error) ERR unknown subcommand ‘ecoding’. Try OBJECT HELP.
127.0.0.1:6379> object encoding pi
“embstr”
127.0.0.1:6379>
还可以保存整数,编码方式是int
127.0.0.1:6379> set pi 3
OK
127.0.0.1:6379> object encoding pi
“int”
127.0.0.1:6379>
8.2.1 编码的转换
对于int编码的字符串对象,执行一些命令使得这个对象不再是整数时,编码会从int变成raw
127.0.0.1:6379> set number 10086
OK
127.0.0.1:6379> object encoding number
“int”
127.0.0.1:6379> append number “is a good number”
(integer) 21
127.0.0.1:6379> object encoding number
“raw”
embstr只读,不可修改,执行修改命令时,会先转换成raw,再执行修改
127.0.0.1:6379> set me chen
OK
127.0.0.1:6379> object encoding me
“embstr”
127.0.0.1:6379> append me " happy"
(integer) 10
127.0.0.1:6379> object encoding me
“raw”
8.2.2 api
8.3 列表对象
列表对象的编码ziplist或者linkedlist
ziplist编码的列表对象使用压缩列表作为底层实现
encoding和type决定了ptr指向的数据结构是什么,从而进行读取
ziplist:ptr指向的是ziplist,保存的是列表里的值,每一节点里存的都是一个字符串对象
linkedList:prt指向的是链表头节点,
8.3.1 编码转换
●对象中保存的字符串长度不超过64字节
●元素数量小于512
两个条件任意一个不能满足就会由ziplist编码转为linkedList编码
8.3.2 命令列表(实用)
命令 含义
lpush push新元素到列表头
rpsh push新元素到列表尾
lpop pop头节点
rpop pop尾节点
lindex 返回指定节点
llen 长度
linsert 插入表头、表尾或者其他位置
lrem 删除给定元素
ltrim 删除所有不在指定索引范围内的节点
lset 更新节点值
8.4 哈希对象
ziplist或者hastable
ziplist:key和value挨着
hashtable:key连续,且保存了value的地址?
8.4.1 编码转换
●不超过64字节
●键值对数量不多于512
同时满足:ziplist
否则 hashtbale
8.5 集合对象
●都是整数值
●元素数量不超过512
此时用的是intset
否则用hashtable
条件二通过set-max-intset-entries来修改
命令:sadd、scard、sismember(查找是否存在)、srandmember(随机返回一个)、spop(随机返回并删除)、srem(删除给定元素)
8.6 有序集合对象
ziplist和skiplist
ziplist中,集合元素按分值从小到大排序,分值小的元素被放在靠近表头的方向,分值较大的元素放置在靠近表尾的方向。
zset结构中包含一个字典和一个跳跃表
如注释:
●dict用来支持o(1)复杂度的按成员取分值操作
● zsl(跳跃表)用来支持平均复杂度为O(log N)的按照分值定位成员和范围操作
typedef struct zset {
// 字典,键为成员,值为分值
// 用于支持 O(1) 复杂度的按成员取分值操作
dict *dict;
// 跳跃表,按分值排序成员
// 用于支持平均复杂度为 O(log N) 的按分值定位成员操作
// 以及范围操作
zskiplist *zsl;
} zset;
跳跃表里
object属性:保存了元素的成员
score属性:保存了元素的分值
zscore命令是通过dict来实现的。
这两种类型的数据结构是通过指针来共享相同元素的成员和分值。虽然是两种数据结构,但是不会产生任何重复的成员或者分值
●元素数量小于128
●所有元素成员长度小于64
此时使用ziplist,否则用skiplist
命令
zadd、zcard、zcount(统计分值在给定范围内的节点数量)、zrange(表头向表尾遍历,返回给定索引范围内的所有元素)、zrank、
8.7 类型检查与命令多态
命令类型检查:检查type字段
命令多态:虽然使用的是同一个命令,但是会根据type和encoding字段确定使用的是哪种数据结构,并调用相应的api
因此被称为命令多态
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;
8.8 内存回收
通过refcount字段实现,当refcount字段为0时,对象占用的内存会被回收
8.9 对象共享
整数类型的对象会被共享