最近,Netflix 将它们的网关服务 Zuul 进行了升级,全新的 Zuul 2 将 HTTP 请求的处理方式从同步变成了异步。Zuul 是 Netflix 的网关服务,负责接受所有来自外部的请求,并将它们分发到内部集群。
从同步开始
和大部分基于 Java 的 Web 应用类似,Zuul 也采用了 servlet 架构,因此 Zuul 处理每个请求的方式是针对每个请求是用一个线程来处理。通常情况下,为了提高性能,所有请求会被放到处理队列中,从线程池中选取空闲线程来处理该请求。这样的设计方式,足以应付一般的高并发场景。
(点击放大图像)
上图描述了一个典型的多线程阻塞型架构的运行方式:对于每个请求,由一个专门的线程来进行处理,整个处理流程在线程内是阻塞的。由图可见,当一个请求处理速度很慢(如遇到响应很慢的后段应用),可能会影响整个系统的响应。为了应对这种情况,Netflix 也有针对的解决方案: Hystrix 。
走向异步
上一节介绍了同步系统的设计,和同步系统设计方式不同,异步系统通常设计成事件驱动。
(点击放大图像)
如上图所示,当请求到达时,异步系统会将其包装成一个事件,提交到事件循环中。事件循环中会维护一系列的监听器、处理器,针对事件做出一系列的处理,最终将结果返回给用户。这种设计模式通常被称作“反应堆模式(Reactor pattern)”相比于同步多线程系统,异步事件系统可以以较少的线程(甚至是单线程)来处理所有的请求。
同步vs 异步
从前文描述可以看出,相较于同步模型,异步模型则依赖更少的线程资源,理论上可以支撑更高的并发。通常情况下,异步事件系统会使用连接处理线程池和事件处理线程池,前者用于处理外部客户端的连接请求,创建事件后提交到事件处理线程池;后者用于每个处理事件。较少的线程和线程切换可以降低系统开销。
也正是由于异步系统线程以事件级别复用,对于调试、异常处理等带来了难度。在同步系统中,整个请求的处理都在同一个线程中完成,我们可以通过打印线程栈信息来知道请求处理流程;当发生异常时,也可以简单的捕获所有异常,清理请求残留的所有资源。但是在异步系统中,获取事件循环线程的线程栈已经没有意义,通过其栈信息已经无法了解单个请求的处理流程。同时,由于事件处理通常以回调的方式调用,也大大增加了单步调试的成本,调试者必须清楚的了解事件处理的每个处理器顺序,才有可能完整的跟踪整个请求的处理流程。另外,由于资源的分配、释放可能在不同代码位置、不同线程,一旦处理不当,很容易造成资源泄露(如常见的文件句柄泄露、NIO 缓存泄露等)。
构建异步Zuul
对于Netflix 来说,构建异步的Zuul 并没有想象中的那么容易。首先,Netflix 之前的网络库使用的都是同步方式,还有很多库使用了线程变量(ThreadLocal)。对于异步系统来说,线程变量已经无效,因为大量的请求会在同一个线程中进行处理, 所以Zuul 2 的构建过程中,遇到的最大问题是将各处使用线程变量的地方找出来并进行修改。其他大的工作量在于将现有库中使用同步处理的方式,逐步改造成异步的方式。由于没有一个通用的方式来进行快速修改,这些地方的改造只能针对每个业务逻辑单独修改。不过在整个过程中,Netflix 使用了开源工具 Reactive-Audit 来帮助他们查找隐藏在现有库中的同步逻辑。
由于同步系统中可以运行用于异步系统的代码,整个改造流程从 Zuul 已有的过滤器(Zuul filter)开始。Zuul 过滤器包含了网关服务的主要功能(如路由、日志、反向代理、ddos 预防等)。重构之后,Zuul 核心包中的过滤器等组件均能够支持异步的运行,同时能够兼容之前的同步模式。这使得原有同步模式和基于Netty 的异步模式的开发可以在同一个代码库中进行。剩余的工作就是使用异步的方式重写剩余基础设施的代码。
异步化后的效果
Netflix 已经替换了部分 Zuul 为 Zuul 2,但是在实际使用中并没有发现相较于之前的集群有太大的改变。虽然前文分析了异步系统可能带来的优势,但是就目前的使用而言,从 CPU 使用率到系统负载都没有太大的变化。
在众多 Zuul 集群中,异步化之后有明显变化的是日志记录服务前端,它主要用于接收和记录设备上报的日志。因此,该服务写权重很高,并且请求数据量远大于响应数据量。在这样的服务上,基于 Netty 的异步 Zuul 节点在提升了 25% 的吞吐量的基础上,还降低了 25% 的 CPU 利用率。从这里我们可以看出,系统业务逻辑约简单,异步系统带来的收益就越大。
总的来说,这次架构重构对 Netflix 来说是收益颇丰,特别是在对连接的扩容方面,但随之而来的是调试、编码、测试复杂度的上升。Netflix 正在着手将 Zuul 2 进行开源,并且新增诸如 HTTP/2、websocket 等功能,使得社区使用者能够从中受益。
感谢陈兴璐对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ , @丁晓昀),微信(微信号: InfoQChina )关注我们。
评论