关于云原生:Spring-Cloud-Gateway一次请求调用源码解析

61次阅读

共计 5263 个字符,预计需要花费 14 分钟才能阅读完成。

简介:最近通过深刻学习 Spring Cloud Gateway 发现这个框架的架构设计非常简单、无效,很多组件的设计都十分值得学习,本文就 Spring Cloud Gateway 做一个简略的介绍,以及针对一次申请 Spring Cloud Gateway 的解决流程做一个较为具体的剖析。

作者 | 寻筝
起源 | 阿里技术公众号

一 前言

最近通过深刻学习 Spring Cloud Gateway 发现这个框架的架构设计非常简单、无效,很多组件的设计都十分值得学习,本文就 Spring Cloud Gateway 做一个简略的介绍,以及针对一次申请 Spring Cloud Gateway 的解决流程做一个较为具体的剖析。

二 简介

Spring Cloud Gateway 即 Spring 官网推出的一款 API 网关,该框架蕴含了 Spring5、SpringBoot2、Project Reactor,其中底层通信框架用的 netty。Spring Cloud Gateway 在推出之初的时候,Netflix 公司曾经推出了相似性能的 API 网关框架 ZUUL,但 ZUUL 有一个毛病是通信形式是阻塞的,尽管起初降级到了非阻塞式的 ZUUL2,然而因为 Spring Cloud Gateway 曾经推出一段时间,同时本身也面临材料少、维护性较差的因素没有被广泛应用。

1 要害术语

在应用 Spring Cloud Gateway 的时候须要了解三个模块,即

Route:

即一套路由规定,是集 URI、predicate、filter 等属性的一个元数据类。

Predicate:

这是 Java8 函数式编程的一个办法,这里能够看做是满足什么条件的时候,route 规定进行失效。

Filter:

filter 能够认为是 Spring Cloud Gateway 最外围的模块,熔断、平安、逻辑执行、网络调用都是 filter 来实现的,其中又细分为 gateway filter 和 global filter,区别在于是具体一个 route 规定失效还是所有 route 规定都失效。

能够先上一段代码来看看:

 @RequestMapping("/paramTest")
  public Object paramTest(@RequestParam Map<String,Object> param) {return param.get("name");
  }

  @Bean
  public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {return builder.routes()
      .route("path_route", r ->
                   r.path("/get")
                  .filters(f -> f.addRequestParameter("name", "value"))
                  .uri("forward:///paramTest"))
      .build();}
  • route 办法代表的就是一个路由规定;
  • path 办法代表的就是一个 predicate,背地的事实是 PathRoutePredicateFactory,在这段代码的含意即当门路蕴含 /get 的时候,以后规定失效。
  • filters 办法的意思即给以后路由规定增加一个减少申请参数的 filter,每次申请都对参数里增加 name:value 的键值对;
  • uri 办法的含意即最终路由到哪里去,这里的 forward 前缀会将申请交给 spring mvc 的 DispatcherHandler 进行路由,进行本机的逻辑调用,除了 forward 以外还能够应用 http、https 前缀进行 http 调用,lb 前缀能够在配置注册核心后进行 rpc 调用。

上图是 Spring Cloud Gateway 官网文档给出的一个工作原理图,Spring Cloud Gateway 接管到申请后进行路由规定的匹配,而后交给 web handler 进行解决,web handler 会执行一系列的 filter 逻辑。

三 流程剖析

1 承受申请

Spring Cloud Gateway 的底层框架是 netty,承受申请的要害类是 ReactorHttpHandlerAdapter,做的事件很简略,就是将 netty 的申请、响应转为 http 的申请、响应并交给一个 http handler 执行前面的逻辑,下图为该类的源码仅保留外围逻辑。

@Override
public Mono< Void> apply(HttpServerRequest request, HttpServerResponse response) {NettyDataBufferFactory bufferFactory = new NettyDataBufferFactory(response.alloc());
    ServerHttpRequest adaptedRequest;
    ServerHttpResponse adaptedResponse;
// 转换申请
    try {adaptedRequest = new ReactorServerHttpRequest(request, bufferFactory);
        adaptedResponse = new ReactorServerHttpResponse(response, bufferFactory);
    }
    catch (URISyntaxException ex) {if (logger.isWarnEnabled()) {...}
    ...
    return this.httpHandler.handle(adaptedRequest, adaptedResponse)
            .doOnError(ex -> logger.warn("Handling completed with error:" + ex.getMessage()))
            .doOnSuccess(aVoid -> logger.debug("Handling completed with success"));
}

2 WEB 过滤器链

http handler 做的事件第一是将 request 和 response 转为一个 exchange,这个 exchange 十分外围,是各个 filter 之间参数流转的载体,该类蕴含 request、response、attributes(扩大字段),接着做的事件就是 web filter 链的执行,其中的逻辑次要是监控。

其中 WebfilterChainParoxy 又会引出新的一条 filter 链,次要是平安、日志、认证相干的逻辑,由此可见 Spring Cloud Gateway 的过滤器设计是层层嵌套,扩展性很强。

3 寻找路由规定

外围类是 RoutePredicateHandlerMapping,逻辑也非常简单,就是把所有的 route 规定的 predicate 遍历一遍看哪个 predicate 可能命中,外围代码是:

return this.routeLocator.getRoutes()
      .filter(route -> {
         ...
         return route.getPredicate().test(exchange);
      })

因为我这里用的是 path 进行过滤,所以背地的逻辑是 PathRoutePredicateFactory 来实现的,除了 PathRoutePredicateFactory 还有很多 predicate 规定。

这些路由规定都能从官网文档上找到影子。

4 外围过滤器链执行

找到路由规定后下一步就是执行了,这里的外围类是 FilteringWebHandler,其中的源码为:

做的事件很简略:

  • 获取 route 级别的过滤器
  • 获取全局过滤器
  • 两种过滤器放在一起并依据 order 进行排序
  • 执行过滤器链

因为我的配置里蕴含了一个增加申请参数的逻辑,所以红线箭头处就是我配置的 gateway filter 名为 AddRequestParameterGatewayFilterFactory,其余全是 Gloabl Filter,这些过滤器的性能次要是 url 解析,申请转发,响应回写等逻辑,因为咱们这里用的是 forward schema,所以申请转发会由 ForwardRoutingFilter 进行执行。

5 申请转发

ForwardRoutingFilter 做的事件也很简略,间接复用了 spring mvc 的能力,将申请提交给 dispatcherHandler 进行解决,dispatcherHandler 会依据 path 前缀找到须要指标处理器执行逻辑。

@Override
public Mono< Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);

   String scheme = requestUrl.getScheme();
   if (isAlreadyRouted(exchange) || !"forward".equals(scheme)) {return chain.filter(exchange);
   }
   setAlreadyRouted(exchange);

   //TODO: translate url?

   if (log.isTraceEnabled()) {log.trace("Forwarding to URI:"+requestUrl);
   }

   return this.dispatcherHandler.handle(exchange);
}

6 响应回写

响应回写的外围类是 NettyWriteResponseFilter,然而大家能够留神到执行器链中 NettyWriteResponseFilter 的排序是在最后面的,按情理这种响应解决的类应该是在靠后才对,这里的设计比拟奇妙。大家能够看到 chain.filter(exchange).then(),意思就是执行到我的时候间接跳过下一个,等前面的过滤器都执行完后才执行这段逻辑,这种行为管制的办法值得学习。

@Override
public Mono< Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
   // NOTICE: nothing in "pre" filter stage as CLIENT_RESPONSE_ATTR is not added
   // until the WebHandler is run
   return chain.filter(exchange).then(Mono.defer(() -> {HttpClientResponse clientResponse = exchange.getAttribute(CLIENT_RESPONSE_ATTR);

      if (clientResponse == null) {return Mono.empty();
      }
      log.trace("NettyWriteResponseFilter start");
      ServerHttpResponse response = exchange.getResponse();

      NettyDataBufferFactory factory = (NettyDataBufferFactory) response.bufferFactory();
      //TODO: what if it's not netty

      final Flux< NettyDataBuffer> body = clientResponse.receive()
            .retain() //TODO: needed?
            .map(factory::wrap);

      MediaType contentType = response.getHeaders().getContentType();
      return (isStreamingMediaType(contentType) ?
            response.writeAndFlushWith(body.map(Flux::just)) : response.writeWith(body));
   }));
}

四 总结

整体读完 Spring Cloud Gateway 申请流程代码后,有几点感触:

  • 过滤器是 Spring Cloud Gateway 最外围的设计,甚至于能够夸大说 Spring Cloud Gateway 是一个过滤器链执行框架而不是一个 API 网关,因为 API 网关理论的申请转发、申请响应回写都是在过滤器中做的,这些是 Spring Cloud Gateway 感知不到的逻辑。
  • Spring Cloud Gateway 路由规定获取的模块具备优化的空间,因为是循环遍历进行获取的,如果每个 route 规定较多,predicate 规定较简单,就能够思考用 map 进行优化了,当日 route 规定,predicate 规定也不会很简单,兼顾到代码的可读性,以后形式也没有什么问题。
  • 作为 API 网关框架,内置了十分多的过滤器,如果有过滤器的卸载性能可能会更好,用户可用依据理论状况卸载不必要的性能,背地缩小的逻辑开销,在调用量极大的 API 网关场景,收益也会很可观。

原文链接
本文为阿里云原创内容,未经容许不得转载。

正文完
 0