微服务架构:Zuul 1.0 和 2.0 我们该如何选择?

article/2025/9/22 7:58:41

作者:架构师杨波

来源:波波微课

在今年5月中,Netflix终于开源了它的支持异步调用模式的Zuul网关2.0版本,真可谓千呼万唤始出来。从Netflix的官方博文[附录1]中,我们获得的信息也比较令人振奋:

The Cloud Gateway team at Netflix runs and operates more than 80 clusters of Zuul 2, sending traffic to about 100 (and growing) backend service clusters which amounts to more than 1 million requests per second. Netflix部署了超过80+的Zuul2云网关集群,流量经过Zuul2集群被路由到后端超过100+的微服务,且每秒钟经过Zuul2集群的请求超过100万。

Zuul2看起来很强大,支持异步高并发(Zuul1仅支持同步)特性看起来很亮眼,那么我们是否就应该抛弃Zuul1,开始拥抱Zuul2呢?作为架构师,我们不能盲目追时髦,技术的选择必须基于实践和理性的分析,基于我之前对Zuul1的一线落地实战经验,也基于我近期对Zuul2的一些调研,我会在本文中对Zuul1和Zuul2做一个比较客观的编程模型和优劣分析,同时给出我的个人建议。

Zuul 1.0编程模型和优劣

640?wx_fmt=png

Zuul1设计比较简单,代码不多也比较容易读懂,它本质上就是一个同步Servlet,采用多线程阻塞模型,如上图所示[图片来自附录4]。

同步Servlet使用thread per connection方式处理请求。简单讲,每来一个请求,Servlet容器要为该请求分配一个线程专门负责处理这个请求,直到响应返回客户端这个线程才会被释放返回容器线程池。如果后台服务调用比较耗时,那么这个线程就会被阻塞,阻塞期间线程资源被占用,不能干其它事情。我们知道Servlet容器线程池的大小是有限制的,当前端请求量大,而后台慢服务比较多时,很容易耗尽容器线程池内的线程,造成容器无法接受新的请求,Netflix为此还专门研发了Hystrix[附录2]熔断组件来解决慢服务耗尽资源问题。

注意在上图Netflix给出的场景中,它的后台服务调用也是启动另外一个IO线程来处理的,但是本质上还是阻塞模式,后台IO线程在处理的时候,前台容器线程仍然是阻塞的。

同步阻塞模式有利有弊,分析如下图:

640?wx_fmt=png

优势

同步阻塞模式的编程模型比较简单,整个请求->处理->响应的流程(call flow)都是在一个线程中处理的,这样开发调试比较方便易于理解,比如出了问题跟踪调试比较方便。另外,线程局部变量(ThreadLocal)机制在同步多线程模式下可以工作,有些监控产品,例如CAT调用链依赖于ThreadLocal,在同步多线程模式下,CAT埋点比较方便,调用链关系的展示也比较直观。

不足

我们知道线程本身需要消耗CPU和内存资源,且多线程之间切换是有开销的(所谓的上下文切换Context Switch开销),线程越多,这种上下文切换的开销就越大,同步阻塞模式一般会启动很多的线程,必然引入线程切换开销。另外,同步阻塞模式下,容器线程池的数量一般是固定的,造成对连接数有一定限制,当后台服务慢,容器线程池易被耗尽,一旦耗尽容器会拒绝新的请求,这个时候容器线程其实并不忙,只是被后台服务调用IO阻塞,但是干不了其它事情。

总体上,同步阻塞模式比较适用于计算密集型(CPU bound)应用场景。对于IO密集型场景(IO bound),同步阻塞模式会白白消耗很多线程资源,它们都在等待IO的阻塞状态,没有做实质性工作。

Zuul 2.0编程模型和优劣

640?wx_fmt=png

Zuul2的设计相对比较复杂,代码也不太容易读懂,它采用了Netty实现异步非阻塞编程模型,如上图所示[图片来自附录4]。

一般异步模式的本质都是使用队列Queue(或称总线Bus),在上图中,你可以简单理解为前端有一个队列专门负责处理用户请求,后端有个队列专门负责处理后台服务调用,中间有个事件环线程(Event Loop Thread),它同时监听前后两个队列上的事件,有事件就触发回调函数处理事件。这种模式下需要的线程比较少,基本上每个CPU核上只需要一个事件环处理线程,前端的连接数可以很多,连接来了只需要进队列,不需要启动线程,事件环线程由事件触发,没有多线程阻塞问题。

异步非阻塞模式也是有利有弊,分析如下图:

640?wx_fmt=png

优势

异步非阻塞模式启动的线程很少,基本上一个CPU core上只需启一个事件环处理线程,它使用的线程资源就很少,上下文切换(Context Switch)开销也少。非阻塞模式可以接受的连接数大大增加,可以简单理解为请求来了只需要进队列,这个队列的容量可以设得很大,只要不超时,队列中的请求都会被依次处理。

不足

异步模式让编程模型变得复杂。一方面Zuul2本身的代码要比Zuul1复杂很多,Zuul1的代码比较容易看懂,Zuul2的代码看起来就比较费劲。另一方面异步模型没有一个明确清晰的请求->处理->响应执行流程(call flow),它的流程是通过事件触发的,请求处理的流程随时可能被切换断开,内部实现要通过一些关联id机制才能把整个执行流再串联起来,这就给开发调试运维引入了很多复杂性,比如你在IDE里头调试异步请求流就非常困难。另外ThreadLocal机制在这种异步模式下就不能简单工作,因为只有一个事件环线程,不是每个请求一个线程,也就没有线程局部的概念,所以对于CAT这种依赖于ThreadLocal才能工作的监控工具,调用链埋点就不好搞(实际可以工作但需要进行特殊处理)。

总体上,异步非阻塞模式比较适用于IO密集型(IO bound)场景,这种场景下系统大部分时间在处理IO,CPU计算比较轻,少量事件环线程就能处理。

Zuul1和Zuul2的性能比对

Netflix本身对网关使用异步非阻塞模式这件事情是非常谨慎的,它们进行了严格的性能测试,下面是Netflix给出的一些性能数据,来自Zuul2网关核心研发成员Arthur Gonigberg的ppt(Zuul's Journey to Non-Blocking)[附录3]:

640?wx_fmt=png

Netflix给出了一个比较模糊的数据,大致Zuul2的性能比Zuul1好20%左右,这里的性能主要指每节点每秒处理的请求数。为什么说模糊呢?因为这个数据受实际测试环境,流量场景模式等众多因素影响,你很难复现这个测试数据。即便这个20%的性能提升是确实的,其实这个性能提升也并不大,和异步引入的复杂性相比,这20%的提升是否值得是个问题。Netflix本身在其博文[附录4]和ppt[附录3]中也是有点含糊其词,甚至自身都有一些疑问的。

While we did not see a significant efficiency benefit in migrating to async and non-blocking, we did achieve the goals of connection scaling.

比较明确的是,Zuul2在连接数方面表现要好于Zuul1,也就是说Zuul2能接受更多的连接数。

Zuul 2.0架构和额外特性

640?wx_fmt=png

上图是Zuul2的架构,和Zuul1没有本质区别,两点变化:

  1. 前端用Netty Server代替Servlet,目的是支持前端异步。后端用Netty Client代替Http Client,目的是支持后端异步。

  2. 过滤器换了一下名字,用Inbound Filters代替Pre-routing Filters,用Endpoint Filter代替Routing Filter,用Outbound Filters代替Post-routing Filters。

640?wx_fmt=png

上图是Zuul2的一些功能亮点,我个人认为除了对HTTP/2的支持算是一个亮点,其它都是在安全、弹性和运维层面的一些优化,不能算新功能。其中像Request Passport,Status Categories,Request Attempts这些所谓的新功能,其实是为了减轻异步带来的复杂性,方便开发人员调试异步请求而专门开发的。

建议

基于上述分析,我对大家的建议是在生产环境中继续使用Zuul1,原因如下:

  1. Zuul1同步编程模型简单,门槛低,开发运维方便,容易调试定位问题。Zuul2门槛高,调试不方便。

  2. Zuul1监控埋点容易,比如和调用链监控工具CAT集成,如果你用Zuul2的话,CAT不好埋点是个问题。

  3. Zuul1已经开源超过6年,稳定成熟,坑已经被踩平。Zuul2刚开源很新,实际落地案例不多,难说有bug需要踩坑。

  4. 大部分公司达不到Netflix那个量级,Netflix是要应对每日千亿级流量,它们才挖空心思搞异步,一般公司亿级可能都不到,Zuul1绰绰有余。

  5. Zuul1可以集成Hystrix熔断组件,可以部分解决后台服务慢阻塞网关线程的问题。

  6. Zuul1可以使用Servlet 3.0规范支持的AsyncServlet进行优化,可以实现前端异步,支持更多的连接数,达到和Zuul2一样的效果,但是不用引入太多异步复杂性。波波和极客时间合作的课程《微服务架构和实践160讲》,7月份马上上线第三模块《微服务网关Zuul架构和实践》,其中会讲解Zuul1如何使用AsyncServlet优化连接数,欢迎大家关注。

对于Zuul2,我的建议是持谨慎观望的态度,可以在测试环境小规模实验验证,但是暂不上到生产环境。

结论

  1. 同步异步各有利弊,同步多线程编程模型简单,但会有线程开销和阻塞问题,异步非阻塞模式线程少并发高,但是编程模型变得复杂。

  2. 架构师做技术选型需要严谨务实,具备批判性思维(Critical Thinking),即使是对于一线大公司推出的开源产品,也要批判性看待,不可盲目追新。

  3. 个人建议生产环境继续使用Zuul1,同步阻塞模式的一些不足,可以使用熔断组件Hystrix和AsyncServlet等技术进行优化。波波和极客时间合作的课程《微服务架构和实践160讲》,7月份马上上线第三模块《微服务网关Zuul架构和实践》,其中会讲解对Zuul1的这些优化技术,欢迎大家关注。

附录

  1. Open Sourcing Zuul 2 https://medium.com/netflix-techblog/open-sourcing-zuul-2-82ea476cb2b3

  2. Hystrix https://github.com/netflix/hystrix

  3. Zuul's Journey to Non-Blocking https://github.com/strangeloop/StrangeLoop2017/blob/master/slides/ArthurGonigberg-ZuulsJourneyToNonBlocking.pdf

  4. Zuul2:Netflix's Journey to Asynchronous,Non-blocking Systems  https://medium.com/netflix-techblog/zuul-2-the-netflix-journey-to-asynchronous-non-blocking-systems-45947377fb5c

- END -


 往期推荐:

  • 死磕Java系列:


  1. 深入分析ThreadLocal

  2. 深入分析synchronized的实现原理

  3. 深入分析volatile的实现原理

  4. Java内存模型之happens-before

  5. Java内存模型之重排序

  6. Java内存模型之分析volatile

  7. Java内存模型之总结

  8. J.U.C之AQS简介

  9. J.U.C之AQS:CLH同步队列

  10. J.U.C之AQS同步状态的获取与释放

  11. J.U.C之AQS阻塞和唤醒线程

  12. J.U.C之重入锁:ReentrantLock

  13. J.U.C之读写锁:ReentrantReadWriteLock

  14. J.U.C之Condition

  15. J.U.C之并发工具类:CyclicBarrier

  16. J.U.C之并发工具类:Semaphore

  17. J.U.C之并发工具类:CountDownLatch

……

  • Spring系列:

  1. Spring Cloud Zuul中使用Swagger汇总API接口文档

  2. Spring Cloud Config Server迁移节点或容器化带来的问题

  3. Spring Cloud Config对特殊字符加密的处理

  4. Spring Boot使用@Async实现异步调用:使用Future以及定义超时

  5. Spring Cloud构建微服务架构:分布式配置中心(加密解密)

  6. Spring Boot快速开发利器:Spring Boot CLI

……



可关注我的公众号

640?wx_fmt=jpeg

深入交流、更多福利

扫码加入我的知识星球

640?wx_fmt=png

点击“阅读原文”,看本号其他精彩内容


http://chatgpt.dhexx.cn/article/4b6C3yWO.shtml

相关文章

Zuul2核心架构

Zuul2的核心架构就是就是两大体系,netty体系和filter体系。 1 Netty体系 Zuul2底层采用Netty的事件响应模式,要掌握zuul2就必须先要掌握Netty。 1.1 Channel、Event、EventLoop、EventLoopGroup、ChannelHandler Channel:每一次通信就会启…

【Zuul2】网关Zuul控制台DashBoard

目录 一、需求背景 二、实现方案 一、源码获取 二、源码分析 三、效果展示 三、相关问题 一、需求背景 用JAVA为开发语言的流控网关主要分为以下三种: Netflix Zuul/Zuul2Spring Cloud GateWayAlibaba Sentinel 从定位上来看,Zuul2与SpringClou…

【Zuul2】Zuul2网关介绍及案例(非spring集成)

目录 一.使用缘由 二.项目介绍 1.核心内容 (1)三种过滤器 Inbound、Endpoint 、Outbound (2)配置文件application.properties (3)动态配置application.properties 2.参考文档 一.使用缘由 公司需要在springcloudgateway和zuul2间做一次较为完整的调研对比,…

time_t c语言 2038,什么是2038问题?

什么是2038问题 不知道你有没有听过2038问题?无论你是否听过,本文将带你认识什么是2038问题。 Unix时间戳 定义为从格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。 而在C语言中,常用time_t来表示。举个例子: #include #in…

MySQL的时间戳2038年问题还有16年,最好在设计上的时候使用datetime就可以了,不要使用时间戳字段了,即使用了也不要用int类型进行映射,使用long类型映射即可

目录 前言1,关于MySQL时间戳的2038年BUG2,使用Docker创建MySQL 模拟下3,总结 前言 本文的原文连接是: https://blog.csdn.net/freewebsys/article/details/127455169 未经博主允许不得转载。 博主CSDN地址是:https://blog.csdn.n…

计算机为什么不用三十二进制,32位进制导致2038年问题

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 在计算机应用上,2038年问题可能会导致某些软件在2038年无法正常工作。所有使用UNIX时 间表示时间的程序都将受其影响,因为它们以自1970年1月1日经过的秒数(忽略闰秒)来表示时间。 这种时间表示法在类Unix(Unix-like)操作系统上是…

2038年问题 linux内核5.6,Linux Kernel 5.6 开发者已率先做好准备 应对 2038 年问题

新十年伊始,Linux Kernel 5.6的开发者已经准备好着手解决将在下一个十年到来的2038年问题(又称“Y2038”或“Unix Y2K”问题)。Linux 5.6也成为第一个为32位系统准备运行到2038年之后的主线内核。 2038年问题与千年虫问题类似,它可能会导致某些软件在203…

mysql 2038年问题_时间戳(UnixTimestamp)与 《2038年问题》

时间戳是从格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数。 现在时间戳的长度是十位(1435113975--2015/6/24 10:46:15)。 要到 2286/11/21 01:46:40 才会变成11位(10000000000),距离现在还有 271年。 不同时区获取的…

2038年问题 linux内核5.6,Linux Kernel 5.6 开发者已准备好应对 2038 年问题

2038 年问题与千年虫问题类似,它可能会导致某些软件在 2038 年 1 月 19 日 3 时 14 分 07 秒之后无法正常工作。届时,在大部分 32 位操作系统上,依据 “time_t” 标准,时间将会“绕回”且在内部被表示为一个负数,并造成…

2038年问题 linux内核5.6,Linux 5.1延续为2038年问题所做的多年准备

Linux 5.1为2038年问题在内核方面继续进行大量的工作。 多年来在Linux内核一直看到“Y2038”的工作,而这一努力远未结束。Thomas Gleixner为Linux 5.1内核提交了最新的Y2038工作,在之前的内核中做了大量基础工作之后,Linux 5.1内核引入了一组…

2038计算机系统,2038年问题

2038年问题是指在使用POSIX时间的32位计算机应用程序上,格林尼治时间2038年1月19日凌晨03:14:07(北京时间:2038年1月19日中午11:14:07)之后无法正常工作。 中文名 2038年问题 外文名 Year 2038 problem概 念 计算机bug(程序错误) 载 体 使用POSIX时…

聊一聊2038年问题

庚子年是中国传统的 60 甲子纪年法。擅长观测的古人很早就发现,每当年份执行到庚子这一年,自然灾害变多,突发事件频频,一些震动世界、影响安定的大事件也容易发生在这一年。而我们现在所处的 2020 年就是新一轮的庚子年&#xff0…

List集合去重 --指定对象属性去重

在针对特定场景下,将获取到的list<T> 集合 按照某一个特定的对象中的属性进行去重操作,以下代码会将传入的集合进行指定去重,会将指定属性重复的对象 只保留第一个,后续的重复则不会保存到去重后的集合中,当然我们也可以通过集合的差异获取出重复的对象以及后续的再将去…

使用Set集合对List集合进行去重

使用Set集合对List集合进行去重 前段时间正好遇到这样一个需求&#xff1a;我们的支付系统从对方系统得到存储明细对象的List集合&#xff0c;存储的明细对象对象的明细类简化为如下TradeDetail类&#xff0c;需求是这样的&#xff0c;我要对称List集合进行去重&#xff0c;这里…

关于两个List集合对象去重

实际项目开发中&#xff0c;很多业务场景下都会遇见集合去重。在说到两个对象去重之前&#xff0c;首先我们回顾下普通类型的list如何去重&#xff0c;这里先说两个list自带的方法&#xff0c;图画的不是太好&#xff0c;勿喷- -&#xff01; 一&#xff1a;retainAll() List&…

java list集合数据去重方式

1.概述 最近又是一轮代码review , 发现了一些实现去重的代码&#xff0c;在使用 list.contain … 我沉思&#xff0c;是不是其实很多初学者也存在这种去重使用问题&#xff1f; 所以我选择把这个事情整出来&#xff0c;分享一下。 2.contain 去重 首先是造出一个 List 模拟…

Java【List】去重的 6种方法

list集合去重 一、HashSet去重二、TreeSet去重三、LinkedHashSet去重四、迭代器去重五、Stream去重六、contains判断去重等等... 其它实现方法 一、HashSet去重 我们知道 HashSet 天生具备“去重”的特性&#xff0c;那我们只需要将 List 集合转换成 HashSet 集合就可以了&…

List 去重的 6 种方法,这种方法最完美!

在日常的业务开发中&#xff0c;偶尔会遇到需要将 List 集合中的重复数据去除掉的场景。这个时候可能有同学会问&#xff1a;为什么不直接使用 Set 或者 LinkedHashSet 呢&#xff1f;这样不就没有重复数据的问题了嘛&#xff1f; ​ 不得不说&#xff0c;能提这个问题的同学很…

List去除重复数据的五种方式

你知道的越多&#xff0c;不知道的就越多&#xff0c;业余的像一棵小草&#xff01; 你来&#xff0c;我们一起精进&#xff01;你不来&#xff0c;我和你的竞争对手一起精进&#xff01; 编辑&#xff1a;业余草 blog.csdn.net/qq_37939251/article/details/90713643 推荐&…

List元素去重的六种方式

上周的时候完成公司交付的任务&#xff0c;突然间遇到了需要把重复元素去掉的功能&#xff0c;当时我的大脑飞速运转&#xff0c;努力回想以前学习关于list的知识&#xff0c;后来&#xff0c;我发现已经忘得差不多了&#xff0c;所以我就找到了这篇文章&#xff0c;说的很详细…