乐趣区

关于redis:redisredis为什么这么快

作为一名服务端工程师,工作中你必定和 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 多路复用模型同时监听客户端连贯;
  • 单线程在执行过程中不须要进行上下文切换,缩小了耗时。
退出移动版