高性能服务器网络模型详解

1999年Dan Kegel在发表的论文中提出了The C10K problem,这篇论文对传统服务器架构处理大规模并发连接时的挑战进行了详细描述,并提出了一些解决方案和优化技术。这里的C指的是Concurrent(并发)的缩写,C10K问题是指怎么在单台服务器上并发一万个请求。如果你分析过性能问题一定会注意到,性能极限通常受到一个或多个资源的限制,比如内存、文件句柄个数、网络带宽、CPU等。这里讨论的前提是机器的物理资源和系统配置能够满足一万个请求。在这个前提下,网络并发主要关注两个方面:一是应用程序和操作系统内核之间如何进行IO事件通知,二是应用程序进程或线程的分配方式。

I/O模型

阻塞vs非阻塞、同步vs异步

你可能经常看到某某项目用的同步非阻塞模型、某某项目用的异步非阻塞模型,所以在正式讨论I/O模型前,还是先对齐一下关于阻塞、非阻塞以及同步、异步的概念吧。

阻塞、非阻塞指的是系统调用时“等待数据准备好”这个动作。比如read时候,如果内核判定数据还没准备好:

  • 内核让应用进程一直等待,直到数据准备好才通知应用进程就是阻塞;
  • 内核立即通知应用程序说数据没准备好,你先干别的吧,就是非阻塞。

同步、异步指的是“内核空间与用户空间复制数据”这个动作。比如read时候,如果内核判定数据还没准备,过一段时间数据来了:

  • 内核通知应用进程你来读取数据吧,应用程序再次系统调用将数据读走就是同步;
  • 内核将数据拷贝到用户空间后再通知应用进程,数据已经拷贝好了直接用吧,就是异步。

I/O模型详解

Stevens在《UNIX 网络编程 卷1》一书的6.2章节介绍了五种 I/O 模型。以下模型以UDP接收报文为例来说明这五种I/O 的工作方式。

> 阻塞式I/O

该模型使用最简,用户进程调用读取数据系统后就一直等待,直到内核数据准备好,并将数据从内核空间拷贝到用户空间后,调用结束。这种模型效率显然的低下,因为这种模型会导致两种可能结果:

  • 为每个请求分配一个进程或线程,那么高并发意味着内核要调度的进程或线程数量很庞大,调度、上下文切换等开销会使系统性能降低。
  • 固定数量的进程或线程处理请求,那么这些进程或线程全被被占用后,新请求就只能等了。

该模型低效的根本原因在于阻塞,内核数据没有准备好的时候,用户态进程明明可以干其他活的,现在只能白白等待。

>非阻塞式 I/O

这种模型通过非阻塞的方式与内核打交道,如果内核中数据还未准备好,就立刻返回给用户进程,用户进程就可以先干别的事情,过一段再进行读数据的系统调用,直到内核数据准备好,并将数据拷贝到用户空间。相比于阻塞的模型,这种非阻塞+主动轮询的模型避免了用户进程白白等待内核准备数据的时间,所以效率有所提升,但是因为每次轮询都是系统调用,所以上下文切换变多了,因此性能也不高。

>I/O 复用

既然不停主动查询内核数据是否准备好这件事会引起系统性能下降,那能不能通过注册+通知的方式呢?这就是大名鼎鼎的I/O复用模型。该模型允许用户态通过一个进程将所有相关的读写事件(使用select、poll或epoll)注册到内核,然后内核会主动通知用户态进程,一旦任意一个或多个请求的读写数据准备好。这种方式在单个进程或线程中同时处理多个I/O通道的就绪状态被称为I/O多路复用。使用I/O多路复用既不会阻塞处理请求的进程,也不会因为轮询内核数据是否准备好而导致过多的系统调用,因此具有高效的特点。然而,需要注意的是,一旦内核通知应用进程数据准备就绪,仍然需要通过系统调用触发数据的读取过程。

Linux内核对这种模型的支持非常完善,因此许多高性能服务器在Linux环境中广泛采用这种模式。

>信号驱动式 I/O

I/O复用模型中是用一个进程(select、poll或epoll)阻塞或轮询所有请求的数据是否准备好,从而让所有请求进程的处理都不会阻塞。信号驱动式则没有这个复用的I/O进程,每个请求进程自己去内核注册,然后等数据准备好内核通知应用进程去处理。这种模型应用套接字处理的实践场景为基于UDP的NTP服务,几乎没有在TCP上的应用,因为对于TCP来说信号产生过于频繁,而且并没有告诉应用程序发生了什么事件,比如下面条件均会导致TCP套接字产生SIGIO信号:

  • 监听套接字某个连接请求已经完成;
  • 某个断链请求已经发起;
  • 某个断链请求已经完成;
  • 某个半连接已经关闭;
  • 数据到达套接字;
  • 数据已经从套接字发出;
  • 发生某个异步错误。

>异步 I/O

前面几种方式,无论是阻塞还是非阻塞,从内核空间到用户空间复制数据的动作都是在内核通知用户进程后,用户进程再通过系统调用触发完成的,因此都属于同步操作。而异步I/O模型则不同,它允许用户态进程通过系统调用读取数据后,即使内核数据未准备好,也会立即返回给用户进程,告知数据未准备好,让用户进程可以执行其他操作。当数据准备好后,内核会将数据从内核空间拷贝到用户态,并直接回调用户进程,将数据送到用户进程手中。这种模型不仅具备非阻塞特性,还能进一步减少系统调用的次数,因此在理论上相对于其他模型更加高效。需要注意的是,这种模型需要操作系统内核的支持。

在《UNIX网络编程卷1》一书中,截至书稿时,支持POSIX异步I/O的系统相对较少。由于早期Linux内核对网络异步I/O的支持不够成熟,在Linux环境下,大多数高性能网络服务器选择采用I/O复用的方式,如epoll。然而,从Linux内核5.0版本开始,引入了io_uring异步操作,随着该技术的成熟,越来越多的高性能网络服务器(例如nginx)开始支持使用这种异步I/O方式。

>如何简单理解5种I/O?

下面通过一个例子对比一下5种模型,顾客是应用进程,餐饮人员为内核,餐桌为应用进程的数据buffer:

  • 阻塞式I/O:交完钱也要在窗口排队,等师傅做好,将饭端给你,你再端到自己餐桌。
  • 非阻塞式I/O:交完钱你就可以离开窗口玩一会了,窗口有个屏幕,你过一会跑过来看一下自己的饭好了没,直到饭做好,自己端到自己的餐桌。
  • I/O复用:好几个同学都把饭卡交给你,你一个人跑到窗口排队刷卡,谁的饭好了,你就打电话给谁,让他自己将饭端到餐桌。
  • 信号驱动式I/O:你去窗口手机刷卡后就可离开了,饭做好会通过手机通知你,然后自己过去将饭端到餐桌。
  • 异步I/O:去窗口点餐后,告诉服务员你在哪个餐桌就可以离开了,饭做好,服务员会将饭帮你端到餐桌。

进程/线程分配

进程和线程的创建、调度都需要系统开销。在高并发系统中,为每个请求分配一个进程或线程会对性能产生不利影响。为了克服这个问题,高性能的网络模型通常采用进程池或线程池来管理进程或线程。进程/线程池的设计目的是降低创建和销毁进程/线程的频率,并限制系统中总进程/线程的数量,以减少内核调度的开销。

常用高性能模式

  • reactor 模式

《The Design and Implementation of the Reactor》一文详细介绍了reactor模式的工作方式,简单来说,reactor模式=I/O复用 + 进程池/线程池。

  • proactor模式

《Proactor: An Object Behavioral Pattern for Demultiplexing and Dispatching Handlers for Asynchronous Events》一文详细介绍了proactor模式的工作方式,简单来说,proactor模式=异步I/O+ 进程池/线程池。

惊群效应

对于TCP请求来说,最理想的情况是每个事件每次从池中唤醒一个进程或线程去执行,这样既不需要等待又不会引起竞争。用一个进程或线程专门负责处理accept事件,然后将接下来的事件继续分发给其他worker处理是可行的。有一些模型(比如nginx)存在多个进程或线程监听同一个端口的情况,如果不加处理会出现一个accept事件唤醒所有worker进程的情况,即惊群效应。为了应对惊群效应,早期nginx引入了accept_mutex的机制,竞争到锁的worker才会执行accept操作,从而避免所有worker都被唤醒。Linux3.9 版本后提供了reuseport更好的解决了多个进程或线程监听同一个端口引起的惊群问题,简单说就是内核帮你轮询,而不用在应用层面竞争了。

更快、更强大的网络模型

欲望是永无止境的,有人提出The C10K problem问题,就有人提出The C10M problem,前文中的讨论基本都是围绕用户态进程和内核的交互优化,既然和内核交互容易导致性能瓶颈,那为何不旁路掉内核协议栈呢?所以有了更快的网络方案,比如DPDK、XDP、甚至硬件加速等。

粉丝福利, 免费领取C/C++ 开发学习资料包、技术视频/项目代码,1000道大厂面试题,内容包括(C++基础,网络编程,数据库,中间件,后端开发/音视频开发/Qt开发/游戏开发/Linuxn内核等进阶学习资料和最佳学习路线)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/664872.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

buidldroot musl uclib库 编译

buildroot 修改 编译工具链 原本编译器相关信息: Incorrect selection of the C library buidroot编译 注意相关选项,后续使用CUSTOM TOOLCHAIN 时对应 UCLIB 能将生成IMAGE 从2.9K变为2.3K MUSL 能将生成IMAGE 从2.9K变为2.7K 变大了 arm-linux-…

【运维项目经历|025】企业高效邮件系统部署与运维项目

目录 项目名称 项目背景 项目目标 项目成果 我的角色与职责 我主要完成的工作内容 本次项目涉及的技术 本次项目遇到的问题与解决方法 本次项目中可能被面试官问到的问题 问题1:项目周期为多长时间? 问题2:服务器部署架构方式及数量…

ubuntu 18.04 ros1学习

总结了一下,学习内容主要有: 1.ubuntu的基础命令 pwd: 获得当前路径 cd: 进入或者退出一个目录 ls:列举该文件夹下的所有文件名称 mv 移动一个文件到另一个目录中 cp 拷贝一个文件到另一个目录中 rm -r 删除文件 gedit sudo 给予管理员权限 sudo apt-…

uniapp实现图片上传——支持APP、微信小程序

uniapp实现图片、视频上传 文章目录 uniapp实现图片、视频上传效果图组件templatejs 使用 相关文档: 结合 uView 插件 uni.uploadFile 实现 u-upload uploadfile 效果图 组件 简单封装,还有很多属性…,自定义样式等…根据个人所需调整 te…

DNF手游攻略:勇士进阶指南!

在即将到来的6月5日,《DNF手游》将迎来一场盛大的更新,此次更新带来了大量新内容和玩法,极大丰富了游戏的体验。本文将为广大玩家详细解析此次更新的亮点,包括新增的组队挑战玩法“罗特斯入门团本”、新星使宠物的推出、宠物进化功…

ADB日常使用命令

【ADB全称 Android Debug Bridge】 是Android SDK中的一个命令行工具adb命令可以直接操作管理Android模拟器或真实的Android设备(手机) 建立PC和模拟器连接 # 建立连接 adb connect 127.0.1: 模拟器端口号〈逍遥模拟器21503〉 # 验证是否连接成功 adb d…

NFS p.1 服务器的部署以及客户端与服务端的远程挂载

目录 介绍 应用 NFS的工作原理 NFS的使用 步骤 1、两台机子 2、安装 3、配置文件 4、实验 服务端 准备 启动服务: 客户端 准备 步骤 介绍 NFS(Network File System,网络文件系统)是一种古老的用于在UNIX/Linux主…

使用 Apache Commons Exec 管理外部进程

😄 19年之后由于某些原因断更了三年,23年重新扬帆起航,推出更多优质博文,希望大家多多支持~ 🌷 古之立大事者,不惟有超世之才,亦必有坚忍不拔之志 🎐 个人CSND主页——Mi…

基于 Apache Doris 的实时/离线一体化架构,赋能中国联通 5G 全连接工厂解决方案

作者:田向阳,联通西部创新研究院 大数据专家 共创:SelectDB 技术团队 导读: 数据是 5G 全连接工厂的核心要素,为支持全方位的数据收集、存储、分析等工作的高效进行,联通 5G 全连接工厂从典型的 Lambda 架…

使用PNP管控制MCU是否需要复位

这两台用到一款芯片带电池,希望电池还有电芯片在工作的时候插入电源不要给芯片复位,当电池没电,芯片不在工作的时候,插入电源给芯片复位所以使用一个PNP三极管,通过芯片IO控制是否打开复位,当芯片正常工作的…

在长窗口时代,RAG技术是否仍然必要?

自从谷歌推出 Gemini 1.5 Pro,行业内部对于 RAG 的讨论就不绝于耳。 Gemini 1.5 Pro 的性能确实令人瞩目。根据谷歌公布的技术文档,该系统能够稳定处理长达 100 token 的内容,相当于一小时的视频、十一小时的音频、超过三万行的代码或七十万…

Spring Cloud Alibaba-09-Seata分布式事务

Lison <dreamlison163.com>, v1.0.0, 2024.5.03 Spring Cloud Alibaba-09-Seata分布式事务 文章目录 Spring Cloud Alibaba-09-Seata分布式事务分布式事务基础事务本地事务分布式事务分布式事务的场景 分布式事务的解决方案全局事务可靠消息服务最大努力通知TCC事务 Se…

Java实现数据结构---数组

文章目录 概念存储原理数组的操作完整代码 概念 数组是&#xff08;Array&#xff09;是有限个相同类型的变量所组成的有序集合&#xff0c;数组中的每一个变量为称为元素。数组是最简单、最常用的数据结构。 数组下标从零开始。 存储原理 数组用一组连续的内存空间来存储一…

蓝桥杯第17135题 不完整的算式 C++ Java Python

目录 题目 思路和解题方法 步骤 1&#xff1a;识别缺失的部分 步骤 2&#xff1a;根据已知条件计算或推断 步骤 3&#xff1a;处理特殊情况和验证 c 代码 Java 版本 Python 版本&#xff08;仅供参考&#xff09; 代码和解题细节&#xff1a; 题目 题目链接&#xff…

STM32自己从零开始实操03:输出部分原理图

一、继电器电路 1.1指路 延续使用 JZC-33F-012-ZS3 继电器&#xff0c;设计出以小电流撬动大电流的继电器电路。 &#xff08;提示&#xff09;电路需要包含&#xff1a;三极管开关电路、续流二极管、滤波电容、指示灯、输出部分。 1.2数据手册重要信息提炼 联系排列&…

神经网络与深度学习——第3章 线性模型

本文讨论的内容参考自《神经网络与深度学习》https://nndl.github.io/ 第3章 线性模型 线性模型 线性模型&#xff08;Linear Model&#xff09;是机器学习中应用最广泛的模型&#xff0c;指通过样本特征的线性组合来进行预测的模型&#xff0c;给定一个 D D D维样本 x [ x …

解锁 GPT-4o 背后数据带来的情绪价值

GPT-4o 可以说已经是一个富有情感、通人性的智能语音助手&#xff0c;或者更准确地说&#xff0c;是一个越来越接近人类交互的 “新物种”。这个强大的模型同时具备文本、图片、视频和语音理解和合成方面的能力&#xff0c;甚至可以被视为 GPT-5 的一个未完成版。 01 富有情感的…

lipo制作通用版本静态库

文章目录 目的了解多架构的maclipo如何利用lipo编译通用版本静态库lipo 命令整理扩展目的 主要是使用lipo命令在macOS上创建通用版本的静态库(.a文件),来支持多种架构,如arm64,x86_64。 学习目的: 了解mac 不同架构arm64, x86_64了解lipo命令了解多架构的mac 随着appl…

Linux - 文件管理高级1

0.管道 | 将前面命令的标准输出传递给管道作为后面的标准输入 1.文件查找 find find 进行文件查找时&#xff0c;默认进行递归查找&#xff0c;会查找隐藏目录下的文件 1.1 用法 # find 查找路径 查找条件... -type // 文件类型 f 普通文件 b 设备 d …

数据目录用处如此之大?四个步骤教你构建数据目录

在数字化浪潮的推动下&#xff0c;数据已成为企业决策的核心。然而&#xff0c;随着数据量的爆炸性增长&#xff0c;如何高效地管理和利用这些宝贵的数据资产&#xff0c;成为了一个日益严峻的挑战。企业需要一个强大的工具来组织、索引和解释其数据&#xff0c;以便快速发现和…