写点什么

Zuul 2 : Netflix 的异步之旅

2016 年 11 月 01 日

最近,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 )关注我们。

2016 年 11 月 01 日 17:4912317

评论

发布
暂无评论
发现更多内容

Spring 5 中文解析测试篇-Spring测试

青年IT男

Spring5 JUnit

oeasy教您玩转linux010206 蒸汽机车 sl

o

LeetCode题解:239. 滑动窗口最大值,单调队列,JavaScript,详细注释

Lee Chen

LeetCode 前端进阶训练营

翻转链表,机器学习视觉训练,对数据的人工标注,使信息丢失,John 易筋 ARTS 打卡 Week 16

John(易筋)

学习 ARTS 打卡计划 翻转链表 Google论文评判人工预处理 大数据架构Spark

usdt跨境入金支付系统搭建|区块链跑分系统开发

WX13823153201

usdt支付系统开发|承兑商支付跑分系统搭建

WX13823153201

usdt支付系统开发

3. 站在使用层面,Bean Validation这些标准接口你需要烂熟于胸

YourBatman

Hibernate-Validator Bean Validation 数据校验

[翻译] Go Concurrency Patterns: Pipelines and cancellation[Go并发模式]

卓丁

channel pipeline

你问我答:容器平台改造后的安全是如何解决的?

博云技术社区

云计算 容器 微服务 PaaS 博云

甲方日常 8

句子

工作 随笔杂谈 日常

区块链交易所搭建技术方案|去中心化交易所开发

WX13823153201

区块链交易所搭建

有奖征文重磅来袭,来!一起玩把大的!

小红豆

实战解读丨Linux下实现高并发socket最大连接数的配置方法

华为云开发者社区

Linux TCP socket 高并发

深入理解JVM垃圾回收机制 - GC Roots枚举

NORTH

GC Roots枚举 安全点 安全区域 OopMap

ARTS打卡 第15周

引花眠

微服务 ARTS 打卡计划

学习 Java,有什么书籍推荐?学习的方法和过程是怎样的?

沉默王二

Java 学习 程序员 书单

不支持原子性的 Redis 事务也叫事务吗?

海星

Java redis 事务

用函数式写法精简Java代码的一个例子

Sean

Java 函数式编程

一点思考|工作十几年了,竟从未用过do-while!

王磊

Java

架构师训练营第十三周总结

张明森

给大家介绍下,这是我的流程图软件 —— draw.io

程序员小航

工具 流程图 draw.io drawio-desktop 画图软件

读后感之《任正非:以客户为中心》

王新涵

程序的机器级表示-数组的分配和访问

引花眠

计算机基础

从湖南“软硬兼施”,管窥三湘水畔的智能浪潮

脑极体

阿里巴巴发布国内首个公益区块链标准 用技术让公益公开透明

CECBC区块链专委会

区块链 公益

Go: gops如何与Go运行时交互?

陈思敏捷

go golang gops

握草,你竟然在代码里下毒!

小傅哥

Java 程序员 小傅哥 bug 有毒代码

给DevOps加点料——融入安全性的DevSecOps

陈琦

DevOps 运维 测试 开发 安全性

Golang Package sync 透析

卓丁

golang sync

数字货币交易所开发方案|去中心化交易所搭建

WX13823153201

数字货币交易所开发

ARTS Week15

时之虫

ARTS 打卡计划

InfoQ 极客传媒开发者生态共创计划线上发布会

InfoQ 极客传媒开发者生态共创计划线上发布会

Zuul 2 : Netflix的异步之旅-InfoQ