原文网址:Spring Cloud Gateway--用GlobalFilter实现动态路由--实例/方案_IT利刃出鞘的博客-CSDN博客
简介
本文用示例介绍Spring Cloud Gateway的动态路由(强制路由)的方案。
为什么需要动态路由
场景1:开发环境:提高调试效率
在开发软件(例如Idea)上打断点调试是最快的调试方法。如果是与前端联调,前端一般将请求地址设置为网关,这样会负载均衡到不同的机器上,不能指定到自己的电脑。
如果有了动态路由,那么可以指定某个url直接路由到自己电脑。
场景2:线上环境:可以快速将请求切到某个服务器
如果没有动态路由,代码上线时如果出了问题,需要回滚代码,重新部署,很慢。
如果有了动态路由,代码上线时,只更新部分实例,然后将流量切过去,如果有问题,立马切到其他未更新的实例即可。
实例
gateway项目
自定义GlobalFilter(核心)
package com.knife.dynamic;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import java.net.URI;
/**
* 动态路由
*/
@Slf4j
@Component
public class Router4jClientFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest originalRequest = exchange.getRequest();
// 可获得所有请求参数
// Map<String, String> cachedRequestBody = exchange
// .getAttribute(ServerWebExchangeUtils.CACHED_REQUEST_BODY_ATTR);
//获取域名+端口后的path
String rawPath = originalRequest.getURI().getRawPath();
// todo 从redis中取出所有url,然后用rawPath去匹配
String host = "localhost";
int port = 9012;
URI originUri = exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
URI newUri = UriComponentsBuilder.fromUri(originUri)
.host(host)
.port(port)
.build()
.toUri();
//重新封装request对象
ServerHttpRequest newRequest = originalRequest.mutate().uri(newUri).build();
// NettyRoutingFilter 最终从GATEWAY_REQUEST_URL_ATTR 取出uri对象进行http请求
// 所以这里要将新的对象覆盖进去
exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, newUri);
return chain.filter(exchange.mutate().request(newRequest).build());
// 也可以加回调方法
// return chain.filter(exchange.mutate().request(newRequest).build())
// .then(Mono.fromRunnable(() -> {
// //请求完成回调方法 可以在此完成计算请求耗时等操作
// }));
}
/**
* 这里不能用@Order,必须实现Ordered接口
* 值必须大于10150。原因:Gateway有自己的过滤器,两个比较重要的如下:
* RouteToRequestUrlFilter:将根据Route将网关请求转为真实的请求。order = 10000
* ReactiveLoadBalancerClientFilter:负载均衡。order = 10150
*/
@Override
public int getOrder() {
return 15000;
}
}
配置文件
server:
port: 6001
spring:
application:
name: gateway
cloud:
gateway:
discovery:
locator:
# 解析微服务名称为主机名和端口,实现动态路由
enabled: true
# 服务名使用小写
lower-case-service-id: true
nacos:
discovery:
server-addr: localhost:8848
# 配置Gateway日志等级,输出转发细节信息
logging:
level:
org.springframework.cloud.gateway: debug
依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<!--<version>2.3.7.RELEASE</version>-->
<version>2.4.13</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.knife</groupId>
<artifactId>gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gateway</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<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>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.knife</groupId>
<artifactId>router4j-client</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2020.0.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2021.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.4.13</version>
</plugin>
</plugins>
</build>
</project>
nacos项目
nacos直接从官方下载,然后运行即可。
业务项目
Controller
package com.knife.order.controller;
import com.knife.common.entity.Result;
import com.knife.order.entity.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* http://localhost:9011/order/create/?userId=1&productId=1&count=10&money=100
*/
@Slf4j
@RestController
@RequestMapping("order")
public class OrderController {
// 正常流程
@PostMapping("create")
public Result create(Order order) {
log.info("订单服务:创建订单:{}", order);
return new Result().message("创建订单成功");
}
}
配置文件
bootstrap.yml
spring:
application:
name: order
cloud:
nacos:
config:
server-addr: 127.0.0.1:8848
application.yml
server:
port: 9012
spring:
application:
name: order
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.13</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<packaging>pom</packaging>
<groupId>com.knife</groupId>
<artifactId>parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>2020.0.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2021.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2021.1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<exclusions>
<exclusion>
<groupId> springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>com.knife</groupId>
<artifactId>common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.4.13</version>
</plugin>
</plugins>
</build>
</project>
测试
启动程序
1.启动nacos服务
执行:startup.cmd -m standalone
2.启动gateway服务
3.启动两个业务服务(order)
分别以不同的端口号启动。
先以上边的配置启动,再修改application.yml里的端口号,然后启动。修改后的为:
server:
port: 9013
spring:
application:
name: order
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
Idea同一个服务启动两个实例的方法见:Intellij IDEA--项目设置--导入项目/导入包/乱码/JDK版本_IT利刃出鞘的博客-CSDN博客
搜索关键字“同一服务启动多个实例”
测试
POST方式请求:http://localhost:6001/order/order/create/?userId=1&productId=1&count=10&money=100
请求10次,结果:请求全部到了指定的服务:9012端口
9012端口的服务:
9013端口的服务: