本文同步与Linux高性能服务器编程模式 | Rust中文社区 本文时间:2018-11-21,作者:krircc, 简介:天青色
欢迎向Rust中文社区投稿,投稿地址,好文将在以下地方直接展示
Rust中文社区首页Rust中文社区Rust文章栏目知乎专栏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的多路复用模型linux系统入门学习,原理图如下:
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框架本身与具体事件处理逻辑无关linux系统及程序设计,具有很高的复用性;
缺点
1)相比传统的简单模型,Reactor增加了一定的复杂性,因而有一定的门槛,并且不易于调试。
2)Reactor模式需要底层的Synchronous Event Demultiplexer支持linux系统及程序设计linux防火墙设置,比如Java中的Selector支持,操作系统的select系统调用支持,如果要自己实现Synchronous Event Demultiplexer可能不会有那么高效。
3) Reactor模式在IO读写数据时还是在同一个线程中实现的,即使使用多个Reactor机制的情况下,那些共享一个Reactor的Channel如果出现一个长时间的数据读写,会影响这个Reactor中其他Channel的相应时间,比如在大文件传输时,IO操作就会影响其他Client的相应时间,因而对这种操作,使用传统的Thread-Per-Connection或许是一个更好的选择,或则此时使用Proactor模式。
Reactor中的组件Rust异步网络编程
Rust的高性能异步网络编程模式目前是基于mio和futures这两个库构建的生态。
Tokio则连接这2个库构建了一个异步非阻塞事件驱动编程平台。
什么是 mio,futures,tokio1- Mio
Mio是Rust的轻量级快速低级IO库,专注于非阻塞API,事件通知以及用于构建高性能IO应用程序的其他有用实用程序.
特征平台支持2- futures
Rust中的零成本异步编程库,Futures可在没有标准库的情况下工作,例如在裸机环境中。
提供了许多用于编写异步代码的核心抽象:
还包含异步I/O和跨任务通信的抽象。
所有这些是任务系统的基础,它是轻量级线程(协程)的一种形式。使用Future,Streams和Sinks构建大型异步计算,然后将其生成作为独立完成的任务运行,但不阻塞运行它们的线程。
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是一个小组件的集合。用户可以选择最适合手头应用的部件,而无需支付未使用功能的成本。