目录html
原本想对netty的源码进行学习和探究,可是在写netty以前许多底层的知识和原理性的东西理解清楚,那么对学习网络通信框架的效果则会事半功倍。react
本篇主要探讨高性能网络通信框架的一些必要知识和底层操做系统相关的原理。在探讨如何作以前,咱们先讨论下为何要作。linux
随着互联网的高速发展,用户量呈指数形式递增,从原来的PC普及到如今的移动设备普及。用户量都是千万甚至亿为单位计算,尤为是实时通信软件,在线实时互动的应用出现,在线用户数从原来的几十上百到后来的上万甚至上千万。单台服务的性能瓶颈和网络通信瓶颈慢慢呈现。应用架构从单应用到应用数据分离,再到分布式集群高可用架构。单台服务的性能不足能够经过构建服务集群的方式水平扩展,应用性能瓶颈被很好的解决。可是横向扩展带来了直接的经济成本。编程
一个高性能的网络通信框架从硬件设备到操做系统内核以及用户模式都须要精心设计。从底层的I/O访问,到操做系统内核的I/O模型,线程调度以及用户框架都须要精心设计,只要有任何地方有疏漏都会出现短板效应。windows
当咱们在读取socket数据时,虽然咱们在代码仅仅是调用了一个Read
操做,可是实际操做系统层面作了许多事情。首先操做系统须要从用户模式转换为内核模式,处理器会经过网卡驱动对网卡控制器进行操做,网卡控制器则控制网卡。数组
处理器不会直接操控硬件。缓存
为了提升CPU利用率,I/O访问方式也发生了很大变化。服务器
I/O访问的发展趋势是尽量减小处理器干涉I/O操做,让CPU从I/O任务中解脱出来,让处理器能够去作其余事情,从而提升性能。网络
对于I/O访问感兴趣的同窗能够看《操做系统精髓与设计原理(第5版)》第十一章I/O管理相关内容和《WINDOWS内核原理与实现》第六章I/O论述相关内容架构
在讨论I/O模型以前,首先引出一个叫作C10K的问题。在早期的I/O模型使用的是同步阻塞模型,当接收到一个新的TCP链接时,就须要分配一个线程。所以随着链接增长线程增多,频繁的内存复制,上下文切换带来的性能损耗致使性能不佳。所以如何使得单机网络并发链接数达到10K成为通信开发者热门的讨论话题。
前面提到,在最原始的I/O模型中,对文件设备数据的读写须要同步等待操做系统内核,即便文件设备并无数据可读,线程也会被阻塞住,虽然阻塞时不占用CPU始终周期,可是若须要支持并发链接,则必须启用大量的线程,即每一个链接一个线程。这样必不可少的会形成线程大量的上下文切换,随着并发量的增高,性能愈来愈差。
为了解决同步阻塞带来线程过多致使的性能问题,同步非阻塞方案产生。经过一个线程不断的判断文件句柄数组是否有准备就绪的文件设备,这样就不须要每一个线程同步等待,减小了大量线程,下降了线程上下文切换带来的性能损失,提升了线程利用率。这种方式也称为I/O多路复用技术。可是因为数组是有数组长度上限的(linux默认是1024),并且select模型须要对数组进行遍历,所以时间复杂度是\(O_{(n)}\)所以当高并发量的时候,select模型性能会愈来愈差。
poll模型和select模型相似,可是它使用链表存储而非数组存储,解决了并发上限的限制,可是并无解决select模型的高并发性能底下的根本问题。
在linux2.6支持了epoll模型,epoll模型解决了select模型的性能瓶颈问题。它经过注册回调事件的方式,当数据可读写时,将其加入到经过回调方式,将其加入到一个可读写事件的队列中。这样每次用户获取时不须要遍历全部句柄,时间复杂度下降为\(O_{(1)}\)。所以epoll不会随着并发量的增长而性能下降。随着epoll模型的出现C10K的问题已经完美解决。
前面讲的几种模型都是同步I/O模型,异步I/O模型指的是发生数据读写时彻底不一样步阻塞等待,换句话来讲就是数据从网卡传输到用户空间的过程时彻底异步的,不用阻塞CPU。为了更详细的说明同步I/O与异步I/O的区别,接下来举一个实际例子。
当应用程序须要从网卡读取数据时,首先须要分配一个用户内存空间用来保存须要读取的数据。操做系统内核会调用网卡缓冲区读取数据到内核空间的缓冲区,而后再复制到用户空间。在这个过程当中,同步阻塞I/O在数据读取到用户空间以前都会被阻塞,同步非阻塞I/O只知道数据已就绪,可是从内核空间缓冲区拷贝到用户空间时,线程依然会被阻塞。而异步I/O模型在接收到I/O完成通知时,数据已经传输到用户空间。所以整个I/O操做都是彻底异步的,所以异步I/O模型的性能是最佳的。
在个人另外一篇文章《Windows内核原理-同步IO与异步IO》对windows操做系统I/O原理作了简要的叙述,感兴趣的同窗能够看下。
从线程模型上常见的线程模型有Reactor模型和Proactor模型,不管是哪一种线程模型都使用I/O多路复用技术,使用一个线程将I/O读写操做转变为读写事件,咱们将这个线程称之为多路分离器。
对应上I/O模型,Reacor模型属于同步I/O模型,Proactor模型属于异步I/O模型。
在Reactor中,须要先注册事件就绪事件,网卡接收到数据时,DMA将数据从网卡缓冲区传输到内核缓冲区时,就会通知多路分离器读事件就绪,此时咱们须要从内核空间读取到用户空间。
同步I/O采用缓冲I/O的方式,首先内核会从申请一个内存空间用于存放输入或输出缓冲区,数据都会先缓存在该缓冲区。
Proactor模型,须要先注册I/O完成事件,同时申请一片用户空间用于存储待接收的数据。调用读操做,当网卡接收到数据时,DMA将数据从网卡缓冲区直接传输到用户缓冲区,而后产生完成通知,读操做即完成。
异步I/O采用直接输入I/O或直接输出I/O,用户缓存地址会传递给设备驱动程序,数据会直接从用户缓冲区读取或直接写入用户缓冲区,相比缓冲I/O减小内存复制。
本文经过I/O访问方式,I/O模型,线程模型三个方面解释了操做系统为实现高性能I/O作了哪些事情,经过提升CPU使用效率,减小内存复制是提升性能的关键点。
出处:http://www.javashuo.com/article/p-swxpigmz-cr.html 做者:杰哥很忙 本文使用「CC BY 4.0」创做共享协议。欢迎转载,请在明显位置给出出处及连接。