【Vert.x准备篇2】C10K问题与Reactor模式

知乎专栏: 关于Vert.x你需要知道的一切

C10K问题是1999年一个叫Dan Kegel的美国人提出的概念,其中Cconcurrently, 10K指的是1万个网络连接, 结合起来意为如何能够做到并发处理1万个连接。

这里首先要澄清一下,并发(concurrency)和并行(parallel)虽然都是用来描述"同时"干多件事的名词,但他们是有本质区别的。并发指的是CPU通过在不同的线程之间快速切换来营造出一种多个线程同时在执行的假象, 而并行则是真正意义上的多个线程在同时运行。

对于现代操作系统来说,C10K问题的核心在于线程(或进程)无法随着连接数的增加而无休止的创建。对于web应用,我们平时最常用也是最熟悉的线程模型就是"一请求一线程"模型,但它难以解决C10K的原因是单个线程会占用较多的内存资源,内存的有限性就决定了线程的总数是有限制的。即便现在很多企业级服务器都是100G+的内存,线程切换所带来的开销也会随着线程数量的增加而增加,从而进一步限制了有效线程的数量。这样看来解决问题的唯一方法,就是想办法让单个线程能在不发生阻塞的前提下处理多个连接了。于是,在操作系统的支持下,reactor模式诞生。

Reactor模式

Reactor是一种基于事件驱动的设计模式,它可以做到只用少量的线程就能处理大量的I/O操作。简单来说,一个Reactor就是一个事件循环,执行这个循环的线程会阻塞在多路复用器的select()调用中,当感兴趣的I/O事件发生时,操作系统会让select()函数返回,同时告诉你发生了哪些事件,然后当前线程会将事件分发给事件对应的处理器(Handler)来进行处理, 处理完成后再进入下一次循环,以此类推。在这个过程中主要有以下三个核心组件:

  • Reactor

由一条线程执行的无限循环,其任务就是等待操作系统通知I/O ready事件的发生然后将这些事件分派给对应的处理器来处理。

  • Demultiplex

即多路复用器,其作用为让当前线程阻塞在等待事件发生的过程上,然后在事件发生时返回。其实多路复用起初是通讯工程中的术语,本意是让多种不同的信号在同一条物理线路上传输。在这里多路是指可同时监听多个I/O事件,复用是指对这些事件的处理复用同一条线程。前面我们在说Reactor的诞生有一个前提条件,即必须有操作系统的支持。在这里操作系统就扮演着通知应用程序的角色,具体的通知方式在不同的OS有着不同的实现,如epoll(Linux), k-queue(freeBSD), iocp(windows)等。

  • Handler

事件处理器。事件处理器首先要向多路复用器告知自己对哪些事件感兴趣,然后当这些事件发生时自身会被调用。其实Handler就是我们需要实现的业务逻辑,但需要注意的一点是,无论如何都不能阻塞Handler, 因为这会直接block整个事件循环。

现在我们来看一下Reactor是如何做到用少量线程来处理大量I/O的。假定现在已经建立了10个HTTP连接且我们想让服务器同时为这10个client服务而不是像排队一样完成前一个再处理下一个。在一请求一线程的模型下,我们需要启动10条线程来调用receive()方法并block在这个方法调用上, 这样只要有请求数据到来这些线程就会马上进行处理。不过在Reactor模式里,我们可以只使用一条线程先向Demultiplex注册一下我们对这10个连接的"读就绪"事件感兴趣并绑定对应的Handler,之后block在select()调用上; 当事件发生后(如这10个连接的读就绪事件同时发生),我们的主线程从select()中返回,主线程遍历所有的事件并调用对应的事件处理器完成业务逻辑。这样我们仅用一个线程就完成了先前10个线程才能完成的工作。当然,这里是有一些限制条件的,比如Handler中绝对不允许出现阻塞代码。但是业务逻辑难免会有像数据库查询这样的阻塞且耗时的调用, 该怎么办呢?答案是在Handler中只发起DB查询然后立即返回,发起后告知Demultiplex我们对DB查询完成的事件感兴趣, 当DB返回结果时再唤醒Handler进行后续处理。在Vert.x中,注册和等待事件ready的逻辑框架都会为我们代劳,我们只需要专注于编写Handler即可。

世面上各框架在实现Reactor时都会有很多变种,例如在Vert.x中,Reactor会有多个,其负责执行Reactor事件循环的NIO线程也会有多条, 而不是上面最简单的单Reactor模型。

从上面的讨论可以看到,使用Reactor模式并不能降低服务器对于单个请求处理的总耗时,而是能最大限度的减少线程阻塞(提高CPU利用率),从而大大减少了处理10K连接时需要的线程数量,进而提高了服务器的并发处理能力。不过这样也给程序员带来了麻烦,即我们需要为各种会block线程的操作注册各种回调,导致业务逻辑从先前线性的"一本道"变成了被迫分散在各个回调方法中。鱼和熊掌不可兼得,如果你真遇到了高并发问题,那么就只能牺牲一下代码了【Go语言除外😏】。

与Proactor的区别

谈到reactor就不得不提一句proactor。这二者最根本的区别在于,Reactor中监听的是I/O就绪事件,此时数据还在操作系统的内核缓冲区,线程被唤醒后需要主动将数据从内核缓冲区读取到用户进程中; 而Proactor里用户进程监听的是I/O完成事件,即当线程被唤醒时,数据已经从内核转移到进程中了,这个过程是操作系统帮你完成的,你只需要在发起I/O时提供一个buffer, 等I/O完成时buffer已经被填满,而不需要你手动从read()中获取了。

Tomcat为什么"搞不定"高并发

首先,这不是tomcat的锅,而是servlet规范(3.0之前)和业务代码的问题。自Tomcat6开始就已经支持了JDK的NIO, 可以使用少量的线程处理大量的I/O事件,问题在于servlet和你的业务代码是同步的。也就是说,即便tomcat使用了某种黑魔法,仅用了一个线程就能搞定N个连接的创建和读写操作,但当tomcat调用servlet处理业务逻辑时仍然需要从维护的worker线程池中取一个线程来执行,这就又回到一请求一线程的模式了----只有同时启动10K条线程才能真正完成10K个请求的处理,否则后面的请求尽管已经完成了连接的建立和数据的接收,也只能是在一味的等待,等待前面的worker线程干完活才能来处理后面的请求。所以,只要servlet和你的业务代码也异步起来,Tomcat完全可以搞定C10K。只可惜,servlet3.0来的太晚了,异步编程的江山已经被Netty, Vert.x, Akka和Node.js这样的框架(工具)瓜分完毕了。

下一篇我们会介绍Vert.x中的核心组件,以及它是如何实现Reactor模式的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容