乐趣区

关于网络编程:从根上理解高性能高并发六通俗易懂高性能服务器到底是如何实现的

本文原题“高并发高性能服务器是如何实现的”,转载请分割作者。

1、系列文章引言

1.1 文章目标

作为即时通讯技术的开发者来说,高性能、高并发相干的技术概念早就了然与胸,什么线程池、零拷贝、多路复用、事件驱动、epoll 等等名词信手拈来,又或者你对具备这些技术特色的技术框架比方:Java 的 Netty、Php 的 workman、Go 的 gnet 等熟练掌握。但真正到了面视或者技术实际过程中遇到无奈释怀的纳闷时,方知自已所把握的不过是皮毛。

返璞归真、回归实质,这些技术特色背地的底层原理到底是什么?如何能通俗易懂、毫不费力真正透彻了解这些技术背地的原理,正是《从根上了解高性能、高并发》系列文章所要分享的。

1.2 文章源起

我整顿了相当多无关 IM、音讯推送等即时通讯技术相干的资源和文章,从最开始的开源 IM 框架 MobileIMSDK,到网络编程经典巨著《TCP/IP 详解》的在线版本,再到 IM 开发纲领性文章《新手入门一篇就够:从零开发挪动端 IM》,以及网络编程由浅到深的《网络编程懒人入门》、《脑残式网络编程入门》、《高性能网络编程》、《鲜为人知的网络编程》系列文章。

越往常识的深处走,越感觉对即时通讯技术理解的太少。于是起初,为了让开发者门更好地从根底电信技术的角度了解网络(尤其挪动网络)个性,我跨专业收集整理了《IM 开发者的零根底通信技术入门》系列高阶文章。这系列文章未然是一般即时通讯开发者的网络通信技术常识边界,加上之前这些网络编程材料,解决网络通信方面的常识盲点根本够用了。

对于即时通讯 IM 这种零碎的开发来说,网络通信常识的确十分重要,但回归到技术实质,实现网络通信自身的这些技术特色:包含下面提到的线程池、零拷贝、多路复用、事件驱动等等,它们的实质是什么?底层原理又是怎么?这就是整顿本系列文章的目标,心愿对你有用。

1.3 文章目录

《从根上了解高性能、高并发(一):深刻计算机底层,了解线程与线程池》
《从根上了解高性能、高并发(二):深刻操作系统,了解 I / O 与零拷贝技术》
《从根上了解高性能、高并发(三):深刻操作系统,彻底了解 I / O 多路复用》
《从根上了解高性能、高并发(四):深刻操作系统,彻底了解同步与异步》
《从根上了解高性能、高并发(五):深刻操作系统,了解高并发中的协程》
《从根上了解高性能、高并发(六):通俗易懂,高性能服务器到底是如何实现的》(* 本文)

1.4 本篇概述

接上篇《从根上了解高性能、高并发(五):深刻操作系统,了解高并发中的协程》,本篇是高性能、高并发系列的第 6 篇文章(也是完结篇)。

本篇是本系列文章的完结篇,你将能理解到,一个典型的服务器端是如何利用前 5 篇中解说的各单项技术从而实现高性能高并发的。

本文已同步公布于“即时通讯技术圈”公众号,欢送关注。公众号上的链接是:点此进入。

2、本文作者

应作者要求,不提供真名,也不提供集体照片。

本文作者次要技术方向为互联网后端、高并发高性能服务器、检索引擎技术,网名是“码农的荒岛求生”。感激作者的自私分享。

3、注释引言

当你在浏览本篇文章的时候,有没有想过,服务器是怎么把这篇文章发送给你的呢?

说起来很简略:不就是一个用户申请吗?服务器依据申请从数据库中捞出这篇文章,而后通过网络发回去吗。

其实有点简单:服务器端到底是如何并行处理成千上万个用户申请的呢?这外面又波及到哪些技术呢?

这篇文章就是来为你解答这个问题的。

4、多过程

历史上最早呈现也是最简略的一种并行处理多个申请的办法就是利用多过程。

比方在 Linux 世界中,咱们能够应用 fork、exec 等零碎调用创立多个过程,咱们能够在父过程中接管用户的连贯申请,而后创立子过程去解决用户申请。

就像这样:

这种办法的长处就在于:

  • 1)编程简略,非常容易了解;
  • 2)因为各个过程的地址空间是互相隔离的,因而一个过程解体后并不会影响其它过程;
  • 3)充分利用多核资源。

多过程并行处理的长处很显著,然而毛病同样显著:

  • 1)各个过程地址空间互相隔离,这一长处也会变成毛病,那就是过程间要想通信就会变得比拟艰难,你须要借助过程间通信(IPC,interprocess communications)机制,想一想你当初晓得哪些过程间通信机制,而后让你用代码实现呢?显然,过程间通信编程绝对简单,而且性能也是一大问题;
  • 2)咱们晓得创立过程开销是比线程要大的,频繁的创立销毁过程无疑会减轻零碎累赘。

幸好,除了过程,咱们还有线程。

5、多线程

不是创立过程开销大吗?不是过程间通信艰难吗?这些对于线程来说通通不是问题。

什么?你还不理解线程,连忙看看这篇《深刻计算机底层,了解线程与线程池》,这里具体解说了线程这个概念是怎么来的。

因为线程共享过程地址空间,因而线程间通信人造不须要借助任何通信机制,间接读取内存就好了。

线程创立销毁的开销也变小了,要晓得线程就像寄居蟹一样,房子(地址空间)都是过程的,本人只是一个租客,因而十分的轻量级,创立销毁的开销也十分小。

咱们能够为每个申请创立一个线程,即便一个线程因执行 I / O 操作——比方读取数据库等——被阻塞暂停运行也不会影响到其它线程。

就像这样:

但线程就是完满的、包治百病的吗,显然,计算机世界素来没有那么简略。

因为线程共享过程地址空间,这在为线程间通信带来便当的同时也带来了无尽的麻烦。

正是因为线程间共享地址空间,因而一个线程解体会导致整个过程解体退出,同时线程间通信几乎太简略了,简略到线程间通信只须要间接读取内存就能够了,也简略到呈现问题也极其容易,死锁、线程间的同步互斥、等等,这些极容易产生 bug,有数程序员贵重的工夫就有相当一部分用来解决多线程带来的无尽问题。

尽管线程也有毛病,然而相比多过程来说,线程更有劣势,但想单纯的利用多线程就能解决高并发问题也是不切实际的。

因为尽管线程创立开销相比过程小,但仍然也是有开销的,对于动辄数万数十万的链接的高并发服务器来说,创立数万个线程会有性能问题,这包含内存占用、线程间切换,也就是调度的开销。

因而,咱们须要进一步思考。

6、事件驱动:Event Loop

到目前为止,咱们提到“并行”二字就会想到过程、线程。

然而:并行编程只能依赖这两项技术吗?并不是这样的!

还有另一项并行技术广泛应用在 GUI 编程以及服务器编程中,这就是近几年十分风行的事件驱动编程:event-based concurrency。

PS:搞 IM 服务端开发的程序员必定不生疏,驰名的 Java NIO 高性能网络编程框架 Netty 中 EvenLoop 这个接口意味着什么(无关 Netty 框架的高性能原理能够读这篇《新手入门:目前为止最透彻的的 Netty 高性能原理和框架架构解析》)。

大家不要感觉这是一项很难懂的技术,实际上事件驱动编程原理上非常简单。

这一技术须要两种原料:

  • 1)event;
  • 2)解决 event 的函数,这一函数通常被称为 event handler;

剩下的就简略了:你只须要宁静的期待 event 到来就好,当 event 到来之后,检查一下 event 的类型,并依据该类型找到对应的 event 处理函数,也就是 event handler,而后间接调用该 event handler 就好了。

That’s it !

以上就是事件驱动编程的全部内容,是不是很简略!

从下面的探讨能够看到:咱们须要一直的接管 event 而后解决 event,因而咱们须要一个循环(用 while 或者 for 循环都能够),这个循环被称为 Event loop。

应用伪代码示意就是这样:

while(true) {
    event = getEvent();
    handler(event);
}

Event loop 中要做的事件其实是非常简单的,只须要期待 event 的带来,而后调用相应的 event 处理函数即可。

留神:这段代码只须要运行在一个线程或者过程中,只须要这一个 event loop 就能够同时解决多个用户申请。

有的同学能够仍然不明确:为什么这样一个 event loop 能够同时解决多个申请呢?

起因很简略:对于网络通信服务器来说,解决一个用户申请时大部分工夫其实都用在了 I / O 操作上,像数据库读写、文件读写、网络读写等。当一个申请到来,简略解决之后可能就须要查询数据库等 I / O 操作,咱们晓得 I / O 是十分慢的,当发动 I / O 后咱们大能够不必期待该 I / O 操作实现就能够持续解决接下来的用户申请。

当初你应该明确了吧:尽管上一个用户申请还没有解决完咱们其实就能够解决下一个用户申请了,这也是并行,这种并行就能够用事件驱动编程来解决。

这就好比餐厅服务员一样:一个服务员不可能始终等上一个顾客下单、上菜、吃饭、买单之后才接待下一个顾客,服务员是怎么做的呢?当一个顾客下完单后间接解决下一个顾客,当顾客吃完饭后会本人回来买单结账的。

看到了吧:同样是一个服务员也能够同时解决多个顾客,这个服务员就相当于这里的 Event loop,即便这个 event loop 只运行在一个线程 (过程) 中也能够同时解决多个用户申请。

置信你曾经对事件驱动编程有一个清晰的认知了,那么接下来的问题就是,这个事件也就是 event 该怎么获取呢?

7、事件起源:IO 多路复用

在《深刻操作系统,彻底了解 I / O 多路复用》这篇文章中咱们晓得,在 Linux/Unix 世界中所有皆文件,而咱们的程序都是通过文件描述符来进行 I / O 操作的,当然对于网络编程中的 socket 也不例外。

那咱们该如何同时解决多个文件描述符呢?

IO 多路复用技术正是用来解决这一问题的:通过 IO 多路复用技术,咱们一次能够监控多个文件形容,当某个“文件”(理论可能是 im 网络通信中 socket)可读或者可写的时候咱们就能失去告诉啦。

这样 IO 多路复用技术就成了 event loop 的原材料供应商,源源不断的给咱们提供各种 event,这样对于 event 起源的问题就解决了。

当然:对于 IO 多路复用技术的具体解说请参见《深刻操作系统,彻底了解 I / O 多路复用》,本文作为纲领性文章,就不再赘述了。

至此:对于利用事件驱动来实现并发编程的所有问题都解决了吗?event 的起源问题解决了,当失去 event 后调用相应的 handler,看上去功败垂成了。

想一想还有没有其它问题?

8、问题:阻塞式 IO

当初:咱们能够应用一个线程(过程)就能基于事件驱动进行并行编程,再也没有了多线程中让人恼火的各种锁、同步互斥、死锁等问题了。

然而:计算机科学中素来没有呈现过一种能解决所有问题的技术,当初没有,在可预期的未来也不会有。

那上述办法有什么问题吗?

不要忘了,咱们 event loop 是运行在一个线程(过程),这尽管解决了多线程问题,然而如果在解决某个 event 时须要进行 IO 操作会怎么样呢?

在《深刻操作系统,了解 I / O 与零拷贝技术》一文中,咱们解说了最罕用的文件读取在底层是如何实现的,程序员最罕用的这种 IO 形式被称为阻塞式 IO。

也就是说:当咱们进行 IO 操作,比方读取文件时,如果文件没有读取实现,那么咱们的程序(线程)会被阻塞而暂停执行,这在多线程中不是问题,因为操作系统还能够调度其它线程。

然而:在单线程的 event loop 中是有问题的,起因就在于当咱们在 event loop 中执行阻塞式 IO 操作时整个线程(event loop)会被暂停运行,这时操作系统将没有其它线程能够调度,因为零碎中只有一个 event loop 在解决用户申请,这样当 event loop 线程被阻塞暂停运行时所有用户申请都没有方法被解决。你能设想当服务器在解决其它用户申请读取数据库导致你的申请被暂停吗?

因而:在基于事件驱动编程时有一条注意事项,那就是不容许发动阻塞式 IO。

有的同学可能会问,如果不能发动阻塞式 IO 的话,那么该怎么进行 IO 操作呢?

PS:有阻塞式 IO,就有非阻塞式 IO。咱们持续往下探讨。

9、解决办法:非阻塞式 IO

为克服阻塞式 IO 所带来的问题,古代操作系统开始提供一种新的发动 IO 申请的办法,这种办法就是异步 IO。对应的,阻塞式 IO 就是同步 IO,对于同步和异步这两个概念能够参考《从根上了解高性能、高并发(四):深刻操作系统,彻底了解同步与异步》。

异步 IO 时,假如调用 aio_read 函数(具体的异步 IO API 请参考具体的操作系统平台),也就是异步读取,当咱们调用该函数后能够立刻返回,并持续其它事件,尽管此时该文件可能还没有被读取,这样就不会阻塞调用线程了。此外,操作系统还会提供其它办法供调用线程来检测 IO 操作是否实现。

就这样,在操作系统的帮忙下 IO 的阻塞调用问题也解决了。

10、基于事件驱动并行编程的难点

尽管有异步 IO 来解决 event loop 可能被阻塞的问题,然而基于事件编程仍然是艰难的。

首先:咱们提到,event loop 是运行在一个线程中的,显然一个线程是没有方法充分利用多核资源的,有的同学可能会说那就创立多个 event loop 实例不就能够了,这样就有多个 event loop 线程了,然而这样一来多线程问题又会呈现。

另一点在于编程方面,在《从根上了解高性能、高并发(四):深刻操作系统,彻底了解同步与异步》这篇文章中咱们讲到过,异步编程须要联合回调函数(这种编程形式须要把解决逻辑分为两局部:一部分调用方本人解决,另一部分在回调函数中解决),这一编程形式的扭转减轻了程序员在了解上的累赘,基于事件编程的我的项目前期会很难扩大以及保护。

那么有没有更好的办法呢?

要找到更好的办法,咱们须要解决问题的实质,那么这个实质问题是什么呢?

11、更好的办法

为什么咱们要应用异步这种难以了解的形式编程呢?

是因为:阻塞式编程尽管容易了解但会导致线程被阻塞而暂停运行。

那么聪慧的你肯定会问了:有没有一种办法既能联合同步 IO 的简略了解又不会因同步调用导致线程被阻塞呢?

答案是必定的:这就是用户态线程(user level thread),也就是赫赫有名的协程(对于协程请详读本系列的上篇《从根上了解高性能、高并发(五):深刻操作系统,了解高并发中的协程》,本文就不再赘述了)。

尽管基于事件编程有这样那样的毛病,然而在当今的高性能高并发服务器上基于事件编程形式仍然十分风行,但曾经不是纯正的基于繁多线程的事件驱动了,而是 event loop + multi thread + user level thread。

对于这一组合,同样值得拿出一篇文章来解说,咱们将在后续文章中具体探讨。

12、本文小结

高并发技术从最开始的多过程一路演进到以后的事件驱动,计算机技术就像生物一样也在一直演变进化,但不管怎样,理解历史能力更粗浅的了解当下。心愿这篇文章能对大家了解高并发服务器有所帮忙。

附录:更多高性能、高并发文章精选

《高性能网络编程 (一):单台服务器并发 TCP 连接数到底能够有多少》
《高性能网络编程(二):上一个 10 年,驰名的 C10K 并发连贯问题》
《高性能网络编程(三):下一个 10 年,是时候思考 C10M 并发问题了》
《高性能网络编程(四):从 C10K 到 C10M 高性能网络应用的实践摸索》
《高性能网络编程(五):一文读懂高性能网络编程中的 I / O 模型》
《高性能网络编程(六):一文读懂高性能网络编程中的线程模型》
《高性能网络编程(七):到底什么是高并发?一文即懂!》
《以网游服务端的网络接入层设计为例,了解实时通信的技术挑战》
《知乎技术分享:知乎千万级并发的高性能长连贯网关技术实际》
《淘宝技术分享:手淘亿级挪动端接入层网关的技术演进之路》
《一套海量在线用户的挪动端 IM 架构设计实际分享(含具体图文)》
《一套原创分布式即时通讯(IM) 零碎实践架构计划》
《微信后盾基于工夫序的海量数据冷热分级架构设计实际》
《微信技术总监谈架构:微信之道——大道至简(演讲全文)》
《如何解读《微信技术总监谈架构:微信之道——大道至简》》
《疾速裂变:见证微信弱小后盾架构从 0 到 1 的演进历程(一)》
《17 年的实际:腾讯海量产品的技术方法论》
《腾讯资深架构师干货总结:一文读懂大型分布式系统设计的方方面面》
《以微博类利用场景为例,总结海量社交零碎的架构设计步骤》
《新手入门:零根底了解大型分布式架构的演进历史、技术原理、最佳实际》
《从老手到架构师,一篇就够:从 100 到 1000 万高并发的架构演进之路》

本文已同步公布于“即时通讯技术圈”公众号。

▲ 本文在公众号上的链接是:点此进入。同步公布链接是:http://www.52im.net/thread-3315-1-1.html

退出移动版