案例搭建
官网地址
父Pom
<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
<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>
server:port: 9005
spring :application:name: gateway-servercloud:nacos:discovery:server-addr: ip:8848gateway:routes:- id: gateway-provideruri: http://127.0.0.1:8088predicates:- Path=/provider/**filters:- StripPrefix=1- id: gateway-consumeruri: http://127.0.0.1:8089predicates:- Path=/consumer/** filters:- StripPrefix=1 #转发之前将路径前面的一个字符串去除 比如/consumer/echo 最终转发到服务是 http://127.0.0.1:8089/echo- id: gateway-lburi: lb://openfeign-provider #负载均衡,填写nacos里面的服务名称predicates:- Path=/lb/**filters:- StripPrefix=1
启动一个普通的服务端和消费端,另外起一个有三台服务的集群,这些节点的搭建比较容易,可以看同目录笔记。
怎么做到负载均衡?怎么探索源码找到负载均衡?
org.springframework.cloud.gateway.filter.LoadBalancerClientFilter
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 urladdOriginalRequestUrl(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();}// 选择好节点以后重新组织请求URIURI 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进行定义
@Component
public class CostGatewayFilterFactory extendsAbstractGatewayFilterFactory<CostGatewayFilterFactory.CustomConfig> {public CostGatewayFilterFactory() {super(CustomConfig.class);}@Overridepublic List<String> shortcutFieldOrder() {return Arrays.asList("switchStatus");}@Overridepublic GatewayFilter apply(CostGatewayFilterFactory.CustomConfig config) {return new GatewayFilter() {@Overridepublic 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");}));}@Overridepublic 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;}}
}
配置
routes:- id: gateway-provideruri: http://127.0.0.1:8088predicates:- Path=/provider/**filters:- StripPrefix=1- Cost=ON # 这个名字取前面GatewayFilterFactory的
自定义全局
@Component
public class GlobalCustomFilter implements GlobalFilter {@Overridepublic 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的配置,并且将请求的真实地址存放到了
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 进行了真正的转发调用
这个经典的图必须贴上: