zuul终究还是被时代淘汰了,spring基本上也逐渐放弃了Netflix平台,自己搞起了gateway。这不得不聊起一个人 Spencer Gibb,他是zuul的核心开发,带领一群有志青年,放弃zuul,转战spring cloud gateway。
应用搭建
其maven依赖如下:
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
之后,只需要写个简单的spring boot程序就可以了,与zuul不同的是,gateway没有@Enable开头的开关:
java
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
}
这就是gateway与zuul设计哲学的不同。
网关配置
光完成上述代码肯定不够,因为没有配置转发。以下是配置文件的一个例子:
yml
server:
port: 80 # 网关端口
spring:
cloud:
gateway:
routes:
- id: user_route
# 直接写死目标服务的 IP 和端口
uri: http://127.0.0.1:8001
predicates:
- Path=/user/**
之后就可以正常转发了。
谓词配置
配置中的predicates是gateway的谓词,这部分可谓非常复杂了。上述例子里用的是路径谓词。此外我还整理了其他比较好用的谓词.
| 谓词名称 | 用途 | 典型场景 |
|---|---|---|
| Path | 匹配请求路径 | 将 /api/ 转发给某个服务 |
| Query | 匹配请求参数 | 根据 URL 中的参数(如版本号)分流 |
| Header | 匹配请求头 | 根据 User-Agent 或自定义 Header 鉴权 |
| Method | 匹配请求方法 | 只允许 GET 或 POST 请求通过 |
| Cookie | 匹配 Cookie | 针对特定用户群体的灰度发布 |
| Host | 匹配域名 | 基于域名的路由(如 *.abc.com) |
| RemoteAddr | 匹配客户端 IP | 黑白名单、IP 限流 |
| After/Before | 匹配时间 | 活动期间开启特定页面,或维护窗口 |
过滤器
除了谓词以外,gateway还提供了过滤器配置,以达到更精致的网关控制。以下是常见的过滤器:
| 分类 | 过滤器名称 (Filter Name) | 核心作用 |
|---|---|---|
| 请求处理 | AddRequestHeader |
给请求头添加自定义信息 |
AddRequestParameter |
给请求添加参数 | |
RewritePath |
重写请求路径(最常用) | |
StripPrefix |
截断请求路径前缀 | |
RequestRateLimiter |
请求限流(令牌桶算法) | |
| 响应处理 | AddResponseHeader |
给响应头添加信息 |
DedupeResponseHeader |
去除响应头中重复的值 | |
SetStatus |
修改返回的状态码 | |
| 路由增强 | Hystrix |
熔断保护(旧版) |
RequestSize |
限制请求包大小 | |
Retry |
失败自动重试 |
转发实现
gateway是怎么转发的?gateway是基于WebFlux技术的。gateway写了一个RoutePredicateHandlerMapping,该类实现了HandlerMapping接口,所以充当了传统servlet里控制器的角色。核心方法在lookupRoute里。在追踪gateway源码时我们可以记录一下堆栈的变化。
- 线程Thread[reactor-http-nio-2,5,main]
at org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping.lookupRoute(RoutePredicateHandlerMapping.java:128)
at org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping.getHandlerInternal(RoutePredicateHandlerMapping.java:87)
at org.springframework.web.reactive.handler.AbstractHandlerMapping.getHandler(AbstractHandlerMapping.java:181)
- 线程Thread[reactor-http-nio-2,5,main]
at org.springframework.cloud.gateway.handler.FilteringWebHandler.handle(FilteringWebHandler.java:77)
at org.springframework.web.reactive.result.SimpleHandlerAdapter.handle(SimpleHandlerAdapter.java:45)
at org.springframework.web.reactive.DispatcherHandler.invokeHandler(DispatcherHandler.java:161)
- 线程Thread[reactor-http-nio-2,5,main]
at org.springframework.cloud.gateway.filter.NettyRoutingFilter.filter(NettyRoutingFilter.java:104)
at org.springframework.cloud.gateway.handler.FilteringWebHandler$GatewayFilterAdapter.filter(FilteringWebHandler.java:138)
at org.springframework.cloud.gateway.filter.OrderedGatewayFilter.filter(OrderedGatewayFilter.java:44)
在NettyRoutingFilter这个类里终于找到了转发的代码了,我只贴一行代码,不过这行代码可够长的了:
java
Flux<HttpClientResponse> responseFlux = this.httpClient.headers(headers -> {
headers.add(httpHeaders);
if (preserveHost) {
String host = request.getHeaders().getFirst(HttpHeaders.HOST);
headers.add(HttpHeaders.HOST, host);
}
else {
// let Netty set it based on hostname
headers.remove(HttpHeaders.HOST);
}
}).request(method).uri(url).send((req, nettyOutbound) -> {
if (log.isTraceEnabled()) {
nettyOutbound.withConnection(connection -> log.trace(
"outbound route: " + connection.channel().id().asShortText()
+ ", inbound: " + exchange.getLogPrefix()));
}
return nettyOutbound.send(request.getBody()
.map(dataBuffer -> ((NettyDataBuffer) dataBuffer).getNativeBuffer()));
}).responseConnection((res, connection) -> {
// 省略很多很多代码。。。。。。。
return Mono.just(res);
});