Redis并没有使用基础数据结构去实现键值数据库,而是基于数据结构封装了一个个对象。
由于Redis是键值数据库,所以每次存储数据时,至少包含两个对象,即K、V对应的对象。其数据结构如下所示
class RedisObject{
// 类型
int type;
// 编码
int encoding;
// 指向底层数据结构指针
Object ptr;
// 引用计数
int refcount;
// 上次被访问时间
int lru;
}
类型(type),常用类型如下
| 类型常量 | 名称 |
|---|---|
| REDIS_STRING | 字符串 |
| REDIS_LIST | 链表 |
| REDIS_HASH | 哈希表 |
| REDIS_SET | 集合 |
| REDIS_ZSET | 有序集合 |
编码常量(encoding),如下
| 类型 | 编码对应的底层数据结构 |
|---|---|
| REDIS_ENCODING_INT | long类型的整数 |
| REDIS_ENCODING_EMBSTR | embstr格式的简单动态字符串实现 |
| REDIS_ENCODING_RAW | 简单动态字符串 |
| REDIS_ENCODING_HT | 哈希表 |
| REDIS_ENCODING_LINKEDLIST | 双端链表 |
| REDIS_ENCODING_ZIPLIST | 压缩链表和字典 |
| REDIS_ENCODING_SKIPLIST | 跳跃表 |
| REDIS_ENCODING_INTSET | 整数集合 |
redis中每种对象至少拥有两种实现。对应的关系如下
| redis对象 | 编码类型 | 描述 |
|---|---|---|
| REDIS_STRING | REDIS_ENCODING_EMBSTR | embstr编码的string |
| REDIS_STRING | REDIS_ENCODING_ROW | 简单动态字符串实现的string |
| REDIS_STRING | REDIS_ENCODING_INT | 整数值编码的string |
| REDIS_LIST | REDIS_ENCODING_LINKEDLIST | 双端链表实现的链表 |
| REDIS_LIST | REDIS_ENCODING_ZIPLIST | 压缩链表实现的链表 |
| REDIS_HASH | REDIS_ENCODING_HT | 哈希表实现的字典 |
| REDIS_HASH | REDIS_ENCODING_ZIPLIST | 压缩链表实现的字典 |
| REDIS_SET | REDIS_ENCODING_INTSET | 整数集合实现的集合 |
| REDIS_SET | REDIS_ENCODING_HT | 哈希表实现的集合 |
| REDIS_ZSET | REDIS_ENCODING_ZIPLIST | 压缩链表实现的有序集合 |
| REDIS_ZSET | REDIS_ENCODING_SKIPLIST | 跳表实现的有序集合 |
字符串对象:字符串对象在可用long类型表示时底层数据结构使用int编码,当无法使用long表示并且字符长度小于32字节时使用embstr编码,超过32字节时使用row编码。embstr编码和row编码底层数据结构都是sds,不同的是embstr由于固定了最长只能是32字节,所以内存可以并且是一次性分配的,sds和redisObject在一段连续的内存中,而row是分两次进行内存分配。
列表对象:列表对象底层有两个实现一个是LinkedList,另一个是ZipList,其实猜也能猜出,ZipList是为了节省空间。当保存的字符串元素长度小于64并且元素数量小于512个时使用ZipList作为底层实现,其它情况都是用LinkedList作为底层实现。!
哈希对象:哈希对象也有两个底层实现,一个是ZipList即压缩链表,另一个则是Ht哈希表,压缩链表是每次都存两个元素即键值,键在前,值在后。编码转换的条件也是和列表对象一样,即不能满足zipList条件的使用Ht实现,即当保存的字符串元素长度小于64并且元素数量小于512个时使用ZipList作为底层实现,否则使用Ht实现。
集合对象:集合对象的底层实现包括Ht和IntSet,其中Ht作为底层实现时,其键保存着集合元素,值都为Null。当集合元素数量小于512时并且元素都为整数值时使用IntSet作为底层实现,其他时候使用hashtable编码。
有序集合对象:有序集合对象的底层实现时ZipList或SkipList,使用ZipList作为底层实现时,第一个值保存元素的值,第二个值保存元素的分值。此外无论是使用ZipList还是使用SkipList作为底层实现,都会创建一个Ht用于保存分值到元素值的映射,从而达到O(1)的查询效率。
Redis的命令在执行前会对命令作类型检查,看当前数据对象是否支持当前命令,类似于Java中,众多实现类都继承了该类的方法,但由于不支持只能抛出Unsupported Exception。同时,也可能存在多个实现类支持这一方法,这也是属于一种多态。
C语言中不存在内存回收机制,所以Redis自己实现了一套通过引用技术法的内存回收机制,在每个redisObject中有一个refcount属性记录了被引用的次数。除了为了回收之外,引用计数还可以做到内存共享的作用。即存在想用某一对象将其引用技术加1而不是直接删除。由于共享字符串的复杂性以及不确定性,使得Redis只会初始化从0-9999的整数作为共享对象。
RedisObject除了以上几个属性,还有一个就是lru对象上次访问时间,通过当前时间减去lru就可以获得对象的空转时长。
原文:https://www.cnblogs.com/ccoder/p/15239987.html