操作系统学习笔记 ---- 网络系统

1 DMA技术

直接内存访问(Direct Memory Access) 技术。

在进行 I/O 设备和内存的数据传输的时候,数据搬运的工作全部交给 DMA 控制器,而 CPU 不再参与任何与数据搬运相关的事情,这样 CPU 就可以去处理别的事务。

DMA将磁盘控制器缓冲区的数据拷贝到内存缓冲区(该过程不占用CPU)

传统的文件传输:
数据读取和写入是从用户空间到内核空间来回复制,而内核空间的数据是通过操作系统层面的 I/O 接口从磁盘读取或写入。
在这里插入图片描述
期间共发生了 4 次用户态与内核态的上下文切换,还发生了 4 次数据拷贝

要想提高文件传输的性能,就需要减少「用户态与内核态的上下文切换」和「内存拷贝」的次数。

2 零拷贝

零拷贝技术实现的方式通常有 2 种:

  • mmap + write
  • sendfile

mmap + write

用 mmap() 替换 read() 系统调用函数

mmap() 系统调用函数会直接把内核缓冲区里的数据「映射」到用户空间,这样,操作系统内核与用户空间就不需要再进行任何的数据拷贝操作。

在这里插入图片描述
减少一次数据拷贝但仍然需要 4 次上下文切换,因为系统调用还是 2 次

sendfile

可以替代前面的 read() 和 write() 这两个系统调用,这样就可以减少一次系统调用
直接把内核缓冲区里的数据拷贝到 socket 缓冲区里,不再拷贝到用户态
在这里插入图片描述
只有2 次上下文切换,和 3 次数据拷贝

网卡支持 SG-DMA(The Scatter-Gather Direct Memory Access)技术(和普通的 DMA 有所不同)
Linux 系统通过下面这个命令,查看网卡是否支持 scatter-gather 特性:
在这里插入图片描述
对于支持网卡支持 SG-DMA 技术的情况下,sendfile() 系统调用的过程发生变化
在这里插入图片描述
只进行了 2 次数据拷贝。即这就是零拷贝(Zero-copy)技术,因为我们没有在内存层面去拷贝数据,也就是说全程没有通过 CPU 来搬运数据,所有的数据都是通过 DMA 来进行传输的。

只需要 2 次上下文切换和数据拷贝次数,就可以完成文件的传输,
而且 2 次的数据拷贝过程,都不需要通过 CPU,2 次都是由 DMA 来搬运。

零拷贝技术可以把文件传输的性能提高至少一倍以上

大文件传输用什么方式实现?

绕开 PageCache 的 I/O 叫直接 I/O,使用 PageCache 的 I/O 则叫缓存 I/O。通常,对于磁盘,异步 I/O 只支持直接 I/O。
在高并发的场景下,针对大文件的传输的方式,应该使用「异步 I/O + 直接 I/O」来替代零拷贝技术。

直接 I/O 应用场景常见的两种:

应用程序已经实现了磁盘数据的缓存,那么可以不需要 PageCache 再次缓存,减少额外的性能损耗。在 MySQL 数据库中,可以通过参数设置开启直接 I/O,默认是不开启;
传输大文件的时候,由于大文件难以命中 PageCache 缓存,而且会占满 PageCache 导致「热点」文件无法充分利用缓存,从而增大了性能开销,因此,这时应该使用直接 I/O。

由于直接 I/O 绕过了 PageCache,就无法享受内核的这两点的优化:

内核的 I/O 调度算法会缓存尽可能多的 I/O 请求在 PageCache 中,最后「合并」成一个更大的 I/O 请求再发给磁盘,这样做是为了减少磁盘的寻址操作;
内核也会「预读」后续的 I/O 请求放在 PageCache 中,一样是为了减少对磁盘的操作;

传输文件的时候,我们要根据文件的大小来使用不同的方式:

· 传输大文件的时候,使用「异步 I/O + 直接 I/O」;
· 传输小文件的时候,则使用「零拷贝技术」;

3 I/O 多路复用

一个进程虽然任一时刻只能处理一个请求,但是处理每个请求的事件时,耗时控制在 1 毫秒以内,这样 1 秒内就可以处理上千个请求,把时间拉长来看,多个请求复用了一个进程(思想类似一个CPU并发多个进程)

select/poll/epoll 这是三个多路复用接口

3.1 select/poll

select实现多路复用的方式是

将已连接的 Socket 都放到一个文件描述符集合,
调用 select 函数将文件描述符集合拷贝到内核里,
内核通过遍历文件描述符集合的方式检查是否有网络事件产生,
当检查到有事件产生后,将此 Socket 标记为可读或可写,
再把整个文件描述符集合拷贝回用户态里,
用户态再遍历找到可读或可写的 Socket,然后再对其处理。

该方式需要 2 次遍历文件描述符集合(一次内核态、一次用户态),发生 2 次拷贝文件描述符集合(从用户态到内核态,内核修改,再从内核态到用户态)

select 使用固定长度的 BitsMap,表示文件描述符集合 ,所支持的文件描述符个数是有限制的

poll
使用动态数组,以链表形式来组织,突破了 select 的文件描述符个数限制(还会受到系统文件描述符限制)

poll 和 select 并没有太大的本质区别,都是使用「线性结构」存储进程关注的 Socket 集合,因此都需要遍历文件描述符集合来找到可读或可写的 Socket,时间复杂度为 O(n),而且也需要在用户态与内核态之间拷贝文件描述符集合

3.2 epoll

1、在内核使用红黑树跟踪进程所有待检测的文件描述字,将需要监控的socket通过 epoll_ctl() 函数传入内核红黑树。因内核维护了红黑树,可保存所有待检测的socket,所以只需传一个待检测的socket

2、使用事件驱动的机制,内核维护一个链表来记录就绪事件,当某个socket有事件发生时,通过回调函数,内核将其加入到就绪事件链表,当用户调用 epoll_wait() 函数时,只返回有事件发生的文件描述符个数。

在这里插入图片描述
epoll_wait 实现的内核代码中调用了 __put_user 函数,这个函数就是将数据从内核拷贝到用户空间。

epoll 支持两种事件触发模式,分别是边缘触发(edge-triggered,ET)水平触发(level-triggered,LT)

  • 使用边缘触发模式时,当被监控的 Socket 描述符上有可读事件发生时,服务器端只会从 epoll_wait 中苏醒一次,即使进程没有调用 read 函数从内核读取数据,也依然只苏醒一次,因此我们程序要保证一次性将内核缓冲区的数据读取完;

  • 使用水平触发模式时,当被监控的 Socket 上有可读事件发生时,服务器端不断地从 epoll_wait 中苏醒,直到内核缓冲区数据被 read 函数读完才结束,目的是告诉我们有数据需要读取;

两者区别:

水平触发的意思是只要满足事件的条件,比如内核中有数据需要读,就一直不断地把这个事件传递给用户;
而边缘触发的意思是只有第一次满足条件的时候才触发,之后就不会再传递同样的事件了。

边缘触发模式一般和非阻塞 I/O 搭配使用

边缘触发的效率比水平触发的效率要高,因为边缘触发可以减少 epoll_wait 的系统调用次数,系统调用也是有一定的开销的的,毕竟也存在上下文的切换。

select/poll 只有水平触发模式,epoll 默认的触发模式是水平触发,但是可以根据应用场景设置为边缘触发模式。

4 高性能网络模式

4.1 Reactor

Reactor 模式也叫 Dispatcher 模式,I/O 多路复用监听事件,收到事件后,根据事件类型分配(Dispatch)给某个进程 / 线程。

Reactor 模式主要由 Reactor 和处理资源池这两个核心部分组成,它俩负责的事情如下:

Reactor 负责监听和分发事件,事件类型包含连接事件、读写事件;
处理资源池负责处理事件,如 read -> 业务逻辑 -> send;

4.1.1 单 Reactor 单进程 / 线程

C语言实现的是单Reactor 单进程;JAVA语言实现的是单Reactor 单线程
「单 Reactor 单进程」的方案示意图:
在这里插入图片描述
「单 Reactor 单进程」方案:

Reactor 对象通过 select (IO 多路复用接口) 监听事件,收到事件后通过 dispatch 进行分发,具体分发给 Acceptor 对象还是 Handler 对象,还要看收到的事件类型;
如果是连接建立的事件,则交由 Acceptor 对象进行处理,Acceptor 对象会通过 accept 方法 获取连接,并创建一个 Handler 对象来处理后续的响应事件;
如果不是连接建立事件, 则交由当前连接对应的 Handler 对象来进行响应;
Handler 对象通过 read -> 业务处理 -> send 的流程来完成完整的业务流程。

缺点:

1、无法充分利用多核CPU性能
2、Handler对象处理业务时,整个进程无法处理其它连接事件,如果处理时间过长,会造成响应的延迟

单 Reactor 单进程的方案不适用计算机密集型的场景,只适用于业务处理非常快速的场景

4.1.2 单 Reactor 多线程 / 多进程

「单 Reactor 多线程」方案的示意图如下:
在这里插入图片描述
「单 Reactor 多线程」方案:

Reactor 对象通过 select (IO 多路复用接口) 监听事件,收到事件后通过 dispatch 进行分发,具体分发给 Acceptor 对象还是 Handler 对象,还要看收到的事件类型;
如果是连接建立的事件,则交由 Acceptor 对象进行处理,Acceptor 对象会通过 accept 方法 获取连接,并创建一个 Handler 对象来处理后续的响应事件;
如果不是连接建立事件, 则交由当前连接对应的 Handler 对象来进行响应;
Handler 对象不再负责业务处理,只负责数据的接收和发送,Handler 对象通过 read 读取到数据后,会将数据发给子线程里的 Processor 对象进行业务处理;
子线程里的 Processor 对象就进行业务处理,处理完后,将结果发给主线程中的 Handler 对象,接着由 Handler 通过 send 方法将响应结果发送给 client;

一个 Reactor 对象承担所有事件的监听和响应,而且只在主线程中运行,在面对瞬间高并发的场景时,容易成为性能的瓶颈的地方

4.1.3 多 Reactor 多进程 / 线程

「多 Reactor 多线程」方案示例图:
在这里插入图片描述
方案详细说明如下:

主线程中的 MainReactor 对象通过 select 监控连接建立事件,收到事件后通过 Acceptor 对象中的 accept 获取连接,将新的连接分配给某个子线程;
子线程中的 SubReactor 对象将 MainReactor 对象分配的连接加入 select 继续进行监听,并创建一个 Handler 用于处理连接的响应事件。
如果有新的事件发生时,SubReactor 对象会调用当前连接对应的 Handler 对象来进行响应。
Handler 对象通过 read -> 业务处理 -> send 的流程来完成完整的业务流程。

多 Reactor 多线程的方案虽然看起来复杂的,但是实际实现时比单 Reactor 多线程的方案要简单的多,原因如下:

  • 主线程和子线程分工明确,主线程只负责接收新连接,子线程负责完成后续的业务处理。

  • 主线程和子线程的交互很简单,主线程只需要把新连接传给子线程,子线程无须返回数据,直接就可以在子线程将处理结果发送给客户端。

4.2 Proactor

Reactor 是非阻塞同步网络模式,而 Proactor 是异步网络模式

  • Reactor 是非阻塞同步网络模式,感知的是就绪可读写事件。在每次感知到有事件发生(比如可读就绪事件)后,就需要应用进程主动调用 read 方法来完成数据的读取,也就是要应用进程主动将 socket 接收缓存中的数据读到应用进程内存中,这个过程是同步的,读取完数据后应用进程才能处理数据。

  • Proactor 是异步网络模式, 感知的是已完成的读写事件。在发起异步读写请求时,需要传入数据缓冲区的地址(用来存放结果数据)等信息,这样系统内核才可以自动帮我们把数据的读写工作完成,这里的读写工作全程由操作系统来做,并不需要像 Reactor 那样还需要应用进程主动发起 read/write 来读写数据,操作系统完成读写工作后,就会通知应用进程直接处理数据。

Proactor 模式的示意图:
在这里插入图片描述
工作流程:

Proactor Initiator 负责创建 Proactor 和 Handler 对象,并将 Proactor 和 Handler 都通过 Asynchronous Operation Processor 注册到内核;
Asynchronous Operation Processor 负责处理注册请求,并处理 I/O 操作;
Asynchronous Operation Processor 完成 I/O 操作后通知 Proactor;
Proactor 根据不同的事件类型回调不同的 Handler 进行业务处理;
Handler 完成业务处理;

5 一致性哈希

负载均衡问题,不同分配策略应对不同场景:如加权轮询,让性能更好的服务器承担更多的请求,但前提是每个服务器上数据是一致的(面对分布式存储,无法应对)

哈希算法,针对同一关键字计算出的哈希值是相同的,可将某一个key确定到一个服务器上,满足分布式系统的负载均衡。

最简单的做法即取模运算,但问题在于如果节点数量发生变化(系统扩容或缩容),必须迁移改变了映射关系的数据

一致性哈希算法

不同于普通的哈希算法,一致哈希算法是对 2^32 进行取模运算,是一个固定的值

对 2^32 进行取模运算的结果值组织成一个圆环
在这里插入图片描述
一致性哈希要进行两步哈希:

  • 第一步:对存储节点进行哈希计算,也就是对存储节点做哈希映射,比如根据节点的 IP 地址进行哈希;
  • 第二步:当对数据进行存储或访问时,对数据进行哈希映射;

一致性哈希是指将「存储节点」和「数据」都映射到一个首尾相连的哈希环上。

  • 首先计算要查询的key的哈希,确定此key在哈希环上映射的位置
  • 然后从该位置顺时针找到第一个节点,即存储该key数据的节点

在一致性哈希算法中,如果增加或删除一个节点,只会影响到该节点在哈希环上顺时针相邻的后继节点

在这里插入图片描述
但一致性哈希算法并不保证节点能够在哈希环上分布均匀

在这里插入图片描述

当节点数量足够大时,哈希环上的节点分布就越均匀

如何通过虚拟节点提高均衡度?

不再将真实节点映射到哈希环上,而是将虚拟节点映射到哈希环上,并将虚拟节点映射到实际节点,所以这里有「两层」映射关系。

在这里插入图片描述
如果有访问请求寻址到「A-01」这个虚拟节点,接着再通过「A-01」虚拟节点找到真实节点 A,这样请求就能访问到真实节点 A 了。

虚拟节点除了会提高节点的均衡度,还会提高系统的稳定性。当节点变化时,会有不同的节点共同分担系统的变化,因此稳定性更高

带虚拟节点的一致性哈希方法不仅适合硬件配置不同的节点的场景,而且适合节点规模会发生变化的场景

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Swing_zzZ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值