怎么深入理解Linux高性能网络架构

57次阅读
没有评论

共计 4175 个字符,预计需要花费 11 分钟才能阅读完成。

本篇文章为大家展示了怎么深入理解 Linux 高性能网络架构,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。 

1. 落寞的小黑

上周北京很冷,周五晚上大白下班奔地铁站,收到了好基友小黑的微信:

于是大白掉头扫了个单车奔五道口了,小黑靠谱地选了个不错的位置。

小黑: 你今天下班挺早呀!

大白: 就咱这觉悟,心里有工作,哪里都是办公桌,不要拘泥于形式嘛。

明显能感觉得到小黑哥最近好像比较累,之前眼里 bulingbuling 闪的光是看不到了。

大白: 下午去面的哪家? 啥岗位? 咋样?

小黑: 是一家做自动驾驶的创业公司,网站是看团队介绍还不错,就去看看了,这次没咋准备,很多问题其实都熟悉,但是回答的不到位。

大白: 哦,明白了,那就是当时理解的不到位,稀里糊涂过去了,现在忽然问起来,想不起重点。

小黑: 差不多吧,问我都做过哪些高性能的网络框架模型,也就是 IO 和事件驱动那一套。

话说完,小黑喝了一大口啤酒,大白看出了小黑心里有一些落寞。毕竟在帝都这个地方竞争和工作压力,以及生活琐事都一直围绕着我们,但是金钱和好运都巧妙地避开了自己 …

想到这里,大白也深深喝了一大口,我命由我不由天,开整!

大白:黑哥,你说这个问题确实不好回答,全是术语和略带歧义的东西,我觉得我们抓住本质去阐述就好。

小黑:来,请开始你的表演,我学习学习。

大白决定和小黑好好聊聊,Linux 开发中常用的高性能网络框架中的一些事儿,火锅的映衬下让夜色和天气都不那么寒冷了。

通过本文你将会了解到以下内容:

IO 事件和 IO 复用

线程模型和事件驱动模型的架构

基于事件驱动的 Reactor 模式详解

同步 IO 和异步 IO 简介

2. IO 事件和 IO 复用

2.1 什么是 IO 事件

IO 指的是输入 Input/ 输出 Output,但是从汉语角度来说,出和入是相对的,所以我们需要个参照物。这里我们的参照物选择为程序运行时的主存储空间,外部通常包括网卡、磁盘等。有了上述的设定理解起来就方便多了,我们来一起看下:

IO 的本质是数据的流动,数据可以从网卡到程序内存,也可以从程序内存写到网卡,磁盘操作也是如此。

所以可以把常见的 IO 分为:

网络 IO:内存和网卡的数据交互

文件 IO:内存和磁盘的数据交互

那什么又是 IO 事件呢? 事件可以理解为一种状态或者动作,也就是状态的迁移会触发一种相应的动作。网络 IO 的事件通常包括:

可读事件

可写事件

异常事件

理解可读可写事件是非常有必要的,一般来说一个 socket 大部分时候是可写的,但是并不是都可读。可读一般代表是一个新连接或者原有连接有新数据交互,对于服务端程序来说也是重点关注的事件。

2.2 什么是 IO 复用

设想假如有几万个 IO 事件,那么应用程序该如何管理呢? 这就要提到 IO 复用了。IO 复用从本质上来说就是应用程序借助于 IO 复用函数向内核注册很多类型的 IO 事件,当这些注册的 IO 事件发生变化时内核就通过 IO 复用函数来通知应用程序。

从图中可以看到,IO 复用中复用的就是一个负责监听管理这些 IO 事件的线程。之所以可以实现一个线程管理成百上千个 IO 事件,是因为大部分时间里某个时刻只有少量 IO 事件被触发。

大概就像这样:草原上的一只大狗可以看管几十只绵羊,因为大部分时候只有个别绵羊不守规矩乱跑,其他的都是乖乖吃草。

3. 网络框架设计要素

要理解网络框架有哪些,必须要清楚网络框架完成了哪些事情。

大致描述下这个请求处理的流程:

远端的机器 A 发送了一个 HTTP 请求到服务器 B,此时服务器 B 网卡接收到数据并产生一个 IO 可读事件;

我们以同步 IO 为例,此时内核将该可读事件通知到应用程序的 Listen 线程;

Listen 线程将任务甩给 Handler 线程,由 Handler 将数据从内核读缓冲区拷贝到用户空间读缓冲区;

请求数据包在应用程序内部进行计算和处理并封装响应包;

Handler 线程等待可写事件的到来;

当这个连接可写时将数据从用户态写缓冲区拷贝到内核缓冲区,并通过网卡发送出去;

备注:上述例子是以同步 IO 为例,并且将线程中的角色分为 Listen 线程、Handler 线程、Worker 线程,分别完成不同的工作,后续会详细展开。

所以我们可以知道,要完成一个数据交互,涉及了几大块内容:

IO 事件监听

数据拷贝

数据处理和计算

大白认为,这三大块内容,不论什么形式的框架都绕不开,也是理解网络架构的关键所在。

4. 高性能网络框架实践

4.1 基于线程模型

在早期并发数不多的场景中,有一种 One Request One  Thread 的架构模式。该模式下每次接收一个新请求就创建一个处理线程,线程虽然消耗资源并不多,但是成千上万请求打过来,性能也是扛不住的。

这是一种比较原始的架构,思路也非常清晰,创建多个线程来提供处理能力,但在高并发生产环境中几乎没有应用,本文不再展开。

4.2 基于事件驱动模型

当前流行的是基于事件驱动的 IO 复用模型,相比多线程模型优势很明显。

在此我们先理解一下什么是事件驱动 Event-Drive-Model。

事件驱动编程是一种编程范式,程序的执行流由外部事件来决定,它的特点是包含一个事件循环,当外部事件发生时使用回调机制来触发相应的处理。

通俗来说就是:有一个循环装置在一直等待各种事件的到来,并将到达的事件放到队列中,再由一个分拣装置来调用对应的处理装置来响应。

4.3 Reactor 反应堆模式

第一次听到这个模式的时候很困惑,究竟反应堆是个啥? 研究了一下发现,反应堆是个核物理的概念,大致是这个样子的:

核反应堆是核电站的心脏,它的工作原理是这样的:原子由原子核与核外电子组成,原子核由质子与中子组成。

当铀 235 的原子核受到外来中子轰击时,一个原子核会吸收一个中子分裂成两个质量较小的原子核,同时放出 2 - 3 个中子。

这裂变产生的中子又去轰击另外的铀 235 原子核,引起新的裂变,如此持续进行就是裂变的链式反应。

结合这种核裂变的图,好像是一个请求打过来,服务器内部瞬间延伸出很多分支来完成响应,一变二,二变四,甚至更多,确实有种反应堆的感觉。接下来我们看看究竟反应堆模式是如何构建高性能网络框架的。

5. 反应堆模式详解

反应堆模式是一种思想,形式却有很多种。

5.1 反应堆模式的本质是什么

从本质上理解,无论什么网络框架都要完成两部分操作:

IO 操作:数据包的读取和写入

CPU 操作:数据请求的处理和封装

所以上述这些问题由谁来做以及多少线程来做,就衍生出了很多形式,所以不要被表面现象迷惑,出现必有原因,追溯之后我们才能真正掌握它。

反应堆模式根据处理 IO 环节和处理数据环节的数量差异分为如下几种:

单 Reactor 线程

单 Reactor 线程和线程池

多 Reactor 线程和线程池

我们来看看这三种常见模式的特点、原理、优缺点、应用场景等。

5.2 单 Reactor 线程模式

这种模式最为简洁,一个线程完成了连接的监听、接收新连接、处理连接、读取数据、写入数据全套工作。由于只使用了一个线程,对于多核利用率偏低,但是编程简单。是不是觉得这个种单线程的模式没有市场? 那可未必,不信你看 Redis。

在这种模式种 IO 操作和 CPU 操作是没有分开的,都是由 1 个线程来完成的,显然如果在 Handler 处理某个请求超时了将会阻塞客户端的正常连接。在 Redis 中由于都是内存操作,速度很快,这种瓶颈虽然存在但是不够明显。

5.3 单 Reactor 线程和线程池模式

为了解决 IO 操作和 CPU 操作的不匹配,也就是 IO 操作和 CPU 操作是在一个线程内部串行执行的,这样就拉低了 CPU 操作效率。

一种解决方法就是将 IO 操作和 CPU 操作分别由单独的线程来完成,各玩各的互不影响。单 Reactor 线程完成 IO 操作、复用工作线程池来完成 CPU 操作就是一种解决思路。

在这种模式种由 Reactor 线程完成连接的管理和数据读取 写回,完全掌管 IO 操作。工作线程池处理来自上游分发的任务,对其中的数据进行解码、计算、编码再返回给 Reactor 线程和客户端完成交互。这种模式有效利用了多核,但是单 Reactor 线程来完成 IO 操作在高并发场景中仍然会出现瓶颈。换句话说,连接实在太多了,一个 Reactor 线程忙不过来建立新连接和响应旧连接这些事情,因此 Reactor 线程也需要几个帮手。

5.4 多 Reactor 线程和线程池模式

水平扩展往往是提供性能的有效方法。

我们将 Reactor 线程进行扩展,一个 Reactor 线程负责处理新连接,多个 Reactor 线程负责处理连接成功的 IO 数据读写。也就是进一步将监听 创建连接   和 处理连接 分别由两个及以上的线程来完成,进一步提高了 IO 操作部分的效率。

这种模式算是比较高配的版本了,在实际生产环境也有使用。

5.5 拓展:同步 IO 和异步 IO

我们可以轻易区分什么是阻塞 IO 和非阻塞 IO,那么什么是同步 IO 和异步 IO 呢? 前面提到 Reactor 模式其中非常重要的一环就是调用 read/write 函数来完成数据拷贝,这部分是应用程序自己完成的,内核只负责通知监控的事件到来了,所以本质上 Reactor 模式属于非阻塞同步 IO。还有一种 Preactor 模式,借助于系统本身的异步 IO 特性,由操作系统进行数据拷贝,在完成之后来通知应用程序来取就可以,效率更高一些,但是底层需要借助于内核的异步 IO 机制来实现。

底层的异步 IO 机制可能借助于 DMA 和 Zero-Copy 技术来实现,理论上性能更高。当前 Windows 系统通过 IOCP 实现了真正的异步 I /O,而在 Linux   系统的异步 I / O 还不完善,比如 Linux 中的 boost.asio 模块就是异步 IO 的支持,但是目前 Linux 系统还是以基于 Reactor 模式的非阻塞同步 IO 为主。

6. 小结

本文从 IO 事件和 IO 复用出发,阐述了网络架构最底层的组成。继续展开了基于线程模型和基于事件驱动模型的网络框架特点及其设计要素。之后重点描述了反应堆模式的核心本质,以及生产环境中的多种形式。最后简单介绍了同步 IO 和异步 IO 的区别,以及 Preactor 模式的优势。希望读者朋友可以摒弃专业术语和表述,抓住问题的本质和重点,找到一个适合自己思维方法去理解和掌握高性能网络架构的设计之道。或许,高性能网络框架只是一个纸老虎。

上述内容就是怎么深入理解 Linux 高性能网络架构,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注丸趣 TV 行业资讯频道。

正文完
 
丸趣
版权声明:本站原创文章,由 丸趣 2023-08-25发表,共计4175字。
转载说明:除特殊说明外本站除技术相关以外文章皆由网络搜集发布,转载请注明出处。
评论(没有评论)