Redis为什么这么快----引入对Redis的思考

Redis为什么这么快----引入对Redis的思考

其实就几点

  1. 高效的数据结构,底层用了大量不同的数据结构等等
  2. 基于内存
  3. 单线程的架构,避免了上下文的切换和多线程带来的竞争,也就不存在加锁释放锁的操作,减少了CPU的消耗,同时采用了采用了非阻塞IO多路复用机制

接下来针对这几点来聊聊Redis。

1、Redis 的底层数据结构一共有6种,分别是,简单动态字符串,双向链表,压缩列表,哈希表,跳表和整数数组,它们和数据类型的对应关系如下图所示:

在这里插入图片描述

2、基于内存为什么会快呢,如图所示

在这里插入图片描述
在这里插入图片描述

像Redis这样把数据存在内存中,读写都直接对数据库进行操作,天然地就比硬盘数据库少了到磁盘读取数据的这一步,而这一步恰恰是计算机处理I/O的瓶颈所在。

而且在内存中读取数据,本质上是电信号的传递,相比与硬盘的机械运动传递信号要快得多。

3、单线程的架构

有必要先说下,我们一直说的 Redis 单线程,只是在处理我们的网络请求和键值对读写的时候只有一个线程来处理,一个正式的Redis Server运行的时候肯定是不止一个线程的!

例如Redis进行持久化的时候会 fork了一个子进程 执行持久化操作,还有异步删除、集群数据同步等等。

那为什么redis要用单线程呢?

首先在理想的情况下,我们当然希望线程越多,同时处理的请求数也越多,实则就是吞吐率。但是实际上并不是这样子的,我们来看看这张图。

在这里插入图片描述

为什么会出现这样的情况?

原因是线程有创建和上下文切换的开销,导致并发执行的速度会比串行慢的情况出现。

  • 并发:指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。
  • 并行:指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看,二者都是一起执行的。

不难发现并发在同一时刻只有一条指令执行,只不过进程(线程)在CPU中快速切换,速度极快,给人看起来就是“同时运行”的印象,实际上同一时刻只有一条指令进行。但实际上如果我们在一个应用程序中使用了多线程,线程之间的轮换以及上下文切换是需要花费很多时间的

关于上下文切换和线程之间的轮换,这里推荐大家一本书:JAVA并发编程的艺术

那怎么提高单线程下的速度呢?

四种IO模型

当一个网络IO发生(假设是read)时,它会涉及两个系统对象,一个是调用这个IO的进程,另一个是系统内核。

当一个read操作发生时,它会经历两个阶段:

  1. 等待数据准备。
  2. 将数据从内核拷贝到进程中。

为了解决网络IO中的问题,提出了4中网络IO模型:

  • 阻塞IO模型
  • 非阻塞IO模型
  • 多路复用IO模型
  • 异步IO模型

阻塞和非阻塞的概念描述的是用户线程调用内核IO操作的方式:阻塞时指IO操作需要彻底完成后才返回到用户空间;而非阻塞是指IO操作被调用后立即返回给用户一个状态值,不需要等到IO操作彻底完成。

阻塞IO模型

对于网络IO来说,很多时候数据在一开始还没到达时(比如还没有收到一个完整的TCP包),系统内核就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。

在这里插入图片描述

当应用进程调用了recvfrom这个系统调用后,系统内核就开始了IO的第一个阶段:准备数据。

当系统内核一直等到数据准备好了,它就会将数据从系统内核中拷贝到用户内存中,然后系统内核返回结果,用户进程才解除阻塞的状态,重新运行起来。所以,阻塞IO模型的特点就是在IO执行的两个阶段(等待数据和拷贝数据)都被阻塞了。

非阻塞IO模型

在Linux中,可以通过设置socket使IO变为非阻塞状态。当对一个非阻塞的socket执行read操作时,读操作流程如下图所示:在这里插入图片描述

从图中可以看出,当用户进程发出 read 操作时,如果内核中的数据还没有准备好,那么它不会阻塞用户进程,而是立刻返回一个错误。

从用户进程角度讲,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。当用户进程判断结果是一个错误时,它就知道数据还没有准备好,于是它可以再次发送read操作。

一旦内核中的数据准备好了,并且又再次收到了用户进程的系统调用,那么它马上就将数据复制到了用户内存中,然后返回正确的返回值。

所以,在非阻塞式IO中,用户进程其实需要不断地主动询问kernel数据是否准备好。非阻塞的接口相比阻塞型接口的显著差异在于被调用之后立即返回。

多路复用IO模型

多路IO复用,有时也称为事件驱动IO(Reactor设计模式)。它的基本原理就是有个函数会不断地轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程,多路IO复用模型的流程如图所示:

在这里插入图片描述

当用户进程调用了select,那么整个进程会被阻塞,而同时,内核会"监视"所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从内核拷贝到用户进程。

这个模型和阻塞IO的模型其实并没有太大的不同,事实上还更差一些。因为这里需要使用两个系统调用(select和recvfrom),而阻塞IO只调用了一个系统调用(recvfrom)。但是,用select的优势在于它可以同时处理多个连接。所以,如果系统的连接数不是很高的话,使用select/epoll的web server不一定比使用多线程的阻塞IO的web server性能更好,可能延迟还更大;select/epoll的优势并不是对单个连接能处理得更快,而是在于能处理更多的连接。

如果select()发现某句柄捕捉到了"可读事件",服务器程序应及时做recv()操作,并根据接收到的数据准备好待发送数据,并将对应的句柄值加入writefds,准备下一次的"可写事件"的select()检测。同样,如果select()发现某句柄捕捉到"可写事件",则程序应及时做send()操作,并准备好下一次的"可读事件"检测准备。

其实就是一个轮询的过程。

IO多路复用是最常使用的IO模型,但是其异步程度还不够“彻底”,因为它使用了会阻塞线程的select系统调用。因此IO多路复用只能称为异步阻塞IO,而非真正的异步IO。

异步IO模型

“真正”的异步IO需要操作系统更强的支持。如下展示了异步 IO 模型的运行流程(Proactor设计模式):

在这里插入图片描述

用户进程发起read操作之后,立刻就可以开始去做其他的事;而另一方面,从内核的角度,当它收到一个异步的read请求操作之后,首先会立刻返回,所以不会对用户进程产生任何阻塞。

然后,内核会等待数据准备完成,然后将数据拷贝到用户内存中,当这一切都完成之后,内核会给用户进程发送一个信号,返回read操作已完成的信息。
Redis中的I/O多路复用

在这里插入图片描述

文件事件是对套接字操作的抽象,每当一个套接字准备好执行连接应答、写入、读取、关闭等操作时,就会产生一个文件事件。因为一个服务器通常会连接多个套接字,所以多个文件事件有可能会并发地出现。I/O 多路复用程序负责监听多个套接字,并向文件事件分派器传送那些产生了事件的套接字。

那如果多个事件同时发生,那分派给哪个处理器呢

会产生一个文件事件。因为一个服务器通常会连接多个套接字,所以多个文件事件有可能会并发地出现。I/O 多路复用程序负责监听多个套接字,并向文件事件分派器传送那些产生了事件的套接字。

那如果多个事件同时发生,那分派给哪个处理器呢

Redis 的 I/O多路复用程序总是会将所有产生事件的套接字都放到一个队列里面,然后通过这个队列,以有序、同步、每次一个套接字的方式向文件事件分派器传送套接字。当上一个套接字产生的事件被处理完毕之后,I/O 多路复用程序才会继续向文件事件分派器传送下一个套接字。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值