redis底层数据结构(redis底层存储结构、源码分析)

article/2025/9/13 21:03:54

文章目录

  • 前言
  • 一、redis为什么快?
  • 二、redis的底层数据结构
    • 2.1、redis的底层存储的扩容机制
      • 2.1.1、扩容时间
      • 2.1.2、扩容多大
      • 2.1.3、扩容后的rehash
      • 2.1.4、何时进行rehash
      • 2.1.5、俩hashtable访问那个呢?
    • 2.2、redis的key的底层数据类型(sds)
      • 2.2.1、sds(Simple dynamic string)
      • 2.2.2、sds诞生的原因
      • 2.2.3、sds的数据结构
      • 2.2.4、sds扩容原理
      • 2.2.5、redis3.0以后对底层sds的优化
  • 三、redis的底层数据库设计?
    • 3.1、redis的底层存储结构
      • 3.1.1、redisDb
      • 3.1.2、dist
      • 3.1.3、dictType
      • 3.1.4、dictht
      • 3.1.5、dictEntry
      • 3.1.6、redisObject
    • 3.2、数据库的总体理解图
  • 四、总结


前言

随着工作年限的增加,我们已经逐渐不再满足会用redis即可了,更希望深究redis底层的设计思想,实现方案,通过源码中精妙的设计,来帮助我们更好的设计,优化业务项目。本文是博主从网上搜的一些学习视频,加上自己的理解而创作的。本人水平有限,如有误导,欢迎斧正,一起学习,共同进步!


一、redis为什么快?

1、高速的存储介质 基于内存
2、优良的底层数据结构 时间复杂度 O(1) ,hashMap结构
3、高效的网络io模型 io模型的高效。epoll、select、kqueue、evport
4、高效的线程模型 执行线程是单线程,(io线程可能是多个线程,但是执行线程一定是1个。redis6.0以后,io线程是多线程,执行线程还是单线程)

二、redis的底层数据结构

redis的底层是一个数组。数组的时间复杂度是 O(1),不管查数组中的某个元素,都会很快。redis的底层是 hashTable,其实是数组,叫了个hashTable的名字。因为redis都是一个key 一个v的格式嘛。然后主结构的数组,就是存的 key 的hash函数的值。就是对key进行求hash,然后在对数组的长度求模。然后将元素存到指定的位置上(是不是有点像hashMap的主结构)。
redis采用链表法解决hash冲突。链表法也有头插法和尾插发的区别。redis是使用的头插法。原因是既然使用redis,肯定是觉得是个高频使用的,不是高频使用的直接放数据库就行,没必要放redis,既然是高频使用的,放在链表的头部,能更快的被查找,使用。

2.1、redis的底层存储的扩容机制

上面已经说了,redis的底层是hashtable,也就是数组。redis的hashtable(主结构数组)也会进行扩容,因为链表一旦长了,肯定就会影响查询的效率了。

2.1.1、扩容时间

既然是扩容,那是啥时候才会进行扩容呢?当hashTable存的元素已经超过了数组的长度的时候,就会进行扩容。

2.1.2、扩容多大

既然是扩容,那扩容多少呢?扩容一倍。比如说原先hashTable的容量是8,扩容后变成了16。当然,扩容后还有一个rehash的过程,

2.1.3、扩容后的rehash

rehash是说,讲原先数组(hashTable)中的元素,移动到扩容后的新的数组的位置中去。比如说原先 hash(key) % hashtable.size() = 某一个位置。 你的hashtable从8变成了16,那具体的存的位置肯定会改变,当然,也可能不会变。比如说原先数组的长是8,key的hash对8求模,是在第4个元素。扩容后了,key的hash对16求模。结果有可能还是第4个元素,也可能不是。具体看第几个元素来判断会不会移动。

2.1.4、何时进行rehash

那么什么时候进行rehash呢,也分为两种。

  • 主动:我访问一次数据的时候(不管是get也好,set也好,都会去访问一下hashtable,因为整个数据的索引都是通过hashtable来管理的),就做一下rehash。从低往高挨着去搬hash巢。一次搬一个巢,一个巢里可能有多个元素。而且他是按顺序去搬,并不是搬你要访问的那个

  • 被动:默认会轮询的搬hash巢,默认是搬100个。之所以只搬运100个hash巢,因为hash巢可能会非常大,你都同时搬运的话,可能会造成线程的卡顿,而对于redis这种内存型数据库来说,线程卡顿是业务上不可接受的。

2.1.5、俩hashtable访问那个呢?

那比如扩容的时候,有俩hashtable。因为redis的底层扩容的时候,是直接复制出来一个2倍的hashtable,然后把原始的hashtable的元素慢慢移动到第二个hashtable中去。因此比如访问的时候,那是访问第一个的hashtable还是第二个的hashtable呢,是先访问老的hashtable,如果有的话,直接拿到数据了,如果没有,就去新的hashtable中去拿。并且如果此时有新的元素加入进来了,就会去存入新的hashtable中,而不会再存到旧的hashtable中去。

2.2、redis的key的底层数据类型(sds)

redis是一个键值对的数据库,redis的值,可以是 string、list、set、zset、hash、bitmap、geospatial、hyperloglog。redis的键呢?redis的键可以是 string、int、double、图片、视频、音频、文件等等。但是,这些都是表象,都是客户端传给redis服务器的传值,具体的类型是由redis-server服务器来决定的。但是,不管是什么类型的key,redis的底层,存的都是string对象。也就是说,redis所有的键,都是string类型的

2.2.1、sds(Simple dynamic string)

redis是c语言写的,虽然redis是c语言写的,但是他却没有直接用c语言的数据结构。它自定义了一个 SDS (Simple dynamic string)的数据类型。c语言中的string是怎么表示的呢?是用一个char类型的数组来表示的,而且编译器会自动加一个 \0 来表示结束。
比如你有一个String类型的数据: dazeng
char [] data = “dazeng”; c语言的编译器,会自动的变成:
char [] data = “dazeng\0”; 去给最后加一个 \0。

2.2.2、sds诞生的原因

之所以要自定义 sds (simple dynamic string),是因为有的字符串中可能就有会 \0 这样的特殊字符,那你在用c语言中的 \0 来表示结尾,就不行了。就会中断,\0后面的字符就会被丢弃,这样显然是不行的。

2.2.3、sds的数据结构

比如说,一个da\0zeng的字符串, char [] buf =“da\0zeng” // 其中 \0 占一个字节
底层是sds结构(redis3.0版本):

	int len:7			// 数据的长度。不在依赖于\0去当做数据结尾,而是去查数据的长度,去截断。int free				// redis3.0以后是改成了alloc,也是因为int浪费空间,buf[14]=dazeng123		// 真正要存的 业务数据
  • len表示数据的长度。这样比较浪费空间,因为redis用的时候,大都是用的string(可能百分之80,90都是用的string)。然后你string的底层是sds,sds的int类型是4字节,4字节是能存 2^32 -1 的长度,好几亿,肯定不会有业务数据有好几亿的长度,所以会很浪费。虽然没毛病,却会很浪费内存。往往我们1个字节, 2^7 -1 的长度,就已经能很好的满足我们的业务需求了。

  • free是表示剩余的空间长度。也是一样的,因为redis的string增加的时候,比如说原先叫 dazeng,现在希望改成 dazeng1 。那么他的长度,可不是从6变成7。而是,希望变成7,就是 72=14。给他分配14的长度(其实这个2倍,也是看的大小,如果小于1m,则直接是翻倍扩容,如果是超过了1m,则是1m1m的加。1M=10241024*Char。Char可以是5bits/8bits/16bits/32bits/64bits)。那你用了7,14的长度用了7,还剩下7,你的free就是7,下次改成 dazeng12 8的长度的时候,就不用从新开辟内存空间,从新去扩容了。

2.2.4、sds扩容原理

大小小于1M的时候,翻倍扩容;大于1M的时候,1M,1M的增加。其实上面已经说完了。比如说原先叫 dazeng,现在希望改成 dazeng1 。那么他的长度,可不是从6变成7。而是,希望变成7,就是 72=14。给他分配14的长度(其实这个2倍,也是看的大小,如果小于1m,则直接是翻倍扩容,如果是超过了1m,则是1m1m的加。1M=10241024*Char。Char可以是5bits/8bits/16bits/32bits/64bits)。那你用了7,14的长度用了7,还剩下7,你的free就是7,下次改成 dazeng12 8的长度的时候,就不用从新开辟内存空间,从新去扩容了。其实扩容后,也会自动的在字符串后面加一个 \0 ,因为redis毕竟是c语音写的,还是会尽可能的兼容c语言的语法的。

2.2.5、redis3.0以后对底层sds的优化

redis的6.0版本中,都是:

sdshdr5		// 长度是  0 - 2^5 -1 
sdshdr8		// 长度是  0 - 2^8 -1 
sdshdr16	// 长度是  0 - 2^16 -1 
sdshdr32	// 长度是  0 - 2^32 -1 

之所以出现了sdshdr5,sdshdr8,sdshdr16,是因为我们只用int类型去表示长度,是比较浪费的。所以才会出现,hdr8,hdr16之类的数据类型。可以用相对更小的空间去描述我们的数据。用 sdshrd5 去描述头部数据。什么是头部数据呢,就是不是真正的业务数据buf,而是去描述业务数据的长度的,叫头部数据(比如说sds结构中的 len就是头部数据),sdshdr5举例,他用一个char类型的flags去表示,char只占一个byte,一个byte又是8个bit位。做了更多的优化。一个byte中,也拆分为两部分,第一部分当做类型,第二部分当做业务数据的长度。

三、redis的底层数据库设计?

在redis.conf 中。databases 16 其实就代表 有16个库,底层是个数组;数据库对应的结构:在server.h的文件中,有个redisDb的结构。

3.1、redis的底层存储结构

3.1.1、redisDb

在这里插入图片描述
其中
dist:是存全部的键值对的空间(最核心的点)
expires:是做一些 过期时间 的处理的
blocking_keys:是一些阻塞的api
ready_keys:一些键的锁,接受消息的时候的一些处理
watched_keys:事务的一些命令 的存储

3.1.2、dist

在这里插入图片描述
其中,下面这三是核心
dictType: 是字典类型,
dictht:其实就是一个hashTable(上面所说的数组),字典的hashtable。注意,它是个数组,数组里面单个元素是dictht。下面会详细介绍dictht
rehashidx:这个就是rehash的时候,已经rehash到了哪个的哈希巢了。

3.1.3、dictType

在这里插入图片描述
核心的是
hashFunction:hash函数,因为键要找到对应的hash巢的话,是通过这个hashfunction来找到对应的巢的
keyCompare:是键做比较的。因为如果两个键的hashCode一致,也不一定是hash冲突,也有可能这俩键是一样的,那就应该直接覆盖,不一样的情况下,才是hash冲突,就链表加就行。

3.1.4、dictht

在这里插入图片描述
table:指向的就是一个hash表的地址(键值对的真正的数据结构)
size:是hash表的长度
sizemask:是size的长度-1,是一个小优化,为了求模的时候更快
used:hashtable中一共有多少个元素

3.1.5、dictEntry

dictht中的 table的数据结构
在这里插入图片描述
key:string类型的key
v:是一个union的数据类型的指针。union的意思是,我有许多类型,但是同一时间,我只会有一个数据类型。比如说是val的话,就是指向的一个redisObject对象
next:是产生hash冲突的时候,next的指针,指向下一个的地址。

3.1.6、redisObject

在这里插入图片描述
type:是说这个对象是个什么样的类型,是string呢,还是list呢还是hash,set,bitmap之类的类型
encoding:是说编码。即使是相同的类型的话,底层是有可能会是不同的编码(值的长度不一样,编码方式会不同)。
lru:是和内存淘汰算法相关的一些处理
refcount:引用计数法相关的一个判断对象是否存活用的(因为redis是c写的,需要自己管理内存)
ptr:是真正的指向内存中的某一个区域,这个区域是用来存键值对的真正的数据的

3.2、数据库的总体理解图

在这里插入图片描述

  1. redisDb是数据库存的结构,通俗的话,就是最外层的存储结构
  2. dist是用来存储所有的数据的键值对的结构,dict中有type、dictht这俩重要的结构。其中type是存类型的,dictht是用来存数据的,之所以要有俩,是为了渐变式的rehash用的。(正常情况下,没有用到rehash的时候,同一时间,只会有一个hashtable)。rehash的时候,会创建一个比原来大一倍的空间
  3. 会根据数据的类型,数据的大小,来确认一个编码方式,然后存储到redisObject中去。
  4. redisObject的ptr又是一个sds的数据结构

四、总结

以上就是今天要讲的内容,本文仅仅简单介绍了redis的底层存储结构,而一些精妙的设计思想,例如redis的sdshdr8,sdshdr16来节省空间、redis的int类型的ptr不是指针信息而是直接是内容来减少一次磁盘io操作、等等这种设计思想上的精妙,是值得我们沉下心思去学习的。


http://chatgpt.dhexx.cn/article/3YHYZkgb.shtml

相关文章

redis底层数据结构 -String

redis包含5种常用数据结构 String 、List、Hash、Set 、Zset 基础铺垫 以set为例 redis其实可以理解为 K-V数据库,因此对每个键值对都会有一个 dictEntry,里面存储了指向 Key 和 Value 的指针;next 指向下一个 dictEntry,与本 …

Redis-常用数据结构

Redis常用数据结构 Redis提供了一些数据结构供我们往Redis中存取数据,最常用的的有5种,字符串(String)、哈希(Hash)、列表(list)、集合(set)、有序集合(ZSET&#xff09…

「Redis数据结构」集合对象(Set)

「Redis数据结构」集合对象(Set) 文章目录 「Redis数据结构」集合对象(Set)一、概述二、结构三、编码转换四、小结 一、概述 Set是Redis中的单列集合,其特点为不保证有序性、保证元素唯一、可以求交集、并集、差集。 …

图解redis五种数据结构底层实现

redis有五种基本数据结构:字符串、hash、set、zset、list。但是你知道构成这五种结构的底层数据结构是怎样的吗?今天我们来花费五分钟的时间了解一下。(目前redis版本为3.0.6) 动态字符串SDS SDS是"simple dynamic string"的缩写。redis中所…

redis底层数据结构-List

举例分析 创建列表对象 numbers 列表对象有两种底层实现结构 1.压缩列表(zipList)实现的列表对象 压缩列表(zipList)是Redis为了节省内存而开发的,是由一系列特殊编码的连续内存块组成的顺序型数据结构,一个压缩列表可以包含任意多个节点(e…

redis中hash数据结构

目录 hash的数据结构ziplist底层实现字典底层实现扩容缩容 引用 hash的数据结构 hash底层数据结构的实现包括两种:ziplist和字典当保存的所有键值对字符串长度小于 64 字节并且键值对数量小于 512 时使用ziplist ,否则使用字典的方式 ziplist底层实现 …

redis的五种数据结构

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 前言 一、字符串(String) 1.SDS的定义 2.SDS与C语言中字符串的区别(优点) 2.1 获取字符串长度 2.2 防止缓冲区的溢…

「Redis数据结构」哈希对象(Hash)

「Redis数据结构」哈希对象(Hash) 文章目录 「Redis数据结构」哈希对象(Hash)一、概述二、编码ZipListHashTable 三、编码转换 一、概述 Redis中hash对象是一个string类型的field和value的映射表,hash特别适合用于存储…

Redis底层数据结构(图文详解)

目录 前言 Redis为什么要使用2个对象?两个对象的好处 redisObject对象解析 String 类型 1、int 整数值实现 2、embstr 3、raw List 类型 1、压缩链表:ziplist 2、双向链表:linkedlist 3、快速列表:quicklist Hash …

Redis数据结构之hash

对象类数据的存储如果具有较频繁的更新需求操作会显得笨重,这里我们可以用redis的hash数据类型解决。 一、hash类型 新的存储需求:对一系列存储的数据进行编组,方便管理,典型应用存储对象信息 需要的存储结构:一个存储…

Redis数据结构之Zset

文章目录 一、zset数据结构二、跳表skipList什么是跳表?1.跳表的查找2.跳表的插入3.跳表的删除4.跳表的更新 一、zset数据结构 相比于set,sorted set 增加了一个权重参数 score,使得集合中的元素能够按 score 进行有序排列,还可以…

Redis数据结构有哪些

Redis数据结构有哪些 一、Redis数据结构 一、Redis数据结构 Redis是一种基于内存的数据库,并且提供一定的持久化功能,它是一种键值(key-value)数据库,使用 key 作为 索引找到当前缓存的数据,并且返回给程序…

「Redis数据结构」压缩列表(ZipList)

「Redis数据结构」压缩列表(ZipList) 文章目录 「Redis数据结构」压缩列表(ZipList)一、概述二、结构三、连锁更新问题四、压缩列表的缺陷五、小结参考 ZipList 是一种特殊的“双端链表” ,由一系列特殊编码的连续内存…

redis数据结构hash

Redis数据结构之hash Hash存储结构 Hash是一个string 类型的field和value的映射表。Hash特别适合存储对象,相对于将对象的每个字段存成单个string 类型。一个对象存储在Hash类型中会占用更少的内存,并且可以更方便的存取整个对象。 我们简单举个实例来…

简述redis数据结构

String:字符串 List:列表 Hash:哈希表 Set:无序集合 Sorted Set:有序集合 bitmap:布隆过滤器 GeoHash:坐标,借助Sorted Set实现,通过zset的score进行排序就可以得到坐标附…

Redis数据结构之——hash

写在前面 以下内容是基于Redis 6.2.6 版本整理总结 一、Redis 数据结构hash的编码格式 Redis中hash数据类型使用了两种编码格式:ziplist(压缩列表)、hashtable(哈希表) 在redis.conf配置文件中,有以下两个参数,意思为:当节点数量小于512并…

Redis底层数据结构详解

Redis底层数据结构详解 我们知道Redis常用的数据结构有五种,String、List、Hash、Set、ZSet,其他的集中数据结构基本上也是用这五种实现的,那么,这五种是Redis提供给你的数据结构,这五种数据结构式怎么实现的&#xf…

Redis的五种基础数据结构

Redis的五种基础数据结构 Redis有5种基础数据结构,分别为:String(字符串),list(列表),hash(字典),set(集合)和zset(有序集合)。 1.String(字符串) 字符串的结构 字符串String是Redis最简单的数据结构,它的内部表示…

Redis的数据结构

一、redis的数据结构 1、String字符串类型 Redis的String能够表示字符串、整数、浮点数三种值的类型 应用场景: 普通的赋值使用incr、decr命令进行递增和递减统计数据。用于实现乐观锁watch(事物)setNx实现分布式锁 底层数据类型: // 数据结构 stru…

为了拿捏 Redis 数据结构,我画了 40 张图(完整版)

大家好,我是小林。 Redis 为什么那么快? 除了它是内存数据库,使得所有的操作都在内存上进行之外,还有一个重要因素,它实现的数据结构,使得我们对数据进行增删查改操作时,Redis 能高效的处理。…