文章目录

  • 背景
  • 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。

说人话为什么需要网关呢?我们来想想没有网关我们有多个服务需要做什么?

首先我们的客户端流量入口调用之间的关系可能是这样的

springcloud getway参数详解 springcloud gateway lb_spring

这样会存在什么问题呢?

  1. 如果添加鉴权功能,需要对每一个服务进行改造
  2. 跨域问题需要对每一个服务进行改造
  3. 灰度发布、动态路由需要对每一个服务进行改造
  4. 存在安全问题。每个微服务暴露的 Endpoint是固定的,客户端访问需要清楚各个微服务真实的Endpoint

如果我们在服务前添加一层网关作为所有流量的入库,整个架构就变成如下方式

springcloud getway参数详解 springcloud gateway lb_spring_02

然后上面所有的问题都可以在网关统一实现,各个服务就无需再实现

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

springcloud getway参数详解 springcloud gateway lb_gateway_03

我们在zou 项目的控制台可以看到输出路由转发成功,代表请求转发成功了

springcloud getway参数详解 springcloud gateway lb_Cloud_04

同时我们也能看到网关打印出了请求log

springcloud getway参数详解 springcloud gateway lb_spring_05