@babydragon
2016-10-17T11:31:29.000000Z
字数 2043
阅读 3394
架构
最近,Netflix将它们的网关服务Zuul进行了升级,全新的Zuul 2将HTTP请求的处理方式从同步变成了异步。本文主要探讨异步化之后的架构及其带来的变化。
最近,Netflix将它们的网关服务Zuul进行了升级,全新的Zuul 2将HTTP请求的处理方式从同步变成了异步。Zuul是Netflix的网关服务,负责接受所有来自外部的请求,并将它们分发到内部集群。
和大部分基于Java的Web应用类似,Zuul也采用了servlet架构,因此Zuul处理每个请求的方式是针对每个请求是用一个线程来处理。通常情况下,为了提高性能,所有请求会被放到处理队列中,从线程池中选取空闲线程来处理该请求。这样的设计方式,足以应付一般的高并发场景。
上图描述了一个典型的多线程阻塞型架构的运行方式:对于每个请求,由一个专门的线程来进行处理,整个处理流程在线程内是阻塞的。由图可见,当一个请求处理速度很慢(如遇到响应很慢的后段应用),可能会影响整个系统的响应。为了应对这种情况,Netflix也有针对的解决方案:Hystrix。
上一节介绍了同步系统的设计,和同步系统设计方式不同,异步系统通常设计成事件驱动。
如上图所示,当请求到达时,异步系统会将其包装成一个事件,提交到事件循环中。事件循环中会维护一系列的监听器、处理器,针对事件做出一系列的处理,最终将结果返回给用户。这种设计模式通常被称作“反应堆模式(Reactor pattern)”相比于同步多线程系统,异步事件系统可以以较少的线程(甚至是单线程)来处理所有的请求。
从前文描述可以看出,相较于同步模型,异步模型则依赖更少的线程资源,理论上可以支撑更高的并发。通常情况下,异步事件系统会使用连接处理线程池和事件处理线程池,前者用于处理外部客户端的连接请求,创建事件后提交到事件处理线程池;后者用于每个处理事件。较少的线程和线程切换可以降低系统开销。
也正是由于异步系统线程以事件级别复用,对于调试、异常处理等带来了难度。在同步系统中,整个请求的处理都在同一个线程中完成,我们可以通过打印线程栈信息来知道请求处理流程;当发生异常时,也可以简单的捕获所有异常,清理请求残留的所有资源。但是在异步系统中,获取事件循环线程的线程栈已经没有意义,通过其栈信息已经无法了解单个请求的处理流程。同时,由于事件处理通常以回调的方式调用,也大大增加了单步调试的成本,调试者必须清楚的了解事件处理的每个处理器顺序,才有可能完整的跟踪整个请求的处理流程。另外,由于资源的分配、释放可能在不同代码位置、不同线程,一旦处理不当,很容易造成资源泄露(如常见的文件句柄泄露、NIO缓存泄露等)。
对于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等功能,使得社区使用者能够从中受益。