GateWay 踩坑点

378 阅读2分钟

(持续更新中)

踩坑点 1

错误信息:

  Spring MVC found on classpath,which is incompatible with Spring Cloud Gatewat at this time.

  Please remove spring-boot-starter-web dependency

在这里能看见,明明确确的让我们移除 webMVC 的依赖。

添加了:spring-cloud-starter-gateway 就别添加: spring-boot-starter-web spring-boot-starter-actuator

因为 gateway 是 webflux 框架的,底层是 netty,基于响应式编程 而原生 web 是基于mvc来编程的 两个依赖都添加,springboot 不知道选择哪一个

踩坑点 2

GatewayConfig 中,uri 没加协议,会解析失败,报错 uri 记得要写全路径,有端口的记得加上端口号,协议也要带上

image.png

踩坑点 3、网关报 503 服务不可用 Service Unavailable

错误描述:使用 Nacos 作为注册中心,gateway 作为网关,网关通过 lb 的方式进行服务路由,在网关报 503 服务不可用Service Unavailable

现象版本 SpringCloud2021版 Nacos 1.4.3

深入问题

是因为 ReactiveLoadBalancerClientFilter 全局过滤器没有加载。官方对其的解释是:

The ReactiveLoadBalancerClientFilter looks for a URI in the exchange attribute named ServerWebExchangeUtils.

GATEWAY_REQUEST_URL_ATTR.

If the URL has a lb scheme (such as lb://myservice), it uses the Spring Cloud ReactorLoadBalancer to resolve the name (myservice in this example) to an actual host and port and replaces the URI in the same attribute.

The unmodified original URL is appended to the list in the ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR attribute.

The filter also looks in the ServerWebExchangeUtils. GATEWAY_SCHEME_PREFIX_ATTR attribute to see if it equals lb. If so, the same rules apply.

大概意思是 将请求过来的 URL 是 LB 方式的通过从服务注册中心获取到服务配置, 并将原来未修改的 URL 中的 IP 和端口解析为目标服务的。截取部分 ReactiveLoadBalancerClientFilter

过滤器代码:

URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
      String schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);
      if (url == null || (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {
         // 不是 lb 的方式直接出去
         return chain.filter(exchange);
      }
      // preserve the original url
      addOriginalRequestUrl(exchange, url);

      if (log.isTraceEnabled()) {
         log.trace(ReactiveLoadBalancerClientFilter.class.getSimpleName() + " url before: " + url);
      }

      URI requestUri = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
      String serviceId = requestUri.getHost();
      Set<LoadBalancerLifecycle> supportedLifecycleProcessors = LoadBalancerLifecycleValidator
            .getSupportedLifecycleProcessors(clientFactory.getInstances(serviceId, LoadBalancerLifecycle.class),
                  RequestDataContext.class, ResponseData.class, ServiceInstance.class);
      // 构造 lb 的请求
      DefaultRequest<RequestDataContext> lbRequest = new DefaultRequest<>(new RequestDataContext(
            new RequestData(exchange.getRequest()), getHint(serviceId, loadBalancerProperties.getHint())));

      // 获取服务中心配置并解析真正的地址
      return choose(lbRequest, serviceId, supportedLifecycleProcessors).doOnNext(response -> {

         if (!response.hasServer()) {
            supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
                  .onComplete(new CompletionContext<>(CompletionContext.Status.DISCARD, lbRequest, response)));
            throw NotFoundException.create(properties.isUse404(), "Unable to find instance for " + url.getHost());
         }

         ServiceInstance retrievedInstance = response.getServer();

         URI uri = exchange.getRequest().getURI();

         // if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,
         // if the loadbalancer doesn't provide one.
         String overrideScheme = retrievedInstance.isSecure() ? "https" : "http";
         if (schemePrefix != null) {
            overrideScheme = url.getScheme();
         }

         DelegatingServiceInstance serviceInstance = new DelegatingServiceInstance(retrievedInstance,
               overrideScheme);

         URI requestUrl = reconstructURI(serviceInstance, uri);

         if (log.isTraceEnabled()) {
            log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
         }
         exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
         exchange.getAttributes().put(GATEWAY_LOADBALANCER_RESPONSE_ATTR, response);
         supportedLifecycleProcessors.forEach(lifecycle -> lifecycle.onStartRequest(lbRequest, response));
      }).then(chain.filter(exchange))
            .doOnError(throwable -> supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
                  .onComplete(new CompletionContext<ResponseData, ServiceInstance, RequestDataContext>(
                        CompletionContext.Status.FAILED, throwable, lbRequest,
                        exchange.getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR)))))
            .doOnSuccess(aVoid -> supportedLifecycleProcessors.forEach(lifecycle -> lifecycle
                  .onComplete(new CompletionContext<ResponseData, ServiceInstance, RequestDataContext>(
                        CompletionContext.Status.SUCCESS, lbRequest,
                        exchange.getAttribute(GATEWAY_LOADBALANCER_RESPONSE_ATTR),
                        new ResponseData(exchange.getResponse(), new RequestData(exchange.getRequest()))))));

其次,取决于是否加载 ReactiveLoadBalancerClientFilter 的是类 GatewayReactiveLoadBalancerClientAutoConfiguration 中关键代码:

   @Bean
   @ConditionalOnBean(LoadBalancerClientFactory.class)
   @ConditionalOnMissingBean(ReactiveLoadBalancerClientFilter.class)
   @ConditionalOnEnabledGlobalFilter
   public ReactiveLoadBalancerClientFilter gatewayLoadBalancerClientFilter(LoadBalancerClientFactory clientFactory,
         GatewayLoadBalancerProperties properties, LoadBalancerProperties loadBalancerProperties) {
      return new ReactiveLoadBalancerClientFilter(clientFactory, properties, loadBalancerProperties);
   }

上述源代码中类 LoadBalancerClientFactory 是包 spring-cloud-loadbalancer 的,所以如果缺少则不会注入过滤器 ReactiveLoadBalancerClientFilter

解决方案:引入负载均衡依赖(LB 依赖)

前面 BB 了那么多,这里一句话搞定

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>