前言
Redis
是一款开源的(BSD许可) 使用C
语言编写的,基于内存的键值对(key,value) 数据库,其拥有多种数据类型,例如: 字符串、哈希、列表、集合 以及 有序集合 等,除此之外,还有 键过期、发布订阅等, 不仅如此,Redis
好能够将内存数据落地到磁盘中,以防止机器故障导致数据丢失,所以其使用场景越来越多,从最开始单纯使用Redis
做缓存,而后有艺高人胆大的人直接用Redis
做主存,各位,有想过,Redis
为什么会这么快么? 今天我们就来探究下Redis
为什么这么快。
我们将从一下几个步骤来剖析一下Redis
为什么这么快
- 基于内存存储
- 高效的数据结构
基于内存存储
存储设备层次
Redis
之所以块,是因为将其所有的数据,都放置在内存中的,在此,我们看下存储设备层次结构图。
在如上层次结构中,由上至下,访问速度会越来越慢,容量则越来越大,这里主要来看下内存和本地硬盘的具体效率。
类型 | 访问时间 | 备注 |
内存 | 小于100ns | 操作系统会将内存地址映射为虚拟的地址表,从而通过该地址进行访问的。 |
硬盘 | 小于3ms | 操作系统通过控制驱动程序,将硬盘中的数据加载到内存,供程序访问。 |
其中寄存器和高速缓存是基于CPU的,而内存和硬盘,则需要通过数据总线进行相连接,所以将数据放置内存中,会比放置在磁盘中要快。
数据加载至CPU执行
我们再从执行过程来来看待 通过硬盘 执行程序 和 直接在内存中 访问数据
通过上面模型图,我们可以更加清晰明了的看懂。
当我们的数据在磁盘的时候
我们想要访问磁盘的数据,我们得先将通过磁盘驱动控制器,将数据通过I/0总线
再通过I/O桥
加载进内存,执行的时候,需要将主存储器中的数据通过系统总线,将数据加载进寄存器,进行处理。
当我们数据在主存储器的时候
我们仅需要将主存储器中的数据通过系统总线,将数据加载进寄存器,进行处理。
高效的数据结构
我们知道,Redis
是键值对数据库,所以我们将从键 和 值,分别来分析下其数据结构。
如何存储键
整个Redis
数据库,都可以将其称之为一个hash
表,当我们存储key
的时候,会进行hash
计算,得到下标,而后进行存储,这里我们不妨提出2个问题。
hashMap
碰撞应当如何解决- 在
Redis
中,hashmap
如何进行扩缩容
如何解决hash
碰撞
当遇到hashMap
碰撞时,Redis
采取的方法是通过拉链法来存储多个碰撞的key
,当其链表长度大于1
的时候,则需要进行遍历该链表从而得到我们需要查询的key
。
因为有Rehash
这种情况一般而言是不存在的
假设我们查询juejin
这个key
,通过hash
算法,我们定位到下标为0处,此时发现其链表大于1,发生了hash
碰撞,则需要遍历整个链表来获取其值。
如何进行hashMap扩缩容
这里就介绍一下扩容,rehash
在介绍扩缩容方法的时候,我们要确定在什么时候进行扩容和缩容,一般而言,若hashmap中保存的个数已经大于hashmap大小的时候,此时则会进行扩容。
扩容,我们也称之为rehash,其大概原理如下。
前提
redis有一个标记是否rehash的变量:rehashidx,默认为-1.
若我们hashmap存储如下
绿色代表存储有数据
若hashMap
存储的节点大于等于hashMap
长度时,则会发生扩容,所谓扩容,就是在申请原有hashmap
长度2倍的一个数组。此时再将redis
变量rehashidx
修改为0。此时就进入了rehash
状态。
而后,进行增删改查的时候,都会进行一次rehash
,所谓的rehash
就是将old
数据逐步迁移到new
中,当old
长度为0的时候,就代表rehash
结束。
结束后,就将old
数组释放掉,且将rehashidx
置为-1
。
如何存储值
在Redis
中,我们有以下几种数据类型,分别是 字符串、哈希、列表、集合 以及 有序集合 等 ,其底层关系,我们对应出来后如下:
关于这点,我们想简单介绍下跳表。
所谓的跳表,其实是一种以空间换取时间的方法之一,其效率可以比肩红黑树,其构建要比红黑树简单的多。
我们来介绍下,为什么要有跳表。
对于一个有序的数组和有序的链表,我们不妨来分析一下几点。
例如数据如下:
我们不妨从增删改查来分析一下数组和链表的区别
类型 | 链表 | 数组 |
增 | 增加方便,无非新增节点而已 | 增加不方便,需要扩容数组 |
删 | 删除方便,删除节点修改指针而已 | 删除不方便,需要对数组缩容 |
改 | 方便,修改完毕后,重新排序即可 | 方便,修改完毕后,对数组重新排序即可 |
查 | 不方便,没办法做二分查找 | 方便,有多种查询方式,包括二分 |
从上述列表中,我们可以看出,在增删改中,链表都即为方便,唯独关于查询,链表由于不是通过下标进行存储的,所以没办法用类似于二分查找。
所以跳表,就是为了解决链表查询不方便的问题,其原理如下
我们再原始链表(黄色)的基础上,建立了多个索引,在查询的时候,我们先从最高层开始查询,若查询9
,则查询路线为
在原链表查询,查询路径为: 1 3 5 7 9
需要查询5次,而通过跳表,查询路径为:5 9
,只需要2次即可。
总结
我们在本篇文章介绍了redis
为什么这么快,我们暂时归纳为2个原因
- 基于内存存储
- 有高效的数据结构
这里并没有提及redis
单线程特性,因为我们认为Redis
核心功能不采取多线程,原因是因为Redis
的瓶颈不在Cpu
上面,而在内存和网络上,所以这是不提及的原因。