文章目录
- 背景
- SpringCloud Gateway 简介
- Gateway入门搭建
- 1. 创建一个SpringBoot 项目
- 2. 添加依赖
- 3. 配置路由转发
- 4. 添加请求`log` `Filter`
- 5. 搭建测试服务
- 测试
- 关于我
背景
接上次线上Spring Boot 和Spring Cloud、Spring Cloud Alibaba版本如何选择以及Zuul和Gateway请求IO模型比对(WebFlux优势)以及Reactor模型分析
选定微服务中网关使用Gateway
,接下来我们先搭建一个简单的网关使用
SpringCloud Gateway 简介
github地址: https://github.com/spring-cloud/spring-cloud-gateway
官网文档地址: https://spring.io/projects/spring-cloud-gateway#learn
SpringCloud Gateway
是 Spring Cloud
的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。
SpringCloud Gateway
作为 Spring Cloud
生态系统中的网关,目标是替代 Zuul
,在Spring Cloud 2.0
以上版本中,没有对新版本的Zuul 2.0
以上最新高性能版本进行集成,仍然还是使用的Zuul 2.0
之前的非Reactor模式的老版本。而为了提升网关的性能,SpringCloud Gateway
是基于WebFlux
框架实现的,而WebFlux
框架底层则使用了高性能的Reactor模式通信框架Netty。
说人话为什么需要网关呢?我们来想想没有网关我们有多个服务需要做什么?
首先我们的客户端流量入口调用之间的关系可能是这样的
这样会存在什么问题呢?
- 如果添加鉴权功能,需要对每一个服务进行改造
- 跨域问题需要对每一个服务进行改造
- 灰度发布、动态路由需要对每一个服务进行改造
- 存在安全问题。每个微服务暴露的 Endpoint是固定的,客户端访问需要清楚各个微服务真实的Endpoint
如果我们在服务前添加一层网关作为所有流量的入库,整个架构就变成如下方式
然后上面所有的问题都可以在网关统一实现,各个服务就无需再实现
Gateway入门搭建
1. 创建一个SpringBoot 项目
2. 添加依赖
<?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.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.wh</groupId>
<artifactId>gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gateway</name>
<description>gateway</description>
<properties>
<java.version>11</java.version>
<spring-cloud.version>2020.0.1</spring-cloud.version>
<spring-cloud-alibaba.version>2021.1</spring-cloud-alibaba.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>com.yomahub</groupId>
<artifactId>tlog-gateway-spring-boot-starter </artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3. 配置路由转发
- Application.yml
server:
port: 8080
spring:
application:
name: gate-way
cloud:
gateway:
routes:
- id: zou_route
uri: http://localhost:10080
predicates:
- Path=/zou/**
这里简单的配置了一下网关的启动端口为8080
,然后配置了一个路由规则
这里配置的路由规则就是 拦截所有请求路径前缀为/zou
然后转发到http://localhost:10080
路径
举个简单例子:
我这里访问网关的url为
localhost:8080/zou/finance/v1/bill/exception
那么实际的访问url就会被转发到
http://localhost:10080/zou/finance/v1/bill/exception
4. 添加请求log
Filter
@Slf4j
@Component
@AllArgsConstructor
public class RequestLogFilter implements GlobalFilter, Ordered {
private static final String START_TIME = "startTime";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String requestUrl = exchange.getRequest().getURI().getRawPath();
// 构建成一条长 日志,避免并发下日志错乱
StringBuilder beforeReqLog = new StringBuilder(300);
// 日志参数
List<Object> beforeReqArgs = new ArrayList<>();
beforeReqLog.append("\n\n================ zou Gateway Request Start ================\n");
// 打印路由
beforeReqLog.append("===> {}: {}\n");
// 参数
String requestMethod = exchange.getRequest().getMethodValue();
beforeReqArgs.add(requestMethod);
beforeReqArgs.add(requestUrl);
// 打印请求头
HttpHeaders headers = exchange.getRequest().getHeaders();
headers.forEach((headerName, headerValue) -> {
beforeReqLog.append("===Headers=== {}: {}\n");
beforeReqArgs.add(headerName);
beforeReqArgs.add(StringUtils.collectionToCommaDelimitedString(headerValue));
});
beforeReqLog.append("================ zou Gateway Request End =================\n");
// 打印执行时间
log.info(beforeReqLog.toString(), beforeReqArgs.toArray());
exchange.getAttributes().put(START_TIME, System.currentTimeMillis());
return chain.filter(exchange).then(Mono.fromRunnable(() -> {
ServerHttpResponse response = exchange.getResponse();
Long startTime = exchange.getAttribute(START_TIME);
long executeTime = 0L;
if (startTime != null) {
executeTime = (System.currentTimeMillis() - startTime);
}
// 构建成一条长 日志,避免并发下日志错乱
StringBuilder responseLog = new StringBuilder(300);
// 日志参数
List<Object> responseArgs = new ArrayList<>();
responseLog.append("\n\n================ zou Gateway Response Start ================\n");
// 打印路由 200 get: /mate*/xxx/xxx
responseLog.append("<=== {} {}: {}: {}\n");
// 参数
responseArgs.add(response.getStatusCode().value());
responseArgs.add(requestMethod);
responseArgs.add(requestUrl);
responseArgs.add(executeTime + "ms");
// 打印请求头
HttpHeaders httpHeaders = response.getHeaders();
httpHeaders.forEach((headerName, headerValue) -> {
responseLog.append("===Headers=== {}: {}\n");
responseArgs.add(headerName);
responseArgs.add(StringUtils.collectionToCommaDelimitedString(headerValue));
});
responseLog.append("================ zou Gateway Response End =================\n");
// 打印执行时间
log.info(responseLog.toString(), responseArgs.toArray());
}));
}
@Override
public int getOrder() {
return 0;
}
}
5. 搭建测试服务
这里我们搭建一个简单的zou
服务,一个简单的SpringBoot
项目
配置文件
server:
port: 10080
servlet:
context-path: /zou
然后在写一个简单的接口
@RestController
@RequestMapping("/finance/v1")
@Slf4j
public class FinanceController {
@GetMapping("/bill/exception")
public String getFinanceExceptionVO() {
System.out.println("路由转发成功");
return "SUCCESS";
}
}
测试
我们访问
localhost:8080/zou/finance/v1/bill/exception
我们在zou
项目的控制台可以看到输出路由转发成功,代表请求转发成功了
同时我们也能看到网关打印出了请求log