Redis定义了一些基本了数据类型,如简单动态字符串、字典、跳跃表、压缩列表等等,但并未使用这些结构作为对象存储,而是定义了一套对象系统,底层使用这些数据结构进行存储,这个对象系统包括了字符串对象、列表对象、哈希对象、集合对象、有序集合对象,Redis针对不同的使用场景,设置不同的数据结构,优化其使用效率。
Redis通过对象引用计数进行内存的自动回收,并通过引用计数实现对象共享机制,Redis对象还保存了最近一次访问的时间(或历史访问频率),通过LRU(LFU)算法对其进行优化。
1. redis对象定义
- redis对象
1 | typedef struct redisObject { |
2. 对象类型
redis对象使用type(4bit)字段表示该对象的类型,Redis数据库中键总是一个字符串对象,而值可以是字符串对象、列表对象、哈希对象、集合对象、有序集合对象和模块对象。
常量 | 对象 | TYPE命令输出 |
---|---|---|
OBJ_STRING 0 | 字符串对象 | “string” |
OBJ_LIST 1 | 列表对象 | “list” |
OBJ_SET 2 | 集合对象 | “set” |
OBJ_ZSET 3 | 有序集合对象 | “zset” |
OBJ_HASH 4 | 哈希对象 | “hash” |
OBJ_MODULE 5 | 模块对象(特殊的类型) | 模块名称 |
3. 对象编码
redis对象的ptr属性指向底层的数据结构,这些数据结构由encoding属性决定。
常量 | 底层数据结构 | encoding 命令输出 | 备注 |
---|---|---|---|
OBJ_ENCODING_RAW 0 | 简单动态字符串 | “raw” | |
OBJ_ENCODING_INT 1 | long类型的整数 | “int” | |
OBJ_ENCODING_HT 2 | 字典 | “hashtable” | |
OBJ_ENCODING_ZIPMAP 3 | 压缩字典 | —- | 不会单独使用 |
OBJ_ENCODING_LINKEDLIST 4 | 双端链表 | —- | 不再用于列表对象 |
OBJ_ENCODING_ZIPLIST 5 | 压缩列表 | “ziplist” | |
OBJ_ENCODING_INTSET 6 | 整数集合 | “intset” | |
OBJ_ENCODING_SKIPLIST 7 | 跳跃表 | “skiplist” | |
OBJ_ENCODING_EMBSTR 8 | embstr类型sds | “embstr” | |
OBJ_ENCODING_QUICKLIST 9 | 快速列表 | “quicklist” |
每种对象都存在不同的编码方式。
对象类型 | 编码类型 | 描述 |
---|---|---|
STRING | INT | 使用数值存储 |
STRING | EMBSTR | 不被修改的字符串,并且不能超过44字节 |
STRING | RAW | 超过44字节的一般字符串 |
LIST | QUICKLIST | 列表对象的编码仅使用QUICKLIST |
HASH | HT | 字典编码的哈希对象 |
HASH | ZIPLIST | 压缩列表编码的哈希对象 |
SET | HT | 字典编码的集合对象 |
SET | INTSET | 整数集合编码的集合对象 |
ZSET | QUICKLIST | 跳跃表编码有序集合对象 |
ZSET | ZIPLIST | 压缩列表编码有序集合对象 |
4. 内存优化
4.1 引用计数
C语言没有垃圾回收机制(GC),所以Redis自己实现了基于引用计数的内存回收,通过跟踪引用计数,在适当的时候释放内存空间,优化性能。
对象的引用计数会随对象的使用而发生改变
- 创建新对象时,引用计数+1
- 对象被使用时,引用计数+1
- 对象不在被使用时,引用计数-1
- 引用计数减为0时,释放占用内存空间
- [0, 10000)范围的整数引用计数设为INT_MAX,用做整数共享
函数 | 作用 |
---|---|
incrRefCount | 对象引用计数+1 |
decrRefCount | 对象引用计数-1,当计数为0时,根据其编码释放底层内存空间 |
resetRefCount | 对象引用计数设置为0,但不释放内存,通常用在 创建对象后调用会增加引用计数的函 |
4.2 对象共享
Redis通过引用计数实现了对象共享的机制,如[0, 10000)范围的数字就是共享整数,若redis中的值在此范围内,则会通过指针指向这些整数。
当服务器将共享对象设置为值的时候,需要检查共享对象和目标对象是否完全相同,只有完全相同才会进行共享,检查对象完全相同的代价取决于对象的复杂程度,基础类型耗时很少,但越复杂的对象则需要越大的代价,尽管可以节省内存,但CPU耗时会增加,所以Redis目前只共享整数
4.3 命令
Redis使用了LRU和LFU算法记录对象被访问的情况,默认开启LRU算法,LFU算法需通过配置文件开启。
使用OBJECT命令可以获取对象内存有关的信息。
1 | # 获取对象的信息 |
使用TYPE命令可以获取对象的类型
1 | # 获取key对象的类型,string, list, set, zset, hash |
5. 源码剖析
- 减少对象引用计数,当引用计数为0时释放该对象
1 | void decrRefCount(robj *o) { |
- 创建一个redis对象
1 | robj *createObject(int type, void *ptr) { |
- 创建一个quicklist列表对象
1 | robj *createQuicklistObject(void) { |
- 创建一个dict集合对象
1 | robj *createSetObject(void) { |
- 创建一个intset集合对象
1 | robj *createIntsetObject(void) { // 创建一个整数结合 |
- 创建一个ziplist哈希对象
1 | robj *createHashObject(void) { |
- 创建一个skiplist有序集合对象
1 | robj *createZsetObject(void) { |
- 创建一个ziplist有序集合对象
1 | robj *createZsetZiplistObject(void) { |