Zuul1.x已经不维护了,并且使用的BIO,当流量较大时性能下降的厉害,并且线程池中的线程用尽时如果某个请求返回了非200并且你没有配置处理过滤器的话,这个线程就假死了。公司的代码扫描工具也提示Zuul1.0里面有很多的jar已经过时了。Zuul2.x虽然修改BIO为NIO,但社区不活跃,没有和Spring兼容,性能也没有预期的好。Spring Cloud社区实现了自己的Gateway就是Spring Cloud Gateway,这里记录一下从Zuul1.x迁移到Spring Cloud Gateway 3.x的坑点。
Exception in thread "main" java.lang.NoClassDefFoundError: org/springframework/core/metrics/ApplicationStartup
at org.springframework.boot.SpringApplication.(SpringApplication.java:232)
at org.springframework.boot.SpringApplication.(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)
spring-boot-parent
。 我测试过,使用spring-boot-parent
可以正常启动,无此错误<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-framework-bomartifactId>
<version>${spring-framework.version}version>
<type>pomtype>
<scope>importscope>
dependency>
<dependency>
<groupId>io.nettygroupId>
<artifactId>netty-bomartifactId>
<version>${netty.version}version>
<type>pomtype>
<scope>importscope>
dependency>
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
Spring Cloud Gateway
使用的是Spring Webflux
而非Spring Web
,导致javax
包没有引入<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>${your-version}version>
dependency>
Spring Cloud Gateway
使用的是Spring Webflux
,不是Spring Web
,所以Spring Web
的配置它无法解析spring:
webflux:
base-dir: /xxx
spring:
cloud:
gateway:
routes:
- id: self
uri: http://localhost:8080
predicates:
- Path=/xxx/**
filters:
# StripPrefix=1:去除原始请求路径中的前1级路径,去除2级的话StripPrefix=2
- StripPrefix=1
spring:
cloud:
gateway:
default-filters:
- StripPrefix=1
spring.cloud.gateway.routes[0].uri
带path无效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
当使用post请求localhost:8080/xxx/user/getAllUser
时,发现转发出去的是http://192.168.1.1:8081/getAllUser
,而不是我期望的http://192.168.1.1:8081/dev/yyy/getAllUser
这种配置看着比较反常,其实在k8s容器中使用Ingress互相通讯时比较常见,里面的ip大多都是某个内部域名。
org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter#filter
,其中代码如下: 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);
}
}
我们单挑这一句URI mergedUrl = UriComponentsBuilder.fromUri(uri).scheme(routeUri.getScheme()).host(routeUri.getHost()).port(routeUri.getPort()).build(encoded).toUri();
来看,其实它只帮你拼装了你的scheme(协议,如http),host(域名或者ip),port(端口号),并没有你配置的path
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
exchange.getRequest().getURI().getPath()
,因为坑点4的配置,当我请求localhost:8080/xxx/user/login
我拿到的path为/dev/yyyy/login
,而我期望的是拿到/xxx/user/login
routeUser
中的filter走完后,请求path就已经改完了,我的全局过滤器是在route之后的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_ATTR
存放了原始的path,直接取出即可。