《Redis设计与实现》(二)对象
一、对象的类型和编码
Redis中每个对象都有一个redisObject结构表示:
typedef struct redisObject {
unsigned type:4; // 类型
unsigned encoding:4; // 编码
void *ptr; // 指向底层实现数据结构的指针
}robj;
type类型: 对于Redis来说,键总是一个字符串对象,值则可以是下面五种对象;当我们称呼XX键时,表示值为XX对象,如列表键;
类型常量 | 对象名称 |
---|---|
REDIS_STRING | 字符串对象 |
REDIS_LIST | 列表对象 |
REDIS_HASH | 哈希对象 |
REDIS_SET | 集合对象 |
REDIS_ZSET | 有序集合对象 |
二、字符串对象
唯一一种会被其他四种对象嵌套的对象;
字符串对象的编码可以是int、raw或者embstr;
1)如果为int,则字符串对象保存为整数值,这个整数值可以用long类型表示,那么字符串对象会将整数保存在字符串对象结构的ptr属性里;
2)如果为raw,则字符串对象保存长度大于39字节的字符串,使用SDS类型保存;
3)如果为embstr,则字符串对象保存长度小于等于39字节的字符串,这是专门用于保存短字符串的优化编码方式;和raw一样,也是用redisObject类型和sds类型保存,但raw需要两次内存分配函数分别创建redisObject和sds,但embstr则将redisObject和sds创建在一块连续区域中;所以也只需一次内存释放函数,并带来缓存的优势。此外,浮点数也会通过embstr方式保存(字符串转浮点数);
int在执行一些命令使得存储的对象不是整数值时,编码方式会变为raw;
embstr编码的字符串对象实际上是只读的,任何对其的修改,都会将其转换为raw编码然后再执行修改命令;
三、列表对象
列表对象的编码可以是ziplist或者linkedlist;
1)ziplist编码的列表对象使用压缩列表作为底层实现,每个压缩列表节点保存一个列表元素。
2)linkedlist编码的列表对象使用双端链表作为底层实现,每个双端链表节点都保存了一个字符串对象(注意,是字符串对象而不是字符串数据结构,需要有redisObject结构),而每个字符串对象都保存了一个列表元素。
使用ziplist的条件,不满足则使用linkedlist:
1) 当列表对象保存的所有字符串元素的长度都小于64字节;
2)保存的元素数量小于512个;
四、哈希对象
哈希对象的编码可以是ziplist或者hashtable;
1)ziplist编码的哈希对象使用压缩列表作为底层实现,每当有新的键值对要加入哈希对象时,查询会先将八婆村了键的压缩列表节点对入到压缩列表表尾,然后再将保存了值得压缩列表节点推入到压缩列表表尾;(也就是说,每个键值对在压缩列表中占了两个节点,键在前值在后);
2)hashtable编码使用字典作为底层实现,每个键值对都使用字典的一个键值对来保存,键和值都是字符串对象;
使用ziplist的条件,不满足则使用hashtable:
1)当哈希对象保存的所有键和值的字符串的长度都小于64字节;
2)保存的元素数量小于512个;
五、集合对象
集合对象的编码可以是intset或者hashtable
1)intset编码的集合对象使用整数集合作为底层实现,集合对象包含的所有元素都被保存在整数集合里;
2) hashtable编码的集合对象使用字典作为底层实现,字典的每个键都是一个字符串对象,值为NULL;
使用intset的条件,不满足则使用hashtable:
1) 当集合对象保存的所有元素都是整数值;
2)保存的元素数量小于512个;
六、有序集合对象
有序集合的每个元素有两个值,一个分值用来表示元素的大小(double),一个为元素值(字符串对象)
有序集合的编码可以是ziplist或者skiplist;
1)ziplist编码使用压缩列表作为底层实现,每个集合元素使用两个紧挨在一起的节点来保存,一个保存元素值,一个保存元素的大小分值;集合中的元素按分值从小到大排列;
2)skiplist编码的使用zset结构作为底层实现,包含了一个字典和一个跳跃表;如下面代码所示,zsl跳跃表按照分值从小到大保存了所有集合元素。通过跳跃表,程序可以对有序集合进行范围操作,如ZRANK等;而dict的键保存有有序集合的键,值保存着有序集合的分值,这样有序集合就可以用O(1)查询出元素的分值大小。此外,zsl和dict每个节点的中保存和元素值和分值都分别通过指针指向同一块内存,不会有太多空间浪费。
typedef struct zset{
zskiplist *zsl;
dict *dict;
}zset;
使用ziplist的条件,不满足则使用skiplist:
1) 当集合对象保存的所有元素成员的长度都小于64字节;
2)保存的元素数量小于128个;
七、内存回收
Redis在自己的对象系统中构建了一个引用计数技术实现的内存回收机制,通过这一机制,程序可以通过跟踪对象的引用计数信息,在适当的时候自动释放对象并进行内存回收。
typedef struct redisObject {
unsigned type:4; // 类型
unsigned encoding:4; // 编码
void *ptr; // 指向底层实现数据结构的指针
int refcount; // 引用计数
}robj;
在内存回收的使用中,对象的引用计数信息会跟随着对象的使用状态而不断变化:创建时为1;每被一个程序使用,便加一;而不再被一个程序使用则减一;当为0时,对象所占用的内存会被释放。
引用计数也用在对象共享,当使用到已经有的对象时,可以将值指针指向该对象,将引用计数加一,可以节约内存空间;目前而言,Redis在初始化服务器时,创建一万个字符串对象,这些对象包含了1到9999的所有整数值,当用到时,可以共享对象,而不是新创建对象。 可以在字符串键中使用,也可以使用在数据结构中嵌套了字符串对象的对象。
typedef struct redisObject {
unsigned type:4; // 类型
unsigned encoding:4; // 编码
void *ptr; // 指向底层实现数据结构的指针
int refcount; // 引用计数
unsigned lru:22; // 空转时长
}robj;
空转时长: 该属性记录了对象最后一次被命令程序访问的时间;当服务器内存达到阈值时,会将空转时间较高的那部分键优先释放掉。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端