首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

微服务网关Zuul和Gateway的区别

  • 25-03-02 13:40
  • 3622
  • 11852
blog.csdn.net

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xc1158840657/article/details/90712084

 

微服务系列(一)聊聊服务网关
前几年随着分布式架构的演变,微服务开始兴起,自然也产生了一系列支持微服务的框架,例如本文要聊到的Spring Cloud。

Spring 相信做Java的小伙伴们已经耳熟能详了,也正是应该这个Spring生态获得广大的关注,在Spring之上开发的新兴框架如Spring Boot、Spring Cloud也很快让大家熟知。

下面主要针对Spring Cloud聊聊它所实现的服务网关,并针对市面上常用的Nginx做一些分析和比较。

由于笔者比较熟悉和擅长的语言是Java,所以仅针对Java框架来做一些源码层面的分析。

服务网关的角色
常见微服务架构

可以看到,网关层是最外层,浏览器与服务器交互时经过的第一个服务节点,它主要起屏蔽下游业务服务的作用,对于浏览器而言,只需要跟网关交互就相当于在与下游多个业务服务节点交互,让浏览器觉得他在和一台服务器交互。

这样的好处显而易见,不管是下游业务服务、支撑服务、基础服务,都对于浏览器屏蔽,与服务器的交互变的非常简单,浏览器无需关心各个节点的依赖关系、如何协同工作,浏览器只会了解到本次请求是否成功;开发者可以灵活的增加业务服务模块;可以在网关层做一些最上层的公用的操作,如过滤恶意请求、设置ip黑白名单、做身份认证、限流、负载均衡等。

换个角度考虑一下,如果去掉网关层,浏览器交互的最外层服务是业务服务层,由于需要解决单点登陆问题,必须在每个业务服务节点上多扮演一个auth client的角色,从开发的角度上看,明显增加了复杂度,试问本可以只需要在一个网关服务上构建auth client,为何要选择在多个(并且可能还会增加)的业务服务上构建auth client呢?

另外,从开发上讲可能还需要解决跨域请求的问题,前后端分离架构中后端api的展示也会是一个问题,也不便于管理;对于同一服务多节点的负载均衡也不好实现,难道需要浏览器每次访问前都去访问一次注册中心?

通过分析发现,微服务架构中,对于再小的业务量的项目,服务网关都是必不可少的。

Spring Cloud Netflix Zuul和Spring Cloud Gateway
Zuul在早期微服务架构中用的非常广泛,如今Spring Cloud推出了Spring Cloud Gateway,那么作为开发者,应该考虑以下问题:该如何选择?他们之间的差异有哪些?各有什么优势呢?

下面从源码入手,探索Zuul的工作原理,尝试理解他的设计理念。

为了图方便,就不去github上download源码了,直接在pom引入依赖,开干…进入com.netflix.zuul.http.ZuulServlet

  1. public class ZuulServlet extends HttpServlet {
  2. private ZuulRunner zuulRunner;
  3. public void init(ServletConfig config) throws ServletException {
  4. ...
  5. }
  6. public void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {
  7. ...
  8. }
  9. }

它继承了HttpServlet,熟悉吗?不熟悉的话,可以打开你熟悉的DispatchServlet,看看它继承了谁?

也就是说,它本质上用了java.servlet API,实现了一个有网关功能的servlet。

那么继续观察一下它的com.netflix.zuul.http.ZuulServlet#service方法:
--------------------- 
版权声明:本文为CSDN博主「XCXCXCXCX__」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/xc1158840657/article/details/90712084

  1. try {
  2. init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);
  3. // Marks this request as having passed through the "Zuul engine", as opposed to servlets
  4. // explicitly bound in web.xml, for which requests will not have the same data attached
  5. RequestContext context = RequestContext.getCurrentContext();
  6. context.setZuulEngineRan();
  7. try {
  8. preRoute();
  9. } catch (ZuulException e) {
  10. error(e);
  11. postRoute();
  12. return;
  13. }
  14. try {
  15. route();
  16. } catch (ZuulException e) {
  17. error(e);
  18. postRoute();
  19. return;
  20. }
  21. try {
  22. postRoute();
  23. } catch (ZuulException e) {
  24. error(e);
  25. return;
  26. }
  27. } catch (Throwable e) {
  28. error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));
  29. } finally {
  30. RequestContext.getCurrentContext().unset();
  31. }

可以看到,它做了以下几件事:

  1. 前置路由
  2. 路由
  3. 后置路由
  4. 异常处理

不管是preRoute()、route()、postRoute()、error(),它们最终调用了com.netflix.zuul.FilterProcessor#runFilters

  1. public Object runFilters(String sType) throws Throwable {
  2. if (RequestContext.getCurrentContext().debugRouting()) {
  3. Debug.addRoutingDebug("Invoking {" + sType + "} type filters");
  4. }
  5. boolean bResult = false;
  6. List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);
  7. if (list != null) {
  8. for (int i = 0; i < list.size(); i++) {
  9. //ZuulFilter list
  10. //轮流执行ZuulFilter的逻辑,result=false或执行完所有ZuulFilter时调用链结束
  11. ZuulFilter zuulFilter = list.get(i);
  12. Object result = processZuulFilter(zuulFilter);
  13. if (result != null && result instanceof Boolean) {
  14. bResult |= ((Boolean) result);
  15. }
  16. }
  17. }
  18. return bResult;
  19. }

这里FilterLoader的源码就不深入分析了,它主要的功能是:

从FilterRegistry(相当于内存中的filter)加载Zuulfilter list
编译groovy文件(笔者的Zuul版本1.3.0,目前仅支持groovy文件)并加载Zuulfilter
对于FilterRegistry,则是用于内存中保存filter,可以动态变化的,注册新的filter以及移除filter等,可提供给jmx、endpoint做远程控制。

继续看看com.netflix.zuul.FilterProcessor#processZuulFilter

  1. public Object processZuulFilter(ZuulFilter filter) throws ZuulException {
  2. RequestContext ctx = RequestContext.getCurrentContext();
  3. boolean bDebug = ctx.debugRouting();
  4. final String metricPrefix = "zuul.filter-";
  5. long execTime = 0;
  6. String filterName = "";
  7. try {
  8. long ltime = System.currentTimeMillis();
  9. filterName = filter.getClass().getSimpleName();
  10. RequestContext copy = null;
  11. Object o = null;
  12. Throwable t = null;
  13. if (bDebug) {
  14. Debug.addRoutingDebug("Filter " + filter.filterType() + " " + filter.filterOrder() + " " + filterName);
  15. copy = ctx.copy();
  16. }
  17. //执行ZuulFilter的runFilter逻辑
  18. ZuulFilterResult result = filter.runFilter();
  19. ExecutionStatus s = result.getStatus();
  20. //执行耗时统计(可以发现Zuul还没有完善这个功能,只是形成了框架)
  21. execTime = System.currentTimeMillis() - ltime;
  22. //处理执行结果,无论成功与否,都记录了debug日志
  23. switch (s) {
  24. case FAILED:
  25. t = result.getException();
  26. ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
  27. break;
  28. case SUCCESS:
  29. o = result.getResult();
  30. ctx.addFilterExecutionSummary(filterName, ExecutionStatus.SUCCESS.name(), execTime);
  31. if (bDebug) {
  32. Debug.addRoutingDebug("Filter {" + filterName + " TYPE:" + filter.filterType() + " ORDER:" + filter.filterOrder() + "} Execution time = " + execTime + "ms");
  33. Debug.compareContextState(filterName, copy);
  34. }
  35. break;
  36. default:
  37. break;
  38. }
  39. if (t != null) throw t;
  40. //目前作为空壳存在,可见是为了方便扩展
  41. usageNotifier.notify(filter, s);
  42. return o;
  43. } catch (Throwable e) {
  44. if (bDebug) {
  45. Debug.addRoutingDebug("Running Filter failed " + filterName + " type:" + filter.filterType() + " order:" + filter.filterOrder() + " " + e.getMessage());
  46. }
  47. usageNotifier.notify(filter, ExecutionStatus.FAILED);
  48. if (e instanceof ZuulException) {
  49. throw (ZuulException) e;
  50. } else {
  51. ZuulException ex = new ZuulException(e, "Filter threw Exception", 500, filter.filterType() + ":" + filterName);
  52. ctx.addFilterExecutionSummary(filterName, ExecutionStatus.FAILED.name(), execTime);
  53. throw ex;
  54. }
  55. }
  56. }

这样一来,Zuul基本原理走完了,可以看出来非常的简单,实际上整个调用链是由ZuulFilter来组成,对于用户而言,只需要关心如果构建自定义的ZuulFilter以及它们之间的顺序。

Zuul提供了com.netflix.zuul.filters.StaticResponseFilter和com.netflix.zuul.filters.SurgicalDebugFilter两种抽象类,StaticResponseFilter会将请求直接处理并返回,即不会经过路由链路;SurgicalDebugFilter则会将请求路由到zuul.debug.vip 或 zuul.debug.host所指定的debug Eureka “VIP” or host。

另外,还有一个类也需要关注,com.netflix.zuul.filters.ZuulServletFilter,通过源码方向追踪后发现usages均是Test类,可见它应该是一个待开发的功能,去允许用户在路由前做过滤处理。

看完了源码,了解了工作原理后,整理一下Zuul的特点:

很明显,由于底层是servlet,Zuul处理的是http请求
Zuul的抽象写的非常简单易懂,易于扩展,易于debug
提供了两种特殊的抽象类,用户使用起来,比较灵活
zuul-core包不依赖Spring,依赖的包很少
没有提供异步支持
流控等均由hystrix支持
那么继续开始分析Spring Cloud Gateway源码…

Spring Cloud Gateway的代码相比zuul会比较难懂,特别是对于不熟悉流式编程的小伙伴来说。

我可以给个建议,如果实在是看不懂、从头到尾很懵的话,请结合spring mvc的源码对照来理解!

先来看看这个类org.springframework.cloud.gateway.config.GatewayAutoConfiguration

  1. @Configuration
  2. @ConditionalOnProperty(name = "spring.cloud.gateway.enabled", matchIfMissing = true)
  3. @EnableConfigurationProperties
  4. @AutoConfigureBefore(HttpHandlerAutoConfiguration.class)
  5. @AutoConfigureAfter({GatewayLoadBalancerClientAutoConfiguration.class, GatewayClassPathWarningAutoConfiguration.class})
  6. @ConditionalOnClass(DispatcherHandler.class)
  7. public class GatewayAutoConfiguration {
  8. ...
  9. //由于内容比较多,就不全部贴出来了
  10. @Bean
  11. @ConditionalOnBean(DispatcherHandler.class)
  12. public ForwardRoutingFilter forwardRoutingFilter(DispatcherHandler dispatcherHandler) {
  13. return new ForwardRoutingFilter(dispatcherHandler);
  14. }
  15. ...
  16. }

继续点进去org.springframework.web.reactive.DispatcherHandler,先不用看它的源码,reactive???它是webflux的核心组件!!!

由于webflux的基本原理和webmvc大同小异,就不仔细分析其源码了,但需要了解的一点是,webflux大量运用流式编程,代码非常简短,也很契合的支持请求异步处理。

那么继续追踪org.springframework.cloud.gateway.filter.ForwardRoutingFilter,找到一个重要的接口org.springframework.cloud.gateway.filter.GlobalFilter。

注释:

Contract for interception-style, chained processing of Web requests that may be used to implement cross-cutting, application-agnostic requirements such as security, timeouts, and others.

译:

用于拦截式Web连接处理的合同,可用于实现跨领域,与应用程序无关的要求,如安全性,超时等。

这里先放着,后边会用到它。

发现了底层由org.springframework.web.reactive.DispatcherHandler来支持后,那么猜想可能会基于DispatcherHandler怎么做扩展呢?

继承DispatcherHandler重新实现分发逻辑(类似Zuul)
扩展DispatcherHandler的组件HandlerMapping
找到一个重要的类org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping,猜想(2)验证

org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping#getHandlerInternal

  1. protected Mono<?> getHandlerInternal(ServerWebExcha nge exchange) {
  2. exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getClass().getSimpleName());
  3. return lookupRoute(exchange)//根据exchange找匹配的route
  4. // .log("route-predicate-handler-mapping", Level.FINER) //name this
  5. .flatMap((Function<Route, Mono<?>>) r -> {//替换请求attributes值
  6. exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
  7. if (logger.isDebugEnabled()) {
  8. logger.debug("Mapping [" + getExchangeDesc(exchange) + "] to " + r);
  9. }
  10. exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);
  11. return Mono.just(webHandler);//执行filter的handle方法
  12. }).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {
  13. exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);
  14. if (logger.isTraceEnabled()) {
  15. logger.trace("No RouteDefinition found for [" + getExchangeDesc(exchange) + "]");
  16. }
  17. })));//异常处理
  18. }

org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping#lookupRoute

  1. protected Mono<Route> lookupRoute(ServerWebExchange exchange) {
  2. return this.routeLocator.getRoutes()
  3. .filterWhen(route -> {//找到匹配的route,一个route包含一个filter链
  4. // add the current route we are testing
  5. exchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, route.getId());
  6. return route.getPredicate().apply(exchange);
  7. })
  8. // .defaultIfEmpty() put a static Route not found
  9. // or .switchIfEmpty()
  10. // .switchIfEmpty(Mono.<Route>empty().log("noroute"))
  11. .next()
  12. //TODO: error handling
  13. .map(route -> {
  14. if (logger.isDebugEnabled()) {
  15. logger.debug("Route matched: " + route.getId());
  16. }
  17. validateRoute(route, exchange);
  18. return route;
  19. });
  20. /* TODO: trace logging
  21. if (logger.isTraceEnabled()) {
  22. logger.trace("RouteDefinition did not match: " + routeDefinition.getId());
  23. }*/
  24. }

org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getRoutes

  1. @Override
  2. public Flux<Route> getRoutes() {
  3. return this.routeDefinitionLocator.getRouteDefinitions()
  4. .map(this::convertToRoute)
  5. //TODO: error handling
  6. .map(route -> {
  7. if (logger.isDebugEnabled()) {
  8. logger.debug("RouteDefinition matched: " + route.getId());
  9. }
  10. return route;
  11. });
  12. /* TODO: trace logging
  13. if (logger.isTraceEnabled()) {
  14. logger.trace("RouteDefinition did not match: " + routeDefinition.getId());
  15. }*/
  16. }

这里又发现了一个类org.springframework.cloud.gateway.route.RouteDefinitionLocator

  1. public interface RouteDefinitionLocator {
  2. Flux getRouteDefinitions();
  3. }

从名称和方法可以看出RouteDefinitionLocator用于存放route信息,而真正可以执行route逻辑的则是Route,Route中包含filter链。

其扩展了以下几种:

DiscoveryClientRouteDefinitionLocator
CachingRouteDefinitionLocator
CompositeRouteDefinitionLocator
InMemoryRouteDefinitionRepository
PropertiesRouteDefinitionLocator
可以发现,这样做的好处是方便扩展功能,例如DiscoveryClientRouteDefinitionLocator可以实现从不同的注册中心上获取服务信息、CachingRouteDefinitionLocator则可以在本地JVM中用map缓存服务信息、或是用InMemoryRouteDefinitionRepository直接在内存中管理服务信息。

那么对于filter,它们均实现了前文所说的org.springframework.cloud.gateway.filter.GlobalFilter接口,Spring Cloud Gateway也提供了丰富的实现,如:

AdaptCachedBodyGlobalFilter
ForwardPathFilter
ForwardRoutingFilter
LoadBalancerClientFilter
NettyRoutingFilter
NettyWriteResponseFilter
RouteToRequestUrlFilter
WebClientHttpRoutingFilter
WebClientWriteResponseFilter
WebsocketRoutingFilter
可以看到,Spring Cloud Gateway对filter的支持更加丰富,包括NettyRoutingFilter&NettyWriteResponseFilter提供HttpClient代理请求的功能、WebClientHttpRoutingFilter&WebClientWriteResponseFilter提供WebClient代理请求的功能、LoadBalancerClientFilter提供负载均衡的支持(其内又有LoadBalancerClient的抽象,支持ribbon,也方便扩展)、ForwardPathFilter重建转发路径、ForwardRoutingFilter进行路由转发等。

另外,还有一些值得关注的功能有:

RateLimiter
GatewayControllerEndpoint
RouteRefreshListener
RateLimiter是一个接口,用户可以自行实现想要的限流策略及实现方式,Spring Cloud Gateway(2.0.0.RELESE)提供了RedisRateLimiter的实现,具体使用方式参考官网

GatewayControllerEndpoint提供了http控制RouteDefinition的endpoint,实现远程管理的效果

RouteRefreshListener是用于监听心跳事件、应用刷新事件、bean刷新、服务实例变化而发布route刷新事件,并及时刷新RouteBeanDefinition信息,与之类似的还有监听org.springframework.cloud.gateway.event.WeightDefinedEvent的org.springframework.cloud.gateway.filter.WeightCalculatorWebFilter,可及时更新权重

到这里就算过了一遍Spring Cloud Gateway的工作原理和设计模型,更加细节上的功能还有待使用者“开发”,整理一下它的特点:

底层依然是servlet,但使用了webflux,多嵌套了一层框架
理解filter、handler、locator就能灵活使用它,但其大量使用的流式编程容易让人懵逼
提供了非常丰富的filter实现和灵活的RoutePredicateFactory(route匹配规则)
依赖spring-boot-starter-webflux和spring-cloud-starter
提供了异步支持
提供函数式编程api,使用起来方便快捷
提供了抽象流控,并默认实现了RedisRateLimiter
提供了抽象负载均衡
支持HttpClient、WebClient代理请求
ps.槽点就是作为Spring家族,注释竟然这么少!

对比Spring Cloud Netflix Zuul和Spring Cloud Gateway

前面整理了两者的特点,现在对比来分析,得出以下结论:

两者均是web网关,处理的是http请求
gateway对比zuul多依赖了spring-webflux,在spring的支持下,功能更强大,内部实现了限流、负载均衡等,扩展性也更强,但同时也限制了仅适合于Spring Cloud套件,而zuul则可以扩展至其他微服务框架中,其内部没有实现限流、负载均衡等
gateway很好的支持异步,而zuul仅支持同步,那么理论上gateway则更适合于提高系统吞吐量(但不一定能有更好的性能),最终性能还需要通过严密的压测来决定
从框架设计的角度看,gateway具有更好的扩展性,并且其已经发布了2.0.0的RELESE版本,稳定性也是非常好的
编码上看,zuul更加简洁易懂,注释规范清晰,而gateway作为Spring家族的一份子,竟然几乎不注释…
总的来说,在微服务架构,如果使用了Spring Cloud生态的基础组件,则Spring Cloud Gateway相比而言更加具备优势,单从流式编程+支持异步上就足以让开发者选择它了。

而对于小型微服务架构或是复杂架构(不仅包括微服务应用还有其他非Spring Cloud服务节点),zuul也是一个不错的选择,当然,这种场景下一般会选择nginx,因为nginx从各个方面都会表现的更好…

Nginx在微服务中的地位
最后简单聊一下nginx,在过去几年微服务架构还没有流行的日子里,nginx已经得到了广大开发者的认可,其性能高、扩展性强、可以灵活利用lua脚本构建插件的特点让人没有抵抗力。

有一个能满足我所有需求还很方便我扩展的东西,还免费,凭啥不用??

但是,如今很多微服务架构的项目中不会选择nginx,我认为原因有以下几点:

微服务框架一般来说是配套的,集成起来更容易
如今微服务架构中,仅有很少的公司会面对无法解决的性能瓶颈,而他们也不会因此使用nginx,而是选择开发一套适合自己的微服务框架
spring boot对于一些模板引擎如FreeMarker、themleaf的支持是非常好的,很多应用还没有达到动、静态文件分离的地步,对nginx的需求程度并不大。
无论如何,nginx作为一个好用的组件,最终使不使用它都是由业务来驱动的,只要它能为我们方便的解决问题,那用它又有何不可呢?

小结
通过总结发现,在微服务架构中网关上的选择,最好的方式是使用现在比较成熟的Spring Cloud套件,其提供了Spring Cloud Gateway网关,或是结合公司情况来开发一套适合自己的微服务套件,至少从网关上可以看出来其内部实现并不难,同时也比较期待开源项目Nacos、Spring Cloud Alibaba 建设情况,期待它能构建一个高活跃社区的、稳定的、适合中国特色(大流量、高并发)的微服务基础架构。

文章知识点与官方知识档案匹配,可进一步学习相关知识
云原生入门技能树首页概览19383 人正在系统学习中
注:本文转载自blog.csdn.net的莫明的编织者的文章"https://blog.csdn.net/u010681191/article/details/99656413"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

101
推荐
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top