阅读完需:约 21 分钟
Spring 官方最终还是按捺不住推出了自己的网关组件:Spring Cloud Gateway ,相比之前我们使用的 Zuul(1.x) 它有哪些优势呢?Zuul(1.x) 基于 Servlet,使用阻塞 API,它不支持任何长连接,如 WebSockets,Spring Cloud Gateway 使用非阻塞 API,支持 WebSockets,支持限流等新特性。

Spring Cloud Gateway
这里Gateway的版本是3.0.4,cloud是2020版本的。
官网:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/
Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
Spring Cloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。
相关概念:
- Route(路由):这是网关的基本构建块。它由一个 ID,一个目标 URI,一组断言和一组过滤器定义。如果断言为真,则路由匹配。
- Predicate(断言):这是一个 Java 8 的 Predicate。输入类型是一个 ServerWebExchange。我们可以使用它来匹配来自 HTTP 请求的任何内容,例如 headers 或参数。
-
Filter(过滤器):这是
org.springframework.cloud.gateway.filter.GatewayFilter
的实例,我们可以使用它修改请求和响应。


Spring Cloud Gateway 的工作原理跟 Zuul 的差不多,最大的区别就是 Gateway 的 Filter 只有 pre 和 post 两种。
客户端向 Spring Cloud Gateway 发出请求。如果 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。 过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。
Spring Cloud Gateway 的特征:
- 基于 Spring Framework 5,Project Reactor 和 Spring Boot 2.0
- 动态路由
- Predicates 和 Filters 作用于特定路由
- 集成 Hystrix 断路器
- 集成 Spring Cloud DiscoveryClient
- 易于编写的 Predicates 和 Filters
- 限流
- 路径重写
基本用法
Spring Cloud Gateway 网关路由有两种配置方式:
- 在配置文件 yml 中配置
- 通过
@Bean
自定义 RouteLocator,在启动主类 Application 中配置
这两种方式是等价的,建议使用 yml 方式进配置。
引入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
Spring Cloud Gateway 是使用 netty+webflux 实现因此不需要再引入 web 模块。
代码配置
我们可以在启动类 GateWayApplication 中添加方法 routeLocator()
来定制转发规则。
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class GatewayConfig {
/**
* 自定义路由
*/
@Bean
RouteLocator routeLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("javaboy_route", r -> r.path("/get").uri("http://httpbin.org"))
.build();
}
}
上面配置了一个 id 为 path_route 的路由,当访问地址http://localhost:8080/
get 时会自动转发到地址:http://www.
httpbin.org/
get和上面的转发效果一样,只是这里转发的是以项目地址/
get 格式的请求地址。
properties 配置
spring.cloud.gateway.routes[0].id=javaboy_route
spring.cloud.gateway.routes[0].uri=http://httpbin.org
spring.cloud.gateway.routes[0].predicates[0]=Path=/get
因为routes可以是多个的所以 properties 中要以数组形式存在。
YML 配置
spring:
cloud:
gateway:
routes:
- id: javaboy_route # 路由唯一标识 不允许重复
uri: http://httpbin.org # uri可以是http 也可以是 eureka的服务地址
predicates:
- Path=/get # 路由的前缀 这里一般只配置一个
filters: # 过滤器 内置了不少通用的过滤器 如以下的重写path的过滤器
- RewritePath=/business(?<segment>.*), /api$\{segment}
- id:我们自定义的路由 ID,保持唯一
- uri:目标服务地址
- predicates:路由条件,Predicate 接受一个输入参数,返回一个布尔值结果。该接口包含多种默认方法来将 Predicate 组合成其他复杂的逻辑(比如:与,或,非)。
- filters:过滤规则,本示例暂时没用。
上面这段配置的意思是,配置了一个 id 为 javaboy_route 的路由规则,当访问地址 http://localhost:8080/get
时会自动转发到地址:http://www.ityouknow.com/get
。配置完成启动项目即可在浏览器访问进行测试,当我们访问地址http://localhost:8080/get
时会展示页面。
完整案例
server:
port: 9527
spring:
application:
name: cloud-gateway
#############################新增网关配置###########################
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
- id: payment_routh2 #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
####################################################################
eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
通过以上两种方式,都可以使路由生效。
但是在实际生产实践中,我们一般很少通过操作api来配置路由。
我们一般是通过配置的方式来配置路由,好处是更加灵活,调整路由不需要改动代码,修改配置即可。
但以上结论也并非绝对,针对一些大公司,路由可能非常复杂,并且路由规则是在外部存储中保存的,便于做后台管理界面去管理这些路由,针对这种情况,还是会通过api的方式来配置路由。
跨域支持
互联网公司的系统基本上都是前后端分离的,如果不支持跨域,则当前端域名和后端暴露接口域名不完全一致时,前端就无法正常请求接口,这个时候,就需要后端支持跨域,而对跨域的支持,正常情况下都是在网关层面做支持,故在spring cloud gateway中支持跨域是很常见的场景。
方式一(推荐,尽可能把网关升级到2.2.x以上版本,低版本很多不完善到地方)
针对SpringCloudGateway2.2.x以上版本 可以直接使用自带的跨域配置即可如下(直接在application.yaml添加配置)
spring:
cloud:
gateway:
filter:
remove-hop-by-hop:
headers:
# 以下是去掉网关默认去掉的请求响应头
- trailer
- te
- keep-alive
- transfer-encoding
- upgrade
- proxy-authenticate
- connection
- proxy-authorization
- x-application-context
# 以下是去掉服务层面定义的跨域
- access-control-allow-credentials
- access-control-allow-headers
- access-control-allow-methods
- access-control-allow-origin
- access-control-max-age
- vary
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowCredentials: true # 是否允许携带cookie
allowedOrigins: "*" # 允许哪些网站的跨域请求
allowedHeaders: "*" # 允许在请求中携带的头信息
allowedMethods: "*" # 允许的跨域ajax的请求方式 如GET,POST
maxAge: 3628800 # 这次跨域检测的有效期
在springboot版本大于2.4时会爆出一下错误
When allowCredentials is true, allowedOrigins cannot contain the
special value "*“since that cannot be set on the “Access-Control-Allow-Origin”
response header. To allow credentials to a set of origins, list them explicitly or
consider using"allowedOriginPatterns” instead.
翻译过来就是
当allowCredentials为true时,allowedOrigins不能包含特殊值“*”,因为不能在“Access Control Allow Origin”响应头上设置该值。要允许凭据指向一组源,请显式列出它们,或者考虑改用“allowedOriginPatterns”。
解决办法:跨域配置报错,将.allowedOrigins
替换成.allowedOriginPatterns
即可。
gateway:
globalcors:
cors-configurations:
'[/**]':
allowCredentials: true
allowedOriginPatterns: "*"
allowedHeaders: "*"
allowedMethods: "*"
maxAge: 3628800
add-to-simple-url-handler-mapping: true
方式二(针对SpringCloudGateway2.1.x及以下版本)
@Bean
public WebFilter corsFilter() {
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
if (!CorsUtils.isCorsRequest(request)) {
return chain.filter(exchange);
}
ServerHttpResponse response = exchange.getResponse();
HttpHeaders headers = response.getHeaders();
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "POST,GET,OPTIONS,DELETE,PUT");
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "content-type");
headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "3628800");
return chain.filter(exchange);
};
}
方式三(针对SpringCloudGateway2.1.x及以下版本)
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;
/**
* 跨域配置
*
* @author jacky
* @since 2019/08/14
*/
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsFilter(){
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", buildConfig());
return new CorsWebFilter(source);
}
private CorsConfiguration buildConfig(){
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("*");
corsConfiguration.addAllowedHeader("content-type");
corsConfiguration.addAllowedMethod("POST,GET,OPTIONS,DELETE,PUT");
corsConfiguration.setMaxAge(3628800L);
return corsConfiguration;
}
}
通过以上方式配置之后可能还会存在某些浏览器跨域存在问题,比如搜狗浏览器可能就会报跨域问题,导致请求异常,这个时候需要增加多一个过滤器即可解决,如下:
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Arrays;
/**
* 跨域的 响应头处理 源码中有bug 需要通过该Filter处理
*
* @author jacky
* @since 2019/08/15
*/
@Component
public class CorsResponseHeaderFilter implements GlobalFilter, Ordered {
@Override
public int getOrder() {
// 指定此过滤器位于NettyWriteResponseFilter之后
// 即待处理完响应体后接着处理响应头
return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER + 1;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
return chain.filter(exchange).then(Mono.defer(() -> {
exchange.getResponse().getHeaders().entrySet().stream()
.filter(kv -> kv.getValue() != null && kv.getValue().size() > 1)
.filter(kv -> kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN)
|| kv.getKey().equals(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS))
.forEach(kv -> kv.setValue(Arrays.asList(kv.getValue().get(0))));
return chain.filter(exchange);
}));
}
}
值得注意的是,在servlet
下跨域配置类是CorsFilter
,位于package org.springframework.web.filter;
包下。

在webFlux
响应式下跨域配置类变成了CorsWebFilter
,位于package org.springframework.web.cors.reactive;
包下。

超时时间配置
路由超时配置
可以为所有路由配置Http超时(响应和连接),并为每个特定路由覆盖Http超时。
全局路由超时时间配置
要配置全局http超时,需要配置以下两个参数:
connect-timeout
必须以毫秒为单位指定连接超时时间.response-timeout
必须指定为java.time.Duration
示例如下:
spring:
cloud:
gateway:
httpclient:
connect-timeout: 1000
response-timeout: 5s
每个路由的超时时间配置
可以通过路由的metadata以下两个参数配置每个路由超时:connect-timeout
必须以毫秒为单位指定连接超时时间.response-timeout
必须以毫秒为单位指定响应超时时间.
示例如下:
- id: per_route_timeouts
uri: https://example.org
predicates:
- name: Path
args:
pattern: /delay/{timeout}
metadata:
response-timeout: 200
connect-timeout: 200
配置说明
配置说明请参考官网
配置名称 | 默认值 | 描述说明 |
---|---|---|
spring.cloud.gateway.default-filters | 应用于每个路由的过滤器定义列表。 | |
spring.cloud.gateway.discovery.locator.enabled | false | 启用DiscoveryClient网关集成的标志。如果启用,则会默认把eureka的服务通过网关暴露出去,所以通常情况下我们都不会启用,而是根据需要去配置路由。 |
spring.cloud.gateway.discovery.locator.filters | ||
spring.cloud.gateway.discovery.locator.include-expression | true | 将评估是否在网关集成中包括服务的SpEL表达式,默认为:true。 |
spring.cloud.gateway.discovery.locator.lower-case-service-id | false | 谓词和过滤器中的小写serviceId选项,默认为false。当eureka自动将serviceId大写时,对eureka很有用。因此MYSERIVCE将与/ myservice/**匹配 |
spring.cloud.gateway.discovery.locator.predicates | ||
spring.cloud.gateway.discovery.locator.route-id-prefix | routeId的前缀,默认为DiscoveryClient.getClass()。getSimpleName()+“ _”。服务ID将被添加以创建routeId。 | |
spring.cloud.gateway.discovery.locator.url-expression | ‘lb://‘+serviceId | 为每个路由创建uri的SpEL表达式,默认为:’lb://‘+serviceId。 |
spring.cloud.gateway.enabled | true | 启用网关功能。 |
spring.cloud.gateway.fail-on-route-definition-error | true | 在路由定义错误时失败的选项,默认为true。否则,将记录警告。 |
spring.cloud.gateway.filter.remove-hop-by-hop.headers | ||
spring.cloud.gateway.filter.remove-hop-by-hop.order | ||
spring.cloud.gateway.filter.request-rate-limiter.deny-empty-key | true | 如果密钥解析器返回空密钥,则切换为拒绝请求,默认为true。 |
spring.cloud.gateway.filter.request-rate-limiter.empty-key-status-code | denyEmptyKey为true时返回的HttpStatus,默认为FORBIDDEN。 | |
spring.cloud.gateway.filter.secure-headers.content-security-policy | default-src ‘self’ https:; font-src ‘self’ https: data:; img-src ‘self’ https: data:; object-src ‘none’; script-src https:; style-src ‘self’ https: ‘unsafe-inline’ | |
spring.cloud.gateway.filter.secure-headers.content-type-options | nosniff | |
spring.cloud.gateway.filter.secure-headers.disable | ||
spring.cloud.gateway.filter.secure-headers.download-options | noopen | |
spring.cloud.gateway.filter.secure-headers.frame-options | DENY | |
spring.cloud.gateway.filter.secure-headers.permitted-cross-domain-policies | none | |
spring.cloud.gateway.filter.secure-headers.referrer-policy | no-referrer | |
spring.cloud.gateway.filter.secure-headers.strict-transport-security | max-age=631138519 | |
spring.cloud.gateway.filter.secure-headers.xss-protection-header | 1 ; mode=block | |
spring.cloud.gateway.forwarded.enabled | true | 启用ForwardedHeadersFilter。 |
spring.cloud.gateway.globalcors.add-to-simple-url-handler-mapping | false | 是否将全局CORS配置添加到URL处理程序。 |
spring.cloud.gateway.globalcors.cors-configurations | ||
spring.cloud.gateway.httpclient.connect-timeout | 连接超时(毫秒),默认值为45秒。 | |
spring.cloud.gateway.httpclient.max-header-size | 最大响应头大小。 | |
spring.cloud.gateway.httpclient.max-initial-line-length | 最大初始行长度。 | |
spring.cloud.gateway.httpclient.pool.acquire-timeout | 仅对于FIXED类型,等待获取的最长时间(毫秒)。 | |
spring.cloud.gateway.httpclient.pool.max-connections | 仅适用于FIXED类型,即在现有连接上启动挂起获取之前的最大连接数。 | |
spring.cloud.gateway.httpclient.pool.max-idle-time | 通道关闭后的时间(毫秒)。如果为空,则没有最长空闲时间。 | |
spring.cloud.gateway.httpclient.pool.max-life-time | 关闭通道的持续时间。如果为空,则没有最长生存时间。 | |
spring.cloud.gateway.httpclient.pool.name | proxy | 通道池映射名称默认为代理。 |
spring.cloud.gateway.httpclient.pool.type | HttpClient要使用的池类型,默认为ELASTIC。 | |
spring.cloud.gateway.httpclient.proxy.host | Netty HttpClient代理配置的主机名。 | |
spring.cloud.gateway.httpclient.proxy.non-proxy-hosts-pattern | 已配置主机列表的正则表达式(Java)。应该绕过代理直接到达 | |
spring.cloud.gateway.httpclient.proxy.password | Netty HttpClient代理配置的密码。 | |
spring.cloud.gateway.httpclient.proxy.port | Netty HttpClient的代理配置端口。 | |
spring.cloud.gateway.httpclient.proxy.username | Netty HttpClient代理配置的用户名。 | |
spring.cloud.gateway.httpclient.response-timeout | 响应超时时间 | |
spring.cloud.gateway.httpclient.ssl.close-notify-flush-timeout | 3000ms | SSL close_notify 刷新超时时间。默认为3000毫秒。 |
spring.cloud.gateway.httpclient.ssl.close-notify-flush-timeout-millis | ||
spring.cloud.gateway.httpclient.ssl.close-notify-read-timeout | SSL关闭通知读取超时。默认为0毫秒。 | |
spring.cloud.gateway.httpclient.ssl.close-notify-read-timeout-millis | ||
spring.cloud.gateway.httpclient.ssl.default-configuration-type | 默认ssl配置类型。默认为TCP。 | |
spring.cloud.gateway.httpclient.ssl.handshake-timeout | 10000ms | SSL握手超时。默认为10000毫秒 |
spring.cloud.gateway.httpclient.ssl.handshake-timeout-millis | ||
spring.cloud.gateway.httpclient.ssl.key-password | 密钥密码,默认值与keyStorePassword相同。 | |
spring.cloud.gateway.httpclient.ssl.key-store | Netty HttpClient的密钥库路径。 | |
spring.cloud.gateway.httpclient.ssl.key-store-password | 密钥库密码。 | |
spring.cloud.gateway.httpclient.ssl.key-store-provider | Netty HttpClient的密钥库提供程序,可选字段。 | |
spring.cloud.gateway.httpclient.ssl.key-store-type | JKS | Netty HttpClient的密钥库类型,默认为JKS。 |
spring.cloud.gateway.httpclient.ssl.trusted-x509-certificates | 用于验证远程终结点的证书的受信任证书。 | |
spring.cloud.gateway.httpclient.ssl.use-insecure-trust-manager | false | 安装netty UnsecureTrustManagerFactory。这是不安全的,不适合生产。 |
spring.cloud.gateway.httpclient.websocket.max-frame-payload-length | 最大帧有效载荷长度。 | |
spring.cloud.gateway.httpclient.websocket.proxy-ping | true | 代理ping帧到下游服务,默认为true。 |
spring.cloud.gateway.httpclient.wiretap | false | 启用Netty HttpClient的窃听调试。 |
spring.cloud.gateway.httpserver.wiretap | false | 启用Netty HttpServer的窃听调试。 |
spring.cloud.gateway.loadbalancer.use404 | false | |
spring.cloud.gateway.metrics.enabled | true | 启用度量数据的收集。 |
spring.cloud.gateway.metrics.tags | 添加到度量的标记映射. | |
spring.cloud.gateway.redis-rate-limiter.burst-capacity-header | X-RateLimit-Burst-Capacity | 返回突发容量配置的标头的名称。 |
spring.cloud.gateway.redis-rate-limiter.config | ||
spring.cloud.gateway.redis-rate-limiter.include-headers | true | 无论是否包含包含速率限制器信息的头,默认值为true。 |
spring.cloud.gateway.redis-rate-limiter.remaining-header | X-RateLimit-Remaining | 返回当前秒期间剩余请求数的标头的名称。 |
spring.cloud.gateway.redis-rate-limiter.replenish-rate-header | X-RateLimit-Replenish-Rate | 返回补充率配置的标头的名称。 |
spring.cloud.gateway.redis-rate-limiter.requested-tokens-header | X-RateLimit-Requested-Tokens | 返回请求的令牌配置的标头的名称。 |
spring.cloud.gateway.routes | 路由列表 | |
spring.cloud.gateway.set-status.original-status-header-name | 包含代理请求的http代码的header的名称。 | |
spring.cloud.gateway.streaming-media-types | ||
spring.cloud.gateway.x-forwarded.enabled | true | 如果XForwardedHeadersFilter已启用。 |
spring.cloud.gateway.x-forwarded.for-append | true | 如果启用了附加X-Forwarded-For作为列表。 |
spring.cloud.gateway.x-forwarded.for-enabled | true | 如果启用了X-Forwarded-For。 |
spring.cloud.gateway.x-forwarded.host-append | true | 如果启用了附加X-Forwarded-Host作为列表。 |
spring.cloud.gateway.x-forwarded.host-enabled | true | 如果启用了X-Forwarded-Host。 |
spring.cloud.gateway.x-forwarded.order | 0 | XForwardedHeadersFilter的顺序。 |
spring.cloud.gateway.x-forwarded.port-append | true | 如果启用了附加X-Forwarded-Port作为列表。 |
spring.cloud.gateway.x-forwarded.port-enabled | true | 如果启用了X-Forwarded-Port。 |
spring.cloud.gateway.x-forwarded.prefix-append | true | 如果启用了附加X-Forwarded-Prefix作为列表。 |
spring.cloud.gateway.x-forwarded.prefix-enabled | true | 如果启用了X-Forwarded-Prefix。 |
spring.cloud.gateway.x-forwarded.proto-append | true | 如果启用X-PRODIED-PROTO作为一个列表。 |
spring.cloud.gateway.x-forwarded.proto-enabled | true | 如果启用了X-Forwarded-Proto。 |
Netty线程池优化
要设置起本身可同时工作的线程数需要设置netty中的reactor.netty.ioWorkerCount
参数。该参数无法直接配置,需要通过System.setProperty
设置,故我们可以创建以下配置类来配置该参数:
@Configuration
public static class ReactNettyConfiguration {
@Bean
public ReactorResourceFactory reactorClientResourceFactory() {
System.setProperty("reactor.netty.ioSelectCount","1");
// 这里工作线程数为2-4倍都可以。看具体情况
int ioWorkerCount = Math.max(Runtime.getRuntime().availableProcessors()*3, 4));
System.setProperty("reactor.netty.ioWorkerCount",String.valueOf(ioWorkerCount);
return new ReactorResourceFactory();
}
}
这里版本是reactor-netty-core-1.0.3
,版本不一样的话 可能参数key不太一样。可以看一下LoopResources 中写的key。
Runtime.getRuntime().availableProcessors()
获取的是cpu核心线程数也就是计算资源,而不是CPU物理核心数,对于支持超线程的CPU来说,单个物理处理器相当于拥有两个逻辑处理器,能够同时执行两个线程。
源码分析
package reactor.netty.resources;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import java.time.Duration;
import java.util.Objects;
import reactor.core.Disposable;
import reactor.core.publisher.Mono;
@FunctionalInterface
public interface LoopResources extends Disposable {
// 这里是worker线程数,未配置的话。从cpu核心数和4直接取一个大的
int DEFAULT_IO_WORKER_COUNT = Integer.parseInt(System.getProperty("reactor.netty.ioWorkerCount", "" + Math.max(Runtime.getRuntime().availableProcessors(), 4)));
// 这里是select线程数 默认是-1
int DEFAULT_IO_SELECT_COUNT = Integer.parseInt(System.getProperty("reactor.netty.ioSelectCount", "-1"));
....
// 创建一个默认的资源,把两个线程数的参数传递过去
static LoopResources create(String prefix) {
if (Objects.requireNonNull(prefix, "prefix").isEmpty()) {
throw new IllegalArgumentException("Cannot use empty prefix");
}
return new DefaultLoopResources(prefix, DEFAULT_IO_SELECT_COUNT, DEFAULT_IO_WORKER_COUNT, true);
}
....
接下来看一下 DefaultLoopResources
做了什么
DefaultLoopResources(String prefix, int selectCount, int workerCount, boolean daemon) {
this.running = new AtomicBoolean(true);
this.daemon = daemon;
this.workerCount = workerCount;
this.prefix = prefix;
this.serverLoops = new AtomicReference<>();
this.clientLoops = new AtomicReference<>();
this.cacheNativeClientLoops = new AtomicReference<>();
this.cacheNativeServerLoops = new AtomicReference<>();
// 因为默认没有配置 所以selectCode必然是-1
if (selectCount == -1) {
this.selectCount = workerCount;
// serverSelectLoops没有创建,而是直接使用的serverLoops
this.serverSelectLoops = this.serverLoops;
this.cacheNativeSelectLoops = this.cacheNativeServerLoops;
}
else {
this.selectCount = selectCount;
this.serverSelectLoops = new AtomicReference<>();
this.cacheNativeSelectLoops = new AtomicReference<>();
}
}
@SuppressWarnings("FutureReturnValueIgnored")
EventLoopGroup cacheNioSelectLoops() {
// 两个相等的话 使用 cacheNioServerLoops 返回工作组
if (serverSelectLoops == serverLoops) {
return cacheNioServerLoops();
}
EventLoopGroup eventLoopGroup = serverSelectLoops.get();
if (null == eventLoopGroup) {
EventLoopGroup newEventLoopGroup = new NioEventLoopGroup(selectCount,
threadFactory(this, "select-nio"));
if (!serverSelectLoops.compareAndSet(null, newEventLoopGroup)) {
//"FutureReturnValueIgnored" this is deliberate
newEventLoopGroup.shutdownGracefully(0, 0, TimeUnit.MILLISECONDS);
}
eventLoopGroup = cacheNioSelectLoops();
}
return eventLoopGroup;
}
// 这里相当于返回了工作组
@SuppressWarnings("FutureReturnValueIgnored")
EventLoopGroup cacheNioServerLoops() {
EventLoopGroup eventLoopGroup = serverLoops.get();
if (null == eventLoopGroup) {
EventLoopGroup newEventLoopGroup = new NioEventLoopGroup(workerCount,
threadFactory(this, "nio"));
if (!serverLoops.compareAndSet(null, newEventLoopGroup)) {
//"FutureReturnValueIgnored" this is deliberate
newEventLoopGroup.shutdownGracefully(0, 0, TimeUnit.MILLISECONDS);
}
eventLoopGroup = cacheNioServerLoops();
}
return eventLoopGroup;
}
可以看出来,如果未配置。netty是没有select线程组的。结合分析reactor模型可以发现,这种情况对处理效率是有影响的。而且最大只和cpu核心数量相同的配置也明显无法重复利硬件用资源。
接口耗时日志
LoginPostFilter
/**
* 打印请求参数及统计执行时长过滤器
* @author xujiahui
*/
@Component
public class LoggingFilter implements GlobalFilter, Ordered {
private static final Log logger = LogFactory.getLog(LoggingFilter.class);
private static final String START_TIME = "startTime";
public LoggingFilter() {
logger.info("Loaded GlobalFilter [Logging]");
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String info = String.format("Method:{%s} Host:{%s} Path:{%s} Query:{%s}",
exchange.getRequest().getMethod().name(),
exchange.getRequest().getURI().getHost(),
exchange.getRequest().getURI().getPath(),
exchange.getRequest().getQueryParams());
logger.info(info);
exchange.getAttributes().put(START_TIME, System.currentTimeMillis());
return chain.filter(exchange).then( Mono.fromRunnable(() -> {
Long startTime = exchange.getAttribute(START_TIME);
if (startTime != null) {
Long executeTime = (System.currentTimeMillis() - startTime);
logger.info(exchange.getRequest().getURI().getRawPath() + " : " + executeTime + "ms");
}
}));
}
@Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE;
}
}
结果
2018-08-31 18:35:02.644 INFO 14376 --- [ctor-http-nio-2] c.w.gateway.filter.LoggingFilter : Method:{POST} Host:{127.0.0.1} Path:{/api/authorize} Query:{{}}
2018-08-31 18:35:02.678 INFO 14376 --- [ctor-http-nio-3] c.w.gateway.filter.LoggingFilter : /api/authorize : 34ms