Zuul migrated to Spring Cloud Gateway stepping on the pit record

origin

Zuul1.x is no longer maintained, and the BIO used, when the traffic is large, the performance drops severely, and when the threads in the thread pool are exhausted, if a request returns a non-200 and you do not configure a processing filter, this The thread is suspended. The company's code scanning tool also indicates that many jars in Zuul1.0 are outdated. Although Zuul2.x modified BIO to NIO, the community is not active, it is not compatible with Spring, and its performance is not as good as expected. The Spring Cloud community has implemented its own Gateway, which is Spring Cloud Gateway. Here is a record of the pitfalls of migrating from Zuul1.x to Spring Cloud Gateway 3.x.

Step on the pit

坑点1. java.lang.NoClassDefFoundError: org/springframework/core/metrics/ApplicationStartup

  • Log output:
Exception in thread "main" java.lang.NoClassDefFoundError: org/springframework/core/metrics/ApplicationStartup
	at org.springframework.boot.SpringApplication.<init>(SpringApplication.java:232)
	at org.springframework.boot.SpringApplication.<init>(SpringApplication.java:245)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1317)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306)
	at com.example.gateway.GatewayApplication.main(GatewayApplication.java:12)
  • Reason: The version of netty itself is not uniform, and the versions of netty and reactor are not uniform, because I use the company's unified parent pom, which is not directly dependent spring-boot-parent. I tested it, it spring-boot-parentcan be started normally without this error
  • Solution: Introduce bom (please replace with your own version number):
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-framework-bom</artifactId>
    <version>${spring-framework.version}</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-bom</artifactId>
    <version>${netty.version}</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

坑点2 java.lang.ClassNotFoundException: javax.servlet.Filter

  • Log output:
Caused by: java.lang.NoClassDefFoundError: javax/servlet/Filter
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
	at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
	at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
	at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
	at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
	at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
	at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at java.lang.ClassLoader.defineClass1(Native Method)
	at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
	at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
	at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
	at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
	at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at java.lang.Class.getDeclaredMethods0(Native Method)
	at java.lang.Class.privateGetDeclaredMethods(Class.java:2701)
	at java.lang.Class.getDeclaredMethods(Class.java:1975)
	at org.springframework.util.ReflectionUtils.getDeclaredMethods(ReflectionUtils.java:467)
	... 21 common frames omitted
Caused by: java.lang.ClassNotFoundException: javax.servlet.Filter
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:335)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	... 61 common frames omitted
  • Reason: This should be Spring Cloud Gatewayused Spring Webfluxinstead Spring Web, resulting in javaxthe package not being imported
  • Solution: Introduce the following jar packages
<dependency>
	<groupId>javax.servlet</groupId>
	<artifactId>javax.servlet-api</artifactId>
	<version>${your-version}</version>
</dependency>

Pit point 3 server.servlet.contextPath does not take effect

  • Reason: Spring Cloud GatewayThe use is Spring Webflux, not Spring Web, so Spring Webthe configuration it can not parse
  • solution
    • Scenario 1. Add configuration:
    spring:
      webflux:
        base-dir: /xxx
    
    • Solution 2. Configure router forwarding
    spring:
      cloud:
        gateway:
          routes:
            - id: self
              uri: http://localhost:8080
              predicates:
                - Path=/xxx/**
              filters:
                 # StripPrefix=1:去除原始请求路径中的前1级路径,去除2级的话StripPrefix=2
                - StripPrefix=1
    
    • Solution 3. Configure the default filter:
     spring:
        cloud:
          gateway:
            default-filters:
              - StripPrefix=1
    

Pit point 4. spring.cloud.gateway.routes[0].uriInvalid with path

  • Problem description, the configuration is as follows (the current service is configured spring.webflux.base-dir=/xxx):
spring:
  cloud:
    gateway:
      routes:
        - id: routeUser
          uri: http://192.168.1.1:8081/dev/yyy
          predicates:
            - Path=/xxx/user/**
          filters:
            # StripPrefix:去除原始请求路径中的前1级路径
            - StripPrefix=1

When using the post request localhost:8080/xxx/user/getAllUser, I found that the forwarding is http://192.168.1.1:8081/getAllUsernot what I expected. http://192.168.1.1:8081/dev/yyy/getAllUser
This configuration looks abnormal. In fact, it is more common to use Ingress to communicate with each other in the k8s container. Most of the IPs in it are internal domain names.

  • Reason: The new url will be reassembled before forwarding. This assembly logic is in org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter#filter, and the code is as follows:
 Route route = (Route)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
        if (route == null) {
    
    
            return chain.filter(exchange);
        } else {
    
    
            log.trace("RouteToRequestUrlFilter start");
            URI uri = exchange.getRequest().getURI();
            boolean encoded = ServerWebExchangeUtils.containsEncodedParts(uri);
            URI routeUri = route.getUri();
            if (hasAnotherScheme(routeUri)) {
    
    
                exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR, routeUri.getScheme());
                routeUri = URI.create(routeUri.getSchemeSpecificPart());
            }

            if ("lb".equalsIgnoreCase(routeUri.getScheme()) && routeUri.getHost() == null) {
    
    
                throw new IllegalStateException("Invalid host: " + routeUri.toString());
            } else {
    
    
                URI mergedUrl = UriComponentsBuilder.fromUri(uri).scheme(routeUri.getScheme()).host(routeUri.getHost()).port(routeUri.getPort()).build(encoded).toUri();
                exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, mergedUrl);
                return chain.filter(exchange);
            }
        }

Let's single out this sentence URI mergedUrl = UriComponentsBuilder.fromUri(uri).scheme(routeUri.getScheme()).host(routeUri.getHost()).port(routeUri.getPort()).build(encoded).toUri();, in fact, it only helps you assemble your scheme (protocol, such as http), host (domain name or ip), port (port number), and does not have the path you configured

  • solution:
spring:
  cloud:
    gateway:
      routes:
        - id: routeUser
          uri: http://192.168.1.1:8081/dev/yyy
          predicates:
            - Path=/xxx/user/**
          filters:
            # StripPrefix:去除原始请求路径中的前1级路径
            - StripPrefix=1
            # 对于/xxx/user开头的url转发时拼装新的url的path前都添加一个/dev/yyy前缀
            # 也可以进行rewritePath,不过不如当前方案简洁
            - PrefixPath=/dev/yyy

Pit point 5 The requestPath obtained by the custom global filter is not the original requestPath

  • Description of the problem: My project has a login verification in the gateway, but some urls that can be accessed without logging in will be verified and whitelisted before the verification. I judge that I use these urls because of pit point exchange.getRequest().getURI().getPath()4 Configuration, when I request localhost:8080/xxx/user/loginthe path I get /dev/yyyy/login, and what I expect is to get/xxx/user/login
  • Reason: routeUserAfter the filter in is completed, the request path has been changed, and my global filter is after the route
  • solution:
LinkedHashSet<URI> uriLinkedHashSet  = (LinkedHashSet<URI>)exchange.getAttributes().get(ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
String requestUrlPath = uriLinkedHashSet.iterator().next().getPath();

exchange.getAttributes()ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTRThe original path is stored in it , just take it out directly.

Feign 报 block()/blockFirst()/blockLast() are blocking, which is not supported in thread reactor-http-nio-x

There is no problem with Feign's uri using domain name or url load balancing, but using service name load balancing will report the above error.
After checking the information, the reason is that there is no non-blocking client in loadbalancer. The higher version of loadbalancer removes the only ribbon component that supports asynchronous, so you have to configure a non-blocking client yourself.

import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.Request;
import org.springframework.cloud.client.loadbalancer.Response;
import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer;
import org.springframework.cloud.loadbalancer.blocking.client.BlockingLoadBalancerClient;
import reactor.core.publisher.Mono;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class CustomNonBlockingLoadBalancerClient extends BlockingLoadBalancerClient {
    
    

    private final ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory;

    public CustomNonBlockingLoadBalancerClient(ReactiveLoadBalancer.Factory<ServiceInstance> loadBalancerClientFactory) {
    
    
        super(loadBalancerClientFactory);
        this.loadBalancerClientFactory = loadBalancerClientFactory;
    }

    @Override
    public <T> ServiceInstance choose(String serviceId, Request<T> request) {
    
    
        ReactiveLoadBalancer<ServiceInstance> loadBalancer =
                loadBalancerClientFactory.getInstance(serviceId);
        if (loadBalancer == null) {
    
    
            return null;
        }
        CompletableFuture<Response<ServiceInstance>> f =
                CompletableFuture.supplyAsync(() -> {
    
    
                    Response<ServiceInstance> loadBalancerResponse =
                            Mono.from(loadBalancer.choose(request)).block();
                    return loadBalancerResponse;
                });
        Response<ServiceInstance> loadBalancerResponse = null;
        try {
    
    
            loadBalancerResponse = f.get();
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } catch (ExecutionException e) {
    
    
            e.printStackTrace();
        }
        if (loadBalancerResponse == null) {
    
    
            return null;
        }
        return loadBalancerResponse.getServer();
    }
}

Configure the above client to replace the default:

import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.loadbalancer.support.LoadBalancerClientFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;

@Configuration
public class LoadBalancerClientConfig {
    
    

    @Resource
    private LoadBalancerClientFactory loadBalancerClientFactory;

    @Bean
    public LoadBalancerClient blockingLoadBalancerClient() {
    
    
        return new CustomNonBlockingLoadBalancerClient(loadBalancerClientFactory);
    }

}

Guess you like

Origin blog.csdn.net/u013014691/article/details/128093298