自顶向下看看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
相关推荐
fat house cat_2 小时前
【redis】线程IO模型
java·redis
敖云岚3 小时前
【Redis】分布式锁的介绍与演进之路
数据库·redis·分布式
让我上个超影吧5 小时前
黑马点评【基于redis实现共享session登录】
java·redis
懒羊羊大王呀8 小时前
Ubuntu20.04中 Redis 的安装和配置
linux·redis
John Song10 小时前
Redis 集群批量删除key报错 CROSSSLOT Keys in request don‘t hash to the same slot
数据库·redis·哈希算法
Zfox_19 小时前
Redis:Hash数据类型
服务器·数据库·redis·缓存·微服务·哈希算法
呼拉拉呼拉19 小时前
Redis内存淘汰策略
redis·缓存
咖啡啡不加糖1 天前
Redis大key产生、排查与优化实践
java·数据库·redis·后端·缓存
MickeyCV1 天前
使用Docker部署MySQL&Redis容器与常见命令
redis·mysql·docker·容器·wsl·镜像
肥仔哥哥19301 天前
springCloud2025+springBoot3.5.0+Nacos集成redis从nacos拉配置起服务
redis·缓存·最新boot3集成