文章 关于我 RSS 订阅 邮件订阅 Email

16 Feb 2018
绿色线程是如何提升服务器并发性能的

LightIO 是去年末开始写的一个库,给 Ruby 提供了低廉的绿色线程,并且可以通过 monkey patch 替代原有的 native thread。这样服务器端可以使用大量绿色线程,用较小的消耗来获得更好的并发性。经过一段时间的开发, LightIO 已经比较完善,并且 monkey patch 后可以成功和 Rails 、Puma 等共同使用。于是我开始考虑如何测试服务器使用 LightIO 后的性能,毕竟能真正的带来性能提升才有继续开发的动力和必要。

在性能测试前要考虑下绿色线程的原理,以及为什么可以带来性能提升?

LightIO 通过包装 Ruby 标准库的 Fiber 来提供绿色线程(在 LightIO 中叫 Beam),并维护一个绿色线程的调度系统。 这个『调度系统』并不复杂,简单来说只有三步:

  1. 检查 Beam,如果有 Beam 可以执行则执行。
  2. Beam 执行到 blocking 操作时,使用 nonblocking 接口替代,并挂起 Beam
  3. 检查完成的 nonblocking 操作,如果有则恢复挂起的 Beam。

从步骤中可以看出,绿色线程帮我们节省的是『blocking 操作』时的等待,遇到 blocking 操作时调度器会尽可能的调度更多的绿色线程(可以认为这就是并发性的提升)。从这点来看效果和 callback 异步编程是一样的,但绿色线程明显更能减少程序员的心智负担。

所谓『blocking 操作』: 主要是指 sleep 休眠线程, Thread#join 等线程同步,还有 Socket 的读写。

在 Web Server 中主要的 blocking 操作就是读写 Socket。比如一个典型的 Rails 程序,会有查询 RDB、Redis、请求外部 API 这些 blocking 操作。

一般来说这些操作也有异步 nonblocking 的调用接口,但假设在逻辑上客户端需要查询的结果,那么服务器必然要得到查询的结果才能响应给客户端,无论是使用同步接口还是异步接口服务器端都需要等待结果才能响应。所以一般直接使用 blocking 接口来处理并返回。

服务器为了同时处理更多的请求,需要开启更多的线程。因为创建线程有不小的消耗,所以一般选择使用线程池来处理请求,这样也会带来问题,假如线程池中的所有的线程都被 blocking,那么服务器会失去接受请求的能力。换而言之线程池的大小限制了服务器的并发能力(忽略机器和操作系统限制)。

如果能忽略线程创建时的消耗,最理想的情况是针对每个请求开启一个线程。

而绿色线程仅仅占用内存,Fiber(或其它语言的 coroutine…) 仅仅是个结构体,只需要占用几个 byte 记录指令地址。因为没有创建线程的昂贵消耗,可以针对每个请求启动一个绿色线程。将服务器并发能力发挥到最大。

考虑这几点,我应该尽可能选择贴近真实世界的情况来测试性能,而不是仅仅进行 Hello World 测试(这不会发挥绿色线程的优势),于是我决定使用 Discourse 来测试,Discourse 是一个典型的 Rails 应用并自带 benchmark 脚本。

Discourse + LightIO 的性能测试,放在下一篇博客继续


知识共享许可协议
本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。
如果对文章感兴趣,欢迎订阅我的博客:
知乎专栏RSS邮件订阅

Til next time,
JJY 2018.02.16

文章 关于我 RSS 订阅 邮件订阅 Email