本系列文章索引《响应式Spring的道法术器》
前情提要 Spring WebFlux快速上手 | Spring WebFlux性能测试

1.4.3 Netflix的异步化案例

前两节通过gatling和简单的示例,我们见识了Spring WebFlux的服务端和客户端的性能实力,在此基础上,也就不难理解下边的案例了。

Netflix是美国流媒体巨头、世界最大的收费视频网站,奥巴马也追、习大大也提的《纸牌屋》就是它的自制剧,几个月前还买下了国产剧《白夜追凶》的播放权。不过估计使用Spring Cloud的朋友可能比喜欢追剧的朋友对Netflix这个词更加眼熟,因为Netflix几乎开源了公司内部全部的微服务架构技术栈,Spring Cloud能够完美整合Netflix的开源架构。这套架构在Netflix公司大规模分布式微服务环境中经过数年的生产环境检验被证明是可靠的。

截止到2016年,Netflix已经拥有8300+万订阅用户,每天播放时间达到了1亿2千万小时,是北美互联网峰值下载量的1/3。为了支撑庞大的用户访问,Netflix从2009便下决心向云原生的微服务生态系统演进,在2016年完成了整体应用迁徙到云端,拥有500+的微服务,在系统演进的7年内,Netflix的流量增长了1000多倍,可谓是开着火箭换发动机。

下面介绍一下这个令人膜拜的微服务架构实践者是如何利用异步非阻塞的技术来提高其API网关性能的(参考Zuul 2 : The Netflix Journey to Asynchronous, Non-Blocking Systems)。

在微服务架构中,通常少不了服务API网关这样一个组件。用过Spring Cloud的朋友对Zuul这个组件应该是熟悉的。zuul是netflix开源的一个API Gateway服务器,云平台上提供动态路由,监控,弹性,安全等边缘服务,相当于是所有服务API的“前台”。

如果不了解,也没关系,先介绍一个Zuul的基础功能——路由:

(8)Netflix对API网关的异步化改造——响应式Spring的道法术器_第1张图片

如图,作为“前台”的Zuul能够根据一定的规则将到来的请求分发到各个具体的服务,仅就这一个功能来说,Zuul通常会承载较大流量,而且与上边第二个测试的例子类似——业务逻辑很少,它的响应时长的主要由被路由服务的响应时长决定。

2016年以前,Netflix的Zuul 1本质上一个web servlet应用,因此是多线程的、阻塞的,也就是说对每一个Http连接都会用一个单独的线程来处理。在Zuul 1中,对于IO操作,会再用一个工作线程来执行,工作线程中的IO操作执行完成后会通知处理请求的线程,操作完成前,后者是阻塞的。

这种方式与前边的测试项目restTemplate-as-caller的最后一次测试是类似的,只不过Zuul 1并非用的Reactor的Scheduler,而可能是采用ExecutorService或类似方式实现的线程池。

(8)Netflix对API网关的异步化改造——响应式Spring的道法术器_第2张图片

Netflix的服务托管于AWS,正常情况下这种工作方式在多核的AWS云实例中运转还OK,但是如果后端服务出现问题,就有可能造成连锁反应。

回想一下restTemplate-as-caller最后一次测试的结果,服务B的延迟是100ms,当并发用户量达到6000时,服务A的95%响应时长会达到236ms,如果还有一个服务C依赖于服务A呢,响应时间就更长了,再加上此时用户可能会不耐烦地频繁刷新页面,情况会更糟。这时候如果看一下CPU的load,可能并不高,因为几乎所有的请求都在阻塞和排队中。

这是一种恶性循环。而在一个分布式系统里,许多依赖不可避免的会出现类似的窘境甚至调用失败,因此Netflix又开发了Hystrix组件(也集成在Spring Cloud中)来应对这种问题,它提供了熔断、隔离、Fallback、cache、监控等功能,能够在一个或多个依赖出现超时、异常等问题时保证系统依然可用。

但Hystrix并非根本解决之道,问题的根源在于同步阻塞的服务调用。于是Netflix开发了Zuul 2,它基于Netty,以异步非阻塞的方式来处理请求,一个CPU核心专心处理一个线程,每一个请求的生命周期存在于Event Loop和Callback中。如下图所示:

(8)Netflix对API网关的异步化改造——响应式Spring的道法术器_第3张图片

从资源成本的角度来说,由于不用为每一个请求开辟独立的线程,能够避免CPU线程切换、大量线程栈内存造成的资源浪费,基本只剩下文件描述符和回调Listener的成本,从而Http连接成本显著降低。此外,由于工作在一个线程上,CPU除了不用来回奔波于成百上千的线程,还能更好地利用一二级CPU缓存,从而进一步提高性能。

Zuul 2上线之后的表现并未另Netflix失望,它轻松搞定了8300万用户(每个用户还有多个设备或浏览器)的持久连接(persistent connection)。

不同于计算密集型的应用,WEB应用通常是高并发和I/O密集型的,尤其是在微服务架构的应用中,CPU执行时间相对于阻塞时间来说通常要短得多,越是如此,异步非阻塞越能发挥出显著的性能提升效果。从这个案例可以看到,以异步非阻塞的方式代替阻塞和多线程方式是提高性能的有效途径。