自顶向下看看Redis的数据结构

完整的redis底层数据结构应该是这样的,看完舒服多了:

最外面一层RedisServer,里面15个RedisDb,RedisDb里面是多个dict对象,dict里面是hashmap结构(实际上为了hash扩容,封装了2个hashmap,每次用一个),key是我们常写的get set的key,value就是 list、String、set这些对象

能用空间解决的优先用空间解决(空间换时间),能压缩的一定压缩

1.string(arraylist)

1.1 数据结构

cpp 复制代码
struct SDS<T>{
 T capacity;   //数组容量
 T len;        //数组长度  
 byte flags;   /zi/特殊标志位,不用理他
 byte[] content;//数组内容
}
    

1、比起传统的c语言字符串,有什么优势那? 传统的c语言字符串就是NUll(0x\0)为结束符,但是如果想要知道这个字符串的长度,就需要遍历。所以redis的字符串就在里面加了一个长度 len**,用空间换取时间。**
2、默认的字符串,如果有append操作,需要分配新的数组,然后将旧的数组复制过去,redis也做了一层优化,就是预分配机制,就是一旦触发了扩容(也就是说append操作),会预分配更长的content。具体规则是1M之前翻倍扩容,>1M就是每次加1M。
3、这个泛型T也有说法,当字符串比较小的时候,T可以用byte和short来表示,真是极致优化
4、embstr和row也有说法,当redis字符串总体长度>64字节的时候,redis认为这就是个大字符串,需要单独分配,这个临界点就是44 (64-16-3-1)64是每次分配内存都是32/64这样分配,16是redisObject对象头的大小,3是sds里面不包含内容的大小,1是字符串最后的null

Redis对象头

1.2 应用

  • 计数、递增、普通的字符串缓存等等

2. list(linkedlist)

2.1数据结构

3.2之前比较小时ziplist,增多时升级为linkedlist

cpp 复制代码
struct{
 int32 zlbytes;          // 整个压缩列表占用字节数
 int32 zltail_offest;    // 最后一个元素距离起始位置的偏移量,方便定位到最后一个
 int16 zllength;         // 元素个数
 T[] entries;            // 元素内容列表
 int8 zlend;             // 标志压缩列表的结束,值为0xFF 
    
}
cpp 复制代码
struct entry {
   int<var> prevlen; // 前一个 entry 的字节长度 int<var> encoding; // 元素类型编码
   optional byte[] content; // 元素内容
}


这里其实也很好理解,当数据量小的时候,数组有很大的优势 1、减少指针占用的空间 2、利用cpu缓存 。但是一旦数据量上来,分配和移动数组都是一个很大的开销,所以还是链表更好。

压缩列表存在的问题:连锁更新
从上图可以看到redis的压缩链表做了一点优化,如果前一个entry的length<254字节,那当前entry的prelen就存一个字节,否则就存5个字节。如果说存在以下情况:

这个时候,如果我往e1前面插入1个大于254字节的内容,就会导致e1需要修改自己的prelen,然后引发多米诺效应,连续需要更新所有的内容。

2.2.1总结一下

压缩列表的好处和坏处

  1. 好处:节省内存、利用cpu缓存
  2. 坏处:当数据量大的时候,分配和拷贝内存都非常消耗性能、还存在连锁更新的恐怖问题

3.2之后,统一使用quicklist,quicklist的解决方法是尽量还是使用ziplist来压缩内存,但是尽量控制ziplist的长度,来规避上面说到的连锁更新的问题。但是并没有解决该问题。

2.2 应用

  • 消息队列

3. hash (ziplist->dict)

3.1 数据结构

数量较少使用ziplist,达到一定大小后升级为类似Java HashMap结构.

3.1.2 ziplist压缩列表

cpp 复制代码
struct{
 int32 zlbytes;          // 整个压缩列表占用字节数
 int32 zltail_offest;    // 最后一个元素距离起始位置的偏移量,方便定位到最后一个
 int16 zllength;         // 元素个数
 T[] entries;            // 元素内容列表
 int8 zlend;             // 标志压缩列表的结束,值为0xFF 
    
}
cpp 复制代码
struct entry {
   int<var> prevlen; // 前一个 entry 的字节长度 int<var> encoding; // 元素类型编码
   optional byte[] content; // 元素内容
}


说白了,就是用数组实现了类似于链表的机构,为了支持从末尾遍历,增加了tail_offset支持直接定位到尾节点,而且每个节点都存放了前一个entry长度,同样是方便从后往前寻址。但是ziplist都是紧凑存储,没有冗余空间(就是空的地方),意味着每次插入一个元素都需要realloc拓展内存,如果ziplist占据内存太大,重新分配内存和拷贝内存就会消耗很大,所以ziplist不适合存储大型字符串,存储的元素也不宜过大。

3.1.3 dict字典 (hashtable[2])

字典是Redis服务器中出现最为频繁的复合型数据结构,除了hash结构的数据会用到字典(dict)外,整个Redis数据库的所有key和value也组成了一个全局字典,还有带过期时间的key集合也是一个字典。zset集合中存储value和score值的映射关系也是通过字典实现的。

cpp 复制代码
Struct RedisDb{
    dict * dict;
    dict * expires
}
cpp 复制代码
struct zset{
   dict * dict;
   zskiplist * zsl;
}
cpp 复制代码
struct dict{
   dictht ht[2];  //字典里面会用到2个hashmap   
}

既然用了链式存储,那当链表上数据量很大的时候(也就是说hash冲突很频繁的时候),那就需要rehash

rehash过程
  • 什么时候触发rehash
  1. 当数据量大小=哈希表大小的时候,再看一下有没有bgsave命令(也是AOF和RDB操作),如果没有就需要rehash
  2. 当数据量 / 哈希表大小=5的时候,强制rehash
  • 怎么rehash
  1. 渐进式的rehash,当有新增、删除、查找或者更新操作时,就会去执行把原来hashble上的数据rehash到新的hashtable上
  2. 如果客户端很空闲怎么办?开启一个定时任务,来完成rehash

3.2 应用

  • 分布式锁(redisson)
  • 对象缓存

4. set (inset或者hash)

4.1数据结构

4.1.1 当set的数据类型为整数的时候,采用下面这种格式

cpp 复制代码
struct{
  int32 encoding;
  int32 length;
  int<T> contents;
}

4.1.2 当set内容为其他类型的时候,采用下面这种格式

cpp 复制代码
struct dict{
   dictht ht[2];  //字典里面会用到2个hashmap   
}

这里有个很明显的问题????? 为什么set 不像hash一样去使用压缩链表,而直接去使用hash

5.zset (ziplist->skiplist)

5.1 数据结构

5.1.2 ziplist

当数据量不是很大的时候,使用的是ziplist

cpp 复制代码
struct {
     dict *dict;
     ziplist  *zip
}

5.1.3 skiplist

cpp 复制代码
struct {
     dict *dict;
     zskiplist  *zsl;
}

当数据量大的时候,还是要使用链式存储,数组一样的存储在存储动态数据的时候,遇到更新操作太操蛋了。

跳表的搜索过程:!!!

  1. 从顶部开始搜索,如果搜索到的内容 < 目标对象,就进入下一层搜索,如果搜索到的内容 > 目标对象,就移动到前一个节点,然后进入下一层搜索
  2. 假设上面搜索权重4
    1. 找到 level2,找到最后 9>4, 然后退回到 level2的1,1往下一层搜索
    2. leve1的1往后找,找到 level的5 >4, 又退回到 level1的1,1往下一层搜索
    3. level0的1往后找,找到4
相关推荐
mit6.8241 小时前
[Redis#3] 通用命令 | 数据类型 | 内部编码 | 单线程 | 快的原因
linux·redis·分布式
mit6.8241 小时前
[Redis#4] string | 常用命令 | + mysql use:cache | session
数据库·redis·后端·缓存
Beekeeper&&P...2 小时前
map和redis关系
数据库·redis·缓存
一朵忽明忽暗的云2 小时前
【Redis_Day6】Hash类型
redis·hash类型
fpcc14 小时前
redis6.0之后的多线程版本的问题
c++·redis
刘九灵14 小时前
Redis ⽀持哪⼏种数据类型?适⽤场景,底层结构
redis·缓存
登云时刻17 小时前
Kubernetes集群外连接redis集群和使用redis-shake工具迁移数据(一)
redis·kubernetes·bootstrap
煎饼小狗1 天前
Redis五大基本类型——Zset有序集合命令详解(命令用法详解+思维导图详解)
数据库·redis·缓存
秋意钟1 天前
缓存雪崩、缓存穿透【Redis】
redis
简 洁 冬冬1 天前
046 购物车
redis·购物车