设为首页 加入收藏

TOP

最近沉迷Redis网络模型,无法自拔!终于知道Redis为啥这么快了(一)
2023-07-25 21:37:06 】 浏览:22
Tags:Redis 终于知

1. 用户空间和内核态空间

1.1 为什么要区分用户和内核

服务器大多都采用 Linux 系统,这里我们以 Linux 为例来讲解:

ubuntu 和 Centos 都是 Linux 的发行版,发行版可以看成对 linux 包了一层壳,任何 Linux 发行版,其系统内核都是 Linux 。我们的应用都需要通过 Linux 内核与硬件交互

file

用户的应用,比如 redis ,mysql 等其实是没有办法去执行访问我们操作系统的硬件的,所以我们可以通过发行版的这个壳子去访问内核,再通过内核去访问计算机硬件
file

计算机硬件包括,如 cpu,内存,网卡等等,内核(通过寻址空间)可以操作硬件的,但是内核需要不同设备的驱动,有了这些驱动之后,内核就可以去对计算机硬件去进行 内存管理,文件系统的管理,进程的管理等等

file

我们想要用户的应用来访问,计算机就必须要通过对外暴露的一些接口,才能访问到,从而简介的实现对内核的操控,但是内核本身上来说也是一个应用,所以他本身也需要一些内存,cpu 等设备资源,用户应用本身也在消耗这些资源,如果不加任何限制,用户去操作随意的去操作我们的资源,就有可能导致一些冲突,甚至有可能导致我们的系统出现无法运行的问题,因此我们需要把用户和内核隔离开

1.2 进程寻址空间

进程的寻址空间划分成两部分:内核空间、用户空间

什么是寻址空间呢?我们的应用程序也好,还是内核空间也好,都是没有办法直接去物理内存的,而是通过分配一些虚拟内存映射到物理内存中,我们的内核和应用程序去访问虚拟内存的时候,就需要一个虚拟地址,这个地址是一个无符号的整数。

比如一个 32 位的操作系统,他的带宽就是 32,他的虚拟地址就是 2 的 32 次方,也就是说他寻址的范围就是 0~2 的 32 次方, 这片寻址空间对应的就是 2 的 32 个字节,就是 4GB,这个 4GB,会有 3 个 GB 分给用户空间,会有 1GB 给内核系统

file

在 linux 中,他们权限分成两个等级,0 和 3,用户空间只能执行受限的命令(Ring3),而且不能直接调用系统资源,必须通过内核提供的接口来访问内核空间可以执行特权命令(Ring0),调用一切系统资源,所以一般情况下,用户的操作是运行在用户空间,而内核运行的数据是在内核空间的,而有的情况下,一个应用程序需要去调用一些特权资源,去调用一些内核空间的操作,所以此时他俩需要在用户态和内核态之间进行切换。

比如:

Linux 系统为了提高 IO 效率,会在用户空间和内核空间都加入缓冲区:

  • 写数据时,要把用户缓冲数据拷贝到内核缓冲区,然后写入设备
  • 读数据时,要从设备读取数据到内核缓冲区,然后拷贝到用户缓冲区

针对这个操作:我们的用户在写读数据时,会去向内核态申请,想要读取内核的数据,而内核数据要去等待驱动程序从硬件上读取数据,当从磁盘上加载到数据之后,内核会将数据写入到内核的缓冲区中,然后再将数据拷贝到用户态的 buffer 中,然后再返回给应用程序,整体而言,速度慢,就是这个原因,为了加速,我们希望 read 也好,还是 wait for data 也最好都不要等待,或者时间尽量的短。

file

2. 网络模型

2.1 阻塞IO

  • 过程 1:应用程序想要去读取数据,他是无法直接去读取磁盘数据的,他需要先到内核里边去等待内核操作硬件拿到数据,这个过程是需要等待的,等到内核从磁盘上把数据加载出来之后,再把这个数据写给用户的缓存区。
  • 过程 2:如果是阻塞 IO,那么整个过程中,用户从发起读请求开始,一直到读取到数据,都是一个阻塞状态。
    file

用户去读取数据时,会去先发起 recvform 一个命令,去尝试从内核上加载数据,如果内核没有数据,那么用户就会等待,此时内核会去从硬件上读取数据,内核读取数据之后,会把数据拷贝到用户态,并且返回 ok,整个过程,都是阻塞等待的,这就是阻塞 IO

总结如下:

顾名思义,阻塞 IO 就是两个阶段都必须阻塞等待:

阶段一:

  • 用户进程尝试读取数据(比如网卡数据)
  • 此时数据尚未到达,内核需要等待数据
  • 此时用户进程也处于阻塞状态

阶段二:

  • 数据到达并拷贝到内核缓冲区,代表已就绪
  • 将内核数据拷贝到用户缓冲区
  • 拷贝过程中,用户进程依然阻塞等待
  • 拷贝完成,用户进程解除阻塞,处理数据

可以看到,阻塞 IO 模型中,用户进程在两个阶段都是阻塞状态。

file

2.2 非阻塞 IO

顾名思义,非阻塞 IO 的 recvfrom 操作会立即返回结果而不是阻塞用户进程

阶段一:

  • 用户进程尝试读取数据(比如网卡数据)
  • 此时数据尚未到达,内核需要等待数据
  • 返回异常给用户进程
  • 用户进程拿到 error 后,再次尝试读取
  • 循环往复,直到数据就绪

阶段二:

  • 将内核数据拷贝到用户缓冲区
  • 拷贝过程中,用户进程依然阻塞等待
  • 拷贝完成,用户进程解除阻塞,处理数据
  • 可以看到,非阻塞 IO 模型中,用户进程在第一个阶段是非阻塞,第二个阶段是阻塞状态。虽然是非阻塞,但性能并没有得到提高。而且忙等机制会导致 CPU 空转,CPU 使用率暴增。

file

2.3 信号驱动

信号驱动 IO 是与内核建立 SIGIO 的信号关联并设置回调,当内核有 FD 就绪时,会发出 SIGIO 信号通知用户,期间用户应用可以执行其它业务,无需阻塞等待。

阶段一:

  • 用户进程调用 sigaction ,注册信号处理函数
  • 内核返回成功,开始监听 FD
  • 用户进程不阻塞等待,可以执行其它业务
  • 当内核数据就绪后,回调用户进程的 SIGIO 处理函数

阶段二:

  • 收到 SIGIO 回调信号
  • 调用 recvfrom ,读取
  • 内核将数据拷贝到用户空间
  • 用户进程处理数据

file
当有大量 IO 操作时,信号较多,SIGIO 处理函数不能及时处理可能导致信号队列溢出,而且内核空间与用户空间的频繁信号交互性能也较低。

2.4 异步 IO

这种方式,不仅仅是用户态在试图读取数据后,不阻塞,而且当内核的数据准备完成后,也不会阻塞

他会由内核将所有数据处理完成后,由内核将数据写入到用户态中,然后才算完成,所以性能极高,不会有任何阻塞,全部都由内核完成,可以看到,异步 IO 模型中,用户进程在两个阶段都是非阻塞状态。

file

2.5 IO 多路复用

场景引入

为了更好的理解 IO ,现在假设这样一种场景:一家餐厅

  • A 情况:这家餐厅中现在只有一位服务员,并且采用客户排队点餐的方式,就像这样:

file
每排到一位客户要吃到饭,都要经过两个步骤:

思考要吃什么
顾客开始点餐,厨师开始炒菜

由于餐厅只有一位服务员,因此一次只能服务一位客户,并且还需要等待当前客户思考出结果,这浪费了后续排队的人非常多的时间,效率极低。这就是阻塞 IO。

当然,为了缓解这种情况,老板完全可以多雇几个人,但这也会增加成本,而在极大客流量的情况下,仍然不会有很高的效率提升

  • B 情况: 这家餐厅中现在只有一位服务员,并且采用客户排队点餐的方式。

每排到一位客户要吃到饭,都要经过两个步骤:

  1. 思考要吃什么
  2. 顾客开始点餐,厨师开始炒菜

与 A 情况不同的是,此时服务员会不断询问顾客:“你想吃番茄鸡蛋盖浇饭吗?那滑蛋牛肉呢?那肉末茄子呢?……”

虽然服务员在不停的问,但是在网络中,这并不会增加数据的就绪速度,主要还是等顾客自己确定。所以,这并不会提高餐厅的效率,说不定还会招来更多差评。这就是非阻塞 IO。

  • C 情况: 这家餐厅中现在只有一位服务员,但是不再采用客户排队的方式,而是顾客自己获取菜单并点餐,点完后通知服务员,就像这样:

file

每排到一位客户要吃

首页 上一页 1 2 3 下一页 尾页 1/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇搭建自己的网络商城-概要设计 下一篇Redis这个内存回收,确实有点牛逼..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目