案例搭建
父Pom
xml
<com.alibaba.cloud.version>2.2.8.RELEASE</com.alibaba.cloud.version>
<com.cloud.version>Hoxton.SR12</com.cloud.version>
<com.dubbo.version>2.2.7.RELEASE</com.dubbo.version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${com.alibaba.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${com.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba.cloud/spring-cloud-starter-dubbo -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
<version>${com.dubbo.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
工程POM
xml
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
yml
server:
port: 9005
spring :
application:
name: gateway-server
cloud:
nacos:
discovery:
server-addr: ip:8848
gateway:
routes:
- id: gateway-provider
uri: http://127.0.0.1:8088
predicates:
- Path=/provider/**
filters:
- StripPrefix=1
- id: gateway-consumer
uri: http://127.0.0.1:8089
predicates:
- Path=/consumer/**
filters:
- StripPrefix=1 #转发之前将路径前面的一个字符串去除 比如/consumer/echo 最终转发到服务是 http://127.0.0.1:8089/echo
- id: gateway-lb
uri: lb://openfeign-provider #负载均衡,填写nacos里面的服务名称
predicates:
- Path=/lb/**
filters:
- StripPrefix=1
启动一个普通的服务端和消费端,另外起一个有三台服务的集群,这些节点的搭建比较容易,可以看同目录笔记。
怎么做到负载均衡?怎么探索源码找到负载均衡?
org.springframework.cloud.gateway.filter.LoadBalancerClientFilter
java
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
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))) {
return chain.filter(exchange);
}
// preserve the original url
addOriginalRequestUrl(exchange, url);
if (log.isTraceEnabled()) {
log.trace("LoadBalancerClientFilter url before: " + url);
}
// 选择实例
final ServiceInstance instance = choose(exchange);
if (instance == null) {
throw NotFoundException.create(properties.isUse404(),
"Unable to find instance for " + url.getHost());
}
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 = instance.isSecure() ? "https" : "http";
if (schemePrefix != null) {
overrideScheme = url.getScheme();
}
// 选择好节点以后重新组织请求URI
URI requestUrl = loadBalancer.reconstructURI(
new DelegatingServiceInstance(instance, overrideScheme), uri);
if (log.isTraceEnabled()) {
log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
}
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);
return chain.filter(exchange);
}
自定义拦截
首先看官方网提供的几种断言,都是比较简单的这里不在演示:
官网地址
源码,这些类提供了这些能力:
自定义局部
自定义类,这个类可以参考DedupeResponseHeaderGatewayFilterFactory进行定义
java
@Component
public class CostGatewayFilterFactory extends
AbstractGatewayFilterFactory<CostGatewayFilterFactory.CustomConfig> {
public CostGatewayFilterFactory() {
super(CustomConfig.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("switchStatus");
}
@Override
public GatewayFilter apply(CostGatewayFilterFactory.CustomConfig config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange,
GatewayFilterChain chain) {
if (!config.switchStatus.equalsIgnoreCase("on")) {
// 如果这个开关是关闭状态
return chain.filter(exchange);
}
long sTime = System.currentTimeMillis();
exchange.getAttributes().put("STIME", sTime);
return chain.filter(exchange).then(Mono.fromRunnable(
() -> {
long stime = (long) exchange.getAttributes().get("STIME");
long cost = System.currentTimeMillis() - stime;
System.out.println("cost is : " + cost + " ms");
}));
}
@Override
public String toString() {
return filterToStringCreator(
CostGatewayFilterFactory.this)
.append(config.getSwitchStatus(), config.getSwitchStatus())
.toString();
}
};
}
public static class CustomConfig {
private String switchStatus;
public String getSwitchStatus() {
return switchStatus;
}
public void setSwitchStatus(String switchStatus) {
this.switchStatus = switchStatus;
}
}
}
配置
yml
routes:
- id: gateway-provider
uri: http://127.0.0.1:8088
predicates:
- Path=/provider/**
filters:
- StripPrefix=1
- Cost=ON # 这个名字取前面GatewayFilterFactory的
自定义全局
java
@Component
public class GlobalCustomFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 过滤前
System.out.println("Before : " + exchange.getRequest().getURI());
return chain.filter(exchange).then(Mono.fromRunnable(
() -> {
long stime = (long) exchange.getAttributes().get("STIME");
long cost = System.currentTimeMillis() - stime;
System.out.println("End cost time is : " + cost);
}));
}
}
Spring Cloud Gateway 是基于Netty 实现的,而不是Tomcat。我本来想试试mvc的过滤器和gateway的过滤器的优先级,结果怎么配置filter也不生效,原来如此,菜狗终究是菜狗。
可以换为Tomcat
转发重定向原理
- Gateway 框架体系处理的入口org.springframework.http.server.reactive.ReactorHttpHandlerAdapter
- 构建网关上下文:org.springframework.web.server.adapter.HttpWebHandlerAdapter
- 遍历 Mapping 获取真实处理请求的 Handler org.springframework.web.reactive.DispatcherHandler
- 构建过滤器链:org.springframework.cloud.gateway.handler.FilteringWebHandler
- 过滤器必经之路:org.springframework.cloud.gateway.handler.FilteringWebHandler.DefaultGatewayFilterChain
- 负载均衡过滤器:org.springframework.cloud.gateway.filter.LoadBalancerClientFilter
- 利用 Netty 发送网络请求过滤器:org.springframework.cloud.gateway.filter.NettyRoutingFilter
RouteToRequestUrlFilter 这个过滤器处理了Route的配置,并且将请求的真实地址存放到了
java
URI mergedUrl = UriComponentsBuilder.fromUri(uri)
// .uri(routeUri)
.scheme(routeUri.getScheme()).host(routeUri.getHost())
.port(routeUri.getPort()).build(encoded).toUri();
exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, mergedUrl);
NettyRoutingFilter 进行了真正的转发调用
这个经典的图必须贴上: