Linux 高性能服务器编程模式

2018-11-21 20:58:39 +08:00
 krircc

本文时间:2018-11-21,作者:krircc, 简介:天青色

欢迎向 Rust 中文社区投稿,投稿地址,好文将在以下地方直接展示

  1. Rust 中文社区首页
  2. Rust 中文社区Rust 文章栏目
  3. 知乎专栏Rust 语言

高性能服务器至少要满足如下几个需求:

而满足如上需求的一个基础就是高性能的 IO!

讲到高性能 IO 绕不开 Reactor 模式,它是大多数 IO 相关组件如 Netty、Redis 在使用的 IO 模式

几乎所有的网络连接都会经过读请求内容——》解码——》计算处理——》编码回复——》回复的过程

Socket

Socket 之间建立链接及通信的过程!实际上就是对 TCP/IP 连接与通信过程的抽象:

阻塞 IO(BIO)、非阻塞 IO(NBIO)、同步 IO、异步 IO

BIO 优点

BIO 缺点

缺点:主要瓶颈在线程上。每个连接都会建立一个线程。虽然线程消耗比进程小,但是一台机器实际上能建立的有效线程有限,且随着线程数量的增加,CPU 切换线程上下文的消耗也随之增加,在高过某个阀值后,继续增加线程,性能不增反降!而同样因为一个连接就新建一个线程,所以编码模型很简单!

就性能瓶颈这一点,就确定了 BIO 并不适合进行高性能服务器的开发!

NBIO:

优点

缺点

NBIO 的优缺点和 BIO 就完全相反了!性能高,不用一个连接就建一个线程,可以一个线程处理所有的连接!相应的,编码就复杂很多,从上面的代码就可以明显体会到了。还有一个问题,由于是非阻塞的,应用无法知道什么时候消息读完了,就存在了半包问题!需要自行进行处理!例如,以换行符作为判断依据,或者定长消息发生,或者自定义协议!

NBIO 虽然性能高,但是编码复杂,且需要处理半包问题!为了方便的进行 NIO 开发,就有了 Reactor 模型!

Proactor 和 Reactor

Proactor 和 Reactor 是两种经典的多路复用 I/O 模型,主要用于在高并发、高吞吐量的环境中进行 I/O 处理。

I/O 多路复用机制都依赖于一个事件分发器,事件分离器把接收到的客户事件分发到不同的事件处理器中,如下

select,poll,epoll

在操作系统级别 select,poll,epoll 是 3 个常用的 I/O 多路复用机制,简单了解一下将有助于我们理解 Proactor 和 Reactor。

select

select 的原理如下:

用户程序发起读操作后,将阻塞查询读数据是否可用,直到内核准备好数据后,用户程序才会真正的读取数据。

poll 与 select 的原理相似,用户程序都要阻塞查询事件是否就绪,但 poll 没有最大文件描述符的限制。

epoll

epoll 是 select 和 poll 的改进,原理图如下:

epoll 使用“事件”的方式通知用户程序数据就绪,并且使用内存拷贝的方式使用户程序直接读取内核准备好的数据,不用再读取数据

Proactor

Proactor 是一个异步 I/O 的多路复用模型,原理图如下:

Reactor

Reactor 是一个同步的 I/O 多路复用模型,它没有 Proactor 模式那么复杂,原理图如下:

Proactor 和 Reactor 的比较

Reactor 多线程模型

前面已经简单介绍了 Proactor 和 Reactor 模型,在实际中 Proactor 由于需要操作系统的支持,实现的案例不多,有兴趣的可以看一下 Boost Asio 的实现,我们主要说一下 Reactor 模型,Netty 也是使用 Reactor 实现的。

但单线程的 Reactor 模型每一个用户事件都在一个线程中执行:

多线程 Reactor

使用线程池的技术来处理 I/O 操作,原理图如下:

主从多线程 Reactor

在多线程 Reactor 中只有一个 Acceptor,如果出现登录、认证等耗性能的操作,这时就会有单点性能问题,因此产生了主从 Reactor 多线程模型,原理如下:

Reactor 模式结构

在解决了什么是 Reactor 模式后,我们来看看 Reactor 模式是由什么模块构成。图是一种比较简洁形象的表现方式,因而先上一张图来表达各个模块的名称和他们之间的关系:

优点

1 )响应快,不必为单个同步时间所阻塞,虽然 Reactor 本身依然是同步的;

2 )编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程 /进程的切换开销;

3 )可扩展性,可以方便的通过增加 Reactor 实例个数来充分利用 CPU 资源;

4 )可复用性,reactor 框架本身与具体事件处理逻辑无关,具有很高的复用性;

缺点

1 )相比传统的简单模型,Reactor 增加了一定的复杂性,因而有一定的门槛,并且不易于调试。

2 ) Reactor 模式需要底层的 Synchronous Event Demultiplexer 支持,比如 Java 中的 Selector 支持,操作系统的 select 系统调用支持,如果要自己实现 Synchronous Event Demultiplexer 可能不会有那么高效。

3 ) Reactor 模式在 IO 读写数据时还是在同一个线程中实现的,即使使用多个 Reactor 机制的情况下,那些共享一个 Reactor 的 Channel 如果出现一个长时间的数据读写,会影响这个 Reactor 中其他 Channel 的相应时间,比如在大文件传输时,IO 操作就会影响其他 Client 的相应时间,因而对这种操作,使用传统的 Thread-Per-Connection 或许是一个更好的选择,或则此时使用 Proactor 模式。

Reactor 中的组件

Rust 异步网络编程

Rust 的高性能异步网络编程模式目前是基于miofutures这两个库构建的生态。

Tokio则连接这 2 个库构建了一个异步非阻塞事件驱动编程平台。

什么是 mio,futures,tokio

1- Mio

Mio 是 Rust 的轻量级快速低级 IO 库,专注于非阻塞 API,事件通知以及用于构建高性能 IO 应用程序的其他有用实用程序.

特征

平台支持

2- futures

Rust 中的零成本异步编程库,Futures 可在没有标准库的情况下工作,例如在裸机环境中。

提供了许多用于编写异步代码的核心抽象:

还包含异步 I/O 和跨任务通信的抽象。

所有这些是任务系统的基础,它是轻量级线程(协程)的一种形式。使用FutureStreamsSinks构建大型异步计算,然后将其生成作为独立完成的任务运行,但不阻塞运行它们的线程。

3- Tokio

Tokio : Rust 编程语言的异步运行时,提供异步事件驱动平台,构建快速,可靠和轻量级网络应用。利用 Rust 的所有权和并发模型确保线程安全

这些组件提供构建异步应用程序所需的运行时组件。

快速

Tokio 构建于 Rust 之上,提供极快的性能,使其成为高性能服务器应用程序的理想选择。

1:零成本抽象

与完全手工编写的等效系统相比,Tokio 的运行时模型不会增加任何开销。

使用 Tokio 构建的并发应用程序是开箱即用的。Tokio 提供了针对异步网络工作负载调整的多线程,工作窃取任务调度程序。

2:非阻塞 I/O

Tokio 由操作系统提供的非阻塞,事件 I/O 堆栈提供支持。

可靠

虽然 Tokio 无法阻止所有错误,但它的目的是最小化它们。Tokio 在运送关键任务应用程序时带来了安心。

1- 所有权和类型系统

Tokio 利用 Rust 的类型系统来提供难以滥用的 API。

2- Backpressure

Backpressure 开箱即用,无需使用任何复杂的 API。

3- 取消

Rust 的所有权模型允许 Tokio 自动检测何时不再需要计算。Tokio 将自动取消它而无需用户调用 cancel 函数。

轻量级

Tokio 可以很好地扩展,而不会增加应用程序的开销,使其能够在资源受限的环境中茁壮成长。

1- 没有垃圾收集器

因为 Tokio 使用 Rust,所以不包括垃圾收集器或其他语言运行时。

2- 模块化

Tokio 是一个小组件的集合。用户可以选择最适合手头应用的部件,而无需支付未使用功能的成本。

3589 次点击
所在节点    Rust
0 条回复

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/510183

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX