共计 3081 个字符,预计需要花费 8 分钟才能阅读完成。
作为一名服务端工程师,工作中你必定和 Redis 打过交道。Redis 为什么快,这点想必你也晓得,至多为了面试也做过筹备。很多人晓得 Redis 快仅仅因为它是基于内存实现的,对于其它起因倒是不置可否。
那么明天就和小莱一起看看:
- 思维导图 –
基于内存实现
这点在一开始就提到过了,这里再简略说说。
Redis 是基于内存的数据库,那不可避免的就要与磁盘数据库做比照。对于磁盘数据库来说,是须要将数据读取到内存里的,这个过程会受到磁盘 I/O 的限度。
而对于内存数据库来说,自身数据就存在于内存里,也就没有了这方面的开销。
高效的数据结构
Redis 中有多种数据类型,每种数据类型的底层都由一种或多种数据结构来反对。正是因为有了这些数据结构,Redis 在存储与读取上的速度才不受妨碍。这些数据结构有什么特地的中央,各位看官接着往下看:
1、简略动静字符串
这个名词可能你不相熟,换成 SDS 必定就晓得了。这是用来解决字符串的。理解 C 语言的都晓得,它是有解决字符串办法的。而 Redis 就是 C 语言实现的,那为什么还要反复造轮子?咱们从以下几点来看:
(1)字符串长度解决
这个图是字符串在 C 语言中的存储形式,想要获取「Redis」的长度,须要从头开始遍历,直到遇到 ‘\0’ 为止。
Redis 中怎么操作呢?用一个 len 字段记录以后字符串的长度。想要获取长度只须要获取 len 字段即可。你看,差距不言自明。前者遍历的工夫复杂度为 O(n),Redis 中 O(1) 就能拿到,速度显著晋升。
(2)内存重新分配
C 语言中波及到批改字符串的时候会从新分配内存。批改地越频繁,内存调配也就越频繁。而内存调配是会耗费性能的,那么性能降落在劫难逃。
而 Redis 中会波及到字符串频繁的批改操作,这种内存调配形式显然就不适宜了。于是 SDS 实现了两种优化策略:
- 空间预调配
对 SDS 批改及空间裁减时,除了调配所必须的空间外,还会额定调配未应用的空间。
具体调配规定是这样的:SDS 批改后,len 长度小于 1M,那么将会额定调配与 len 雷同长度的未应用空间。如果批改后长度大于 1M,那么将调配 1M 的应用空间。
- 惰性空间开释
当然,有空间调配对应的就有空间开释。
SDS 缩短时,并不会回收多余的内存空间,而是应用 free 字段将多进去的空间记录下来。如果后续有变更操作,间接应用 free 中记录的空间,缩小了内存的调配。
(3)二进制平安
你曾经晓得了 Redis 能够存储各种数据类型,那么二进制数据必定也不例外。但二进制数据并不是规定的字符串格局,可能会蕴含一些非凡的字符,比方 ‘\0’ 等。
后面咱们提到过,C 中字符串遇到 ‘\0’ 会完结,那 ‘\0’ 之后的数据就读取不上了。但在 SDS 中,是依据 len 长度来判断字符串完结的。
看,二进制平安的问题就解决了。
2、双端链表
列表 List 更多是被当作队列或栈来应用的。队列和栈的个性一个先进先出,一个先进后出。双端链表很好的反对了这些个性。
– 双端链表 –
(1)前后节点
链表里每个节点都带有两个指针,prev 指向前节点,next 指向后节点。这样在工夫复杂度为 O(1) 内就能获取到前后节点。
(2)头尾节点
你可能留神到了,头节点里有 head 和 tail 两个参数,别离指向头节点和尾节点。这样的设计可能对双端节点的解决工夫复杂度降至 O(1),对于队列和栈来说再适宜不过。同时链表迭代时从两端都能够进行。
(3)链表长度
头节点里同时还有一个参数 len,和上边提到的 SDS 里相似,这里是用来记录链表长度的。因而获取链表长度时不必再遍历整个链表,间接拿到 len 值就能够了,这个工夫复杂度是 O(1)。
你看,这些个性都升高了 List 应用时的工夫开销。
3、压缩列表
双端链表咱们曾经相熟了。不晓得你有没有留神到一个问题:如果在一个链表节点中存储一个小数据,比方一个字节。那么对应的就要保留头节点,前后指针等额定的数据。
这样就节约了空间,同时因为重复申请与开释也容易导致内存碎片化。这样内存的应用效率就太低了。
于是,压缩列表上场了!
它是通过非凡编码,专门为了晋升内存应用效率设计的。所有的操作都是通过指针与解码进去的偏移量进行的。
并且压缩列表的内存是间断调配的,遍历的速度很快。
4、字典
Redis 作为 K-V 型数据库,所有的键值都是用字典来存储的。
日常学习中应用的字典你应该不会生疏,想查找某个词通过某个字就能够间接定位到,速度十分快。这里所说的字典原理上是一样的,通过某个 key 能够间接获取到对应的 value。
字典又称为哈希表,这点没什么可说的。哈希表的个性大家都很分明,可能在 O(1) 工夫复杂度内取出和插入关联的值。
5、跳跃表
作为 Redis 中特有的数据结构 - 跳跃表,其在链表的根底上减少了多级索引来晋升查找效率。
这是跳跃表的简略原理图,每一层都有一条有序的链表,最底层的链表蕴含了所有的元素。这样跳跃表就能够反对在 O(logN) 的工夫复杂度里查找到对应的节点。
上面这张是跳表实在的存储构造,和其它数据结构一样,都在头节点里记录了相应的信息,缩小了一些不必要的零碎开销。
正当的数据编码
对于每一种数据类型来说,底层的反对可能是多种数据结构,什么时候应用哪种数据结构,这就波及到了编码转化的问题。
那咱们就来看看,不同的数据类型是如何进行编码转化的:
String:存储数字的话,采纳 int 类型的编码,如果是非数字的话,采纳 raw 编码;
List:字符串长度及元素个数小于肯定范畴应用 ziplist 编码,任意条件不满足,则转化为 linkedlist 编码;
Hash:hash 对象保留的键值对内的键和值字符串长度小于肯定值及键值对;
Set:保留元素为整数及元素个数小于肯定范畴应用 intset 编码,任意条件不满足,则应用 hashtable 编码;
Zset:zset 对象中保留的元素个数小于及成员长度小于肯定值应用 ziplist 编码,任意条件不满足,则应用 skiplist 编码。
适合的线程模型
Redis 快的起因还有一个是因为应用了适合的线程模型:
1、I/ O 多路复用模型
- I/O:网络 I/O
- 多路 :多个 TCP 连贯
- 复用 :共用一个线程或过程
生产环境中的应用,通常是多个客户端连贯 Redis,而后各自发送命令至 Redis 服务器,最初服务端解决这些申请返回后果。
应答大量的申请,Redis 中应用 I/O 多路复用程序同时监听多个套接字,并将这些事件推送到一个队列里,而后一一被执行。最终将后果返回给客户端。
2、防止上下文切换
你肯定据说过,Redis 是单线程的。那么单线程的 Redis 为什么会快呢?
因为多线程在执行过程中须要进行 CPU 的上下文切换,这个操作比拟耗时。Redis 又是基于内存实现的,对于内存来说,没有上下文切换效率就是最高的。屡次读写都在一个 CPU 上,对于内存来说就是最佳计划。
3、单线程模型
顺便提一下,为什么 Redis 是单线程的。
Redis 中应用了 Reactor 单线程模型,你可能对它并不相熟。没关系,只须要大略理解一下即可。
这张图里,接管到用户的申请后,全副推送到一个队列里,而后交给文件事件分派器,而它是单线程的工作形式。Redis 又是基于它工作的,所以说 Redis 是单线程的。
总结
基于内存实现
- 数据都存储在内存里,缩小了一些不必要的 I/O 操作,操作速率很快。
高效的数据结构
- 底层多种数据结构反对不同的数据类型,反对 Redis 存储不同的数据;
- 不同数据结构的设计,使得数据存储工夫复杂度降到最低。
正当的数据编码
- 依据字符串的长度及元素的个数适配不同的编码格局。
适合的线程模型
- I/O 多路复用模型同时监听客户端连贯;
- 单线程在执行过程中不须要进行上下文切换,缩小了耗时。