SpringCloud升级之路2020.0.x版-41. SpringCloudGateway 基本流程讲解(1)

2021年11月25日 阅读数:2
这篇文章主要向大家介绍SpringCloud升级之路2020.0.x版-41. SpringCloudGateway 基本流程讲解(1),主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

本系列代码地址: https://github.com/JoJoTec/sp...

接下来,将进入咱们升级之路的又一大模块,即网关模块。网关模块咱们废弃了已经进入维护状态的 zuul,选用了 Spring Cloud Gateway 做为内部网关。为什么选择 Spring Cloud Gateway 而不是 nginx 还有 Kong 的缘由是:java

  1. 项目组对于 Java 更加熟悉,而且对于 Project Reactor 异步编程也比较熟悉,这个比较重要
  2. 须要在网关中使用咱们以前实现的基于请求的有状态重试的压力敏感的负载均衡器
  3. 须要在网关中实现重试
  4. 须要在网关中实现实例路径断路
  5. 须要在网关中进行业务统一加解密
  6. 须要在网关中实现 BFF(Backends For Frontends)接口,即根据客户端请求,将某几个不一样接口的请求一次性组合返回
  7. 须要在网关中使用 Redis 记录一些与 Token 相关的值

所以,咱们使用了 Spring Cloud Gateway 做为内部网关,接下来,咱们就来依次实现上面说的这些功能。同时在本次升级使用过程当中, Spring Cloud Gateway 也有一些坑,例如:react

  1. 结合使用 spring-cloud-sleuth 会有链路信息追踪,可是某些状况链路信息会丢失
  2. 对于三方 Reactor 封装的异步 API (例如前面提到的操做 Redis 使用的 spring-data-redis)理解不到位致使关键线程被占用

可是首先,咱们须要简单理解下 Spring Cloud Gateway 究竟包括哪些组件以及整个调用流程是什么样子的。因为 Spring Cloud Gateway 基于 Spring-Boot 和 Spring-Webflux 实现,因此咱们会从外层 WebFilter 开始说明,而后分析如何走到 Spring Cloud Gateway 的封装逻辑,以及 Spring Cloud Gateway 包含的组件,请求是如何转发出去,回来后又通过了哪些处理,这些咱们都会逐一分析。nginx

建立一个简单的 API 网关

为了详细分析流程,咱们先来建立一个简单的网关,用于快速上手并分析。git

首先建立依赖:github

pom.xmlweb

<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>spring-cloud-parent</artifactId>
        <groupId>com.github.jojotech</groupId>
        <version>2020.0.3-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-cloud-api-gateway</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.github.jojotech</groupId>
            <artifactId>spring-cloud-webflux</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
    </dependencies>
</project>

parent 指向了咱们项目的 spring-cloud-parent,同时加入了上一节实现的 spring-cloud-webflux 依赖,同时还须要加入 spring-cloud-starter-gateway,因为在咱们的 spring-cloud-parent 已经指定了 spring-cloud-parent 的版本依赖管理,因此这里不须要指定 spring-cloud-starter-gateway 的版本redis

而后,咱们开始编写配置文件:spring

application.ymlapache

server:
  ##端口为 8181
  port: 8181
spring:
  application:
    # 微服务名称是 apiGateway
    name: apiGateway
  cloud:
    gateway:
      httpclient:
        # 网关转发到其余微服务的 HTTP 链接超时为 500ms
        connect-timeout: 500
        # 网关转发到其余微服务的 HTTP 响应超时为 500ms
        response-timeout: 60000
      routes:
        # 编写转发规则
        - id: first_route
          # 转发到微服务 test-service
          uri: lb://test-service
          # 包含哪些路径
          predicates:
            - Path=/test-ss/**
          #  转发到的微服务访问路径,去掉路径中的第一块,即去掉 /test-ss
          filters:
            - StripPrefix=1
    loadbalancer:
      # 指定 zone,由于咱们以前在负载均衡中加入了只有同一个 zone 的实例才能互相访问的逻辑
      zone: test
      ribbon:
        # 关闭ribbon
        enabled: false
      cache:
        # 本地微服务实例列表缓存时间
        ttl: 5
        # 缓存大小,你的微服务调用多少个其余微服务,大小就设置为多少,默认256
        capacity: 256
    discovery:
      client:
        simple:
          # 使用 spring-common 中的简单 DiscoveryClient 服务发现客户端,就是将微服务实例写死在配置文件中
          instances:
            # 指定微服务 test-service 的实例列表
            test-service:
              - host: httpbin.org
                port: 80
                metadata:
                  # 指定该实例的 zone,由于咱们以前在负载均衡中加入了只有同一个 zone 的实例才能互相访问的逻辑 
                  zone: test
eureka:
  client:
    # 关掉 eureka
    enabled: false

最后编写启动入口类:编程

package com.github.jojotech.spring.cloud.apigateway;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication(scanBasePackages = "com.github.jojotech.spring.cloud.apigateway")
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

启动,访问路径: http://127.0.0.1:8181/test-ss/anything,能够看到请求被发送到 httpbin.org 的 anything 路径中,这个接口会返回请求中的全部信息。

这样,咱们就实现了一个简单的网关。接下来咱们来详细分析其工做流程和源码。

异步环境下请求处理的核心 - Spring Boot + Spring WebFlux 的 WebHandler

咱们建立的简易网关,外层的服务容器其实就是基于 Netty 和 Project Reactor 的容器,咱们跳过这些,直接进入 Spring Boot 相关的处理逻辑。咱们只须要知道,请求和其对应的响应,会被外层的容器封装成为 ServerHttpRequest requestServerHttpResponse response(都在 org.springframework.http.server.reactive 这个包下)。

而后,会交由 WebHandler 进行处理。WebHandler 的实现,实际上是一种责任链装饰模式,以下图所示。每一层的 WebHandler 会将 requestresponse 进行对应本身责任的装饰,而后交给内层的 WebHandler 处理。

image

HttpWebHandlerAdapter - 将请求封装成 ServerWebExchange

WebHandler 的接口定义是:

public interface WebHandler {
    Mono<Void> handle(ServerWebExchange exchange);
}

可是最外层传进来的参数是 requestresponse,须要将他们封装成 ServerWebExchange,这个工做就是在 HttpWebHandlerAdapter 中作的。HttpWebHandlerAdapter 其实主要任务就是将各类参数封装成 ServerWebExchange(除了和本次请求相关的 requestresponse,还有会话管理器 SessionManager,编码解码器配置,国际化配置还有 ApplicationContext 用于扩展)。

除了这些,处理 Forwarded 还有 X-Forwarded* 相关的 Header 的配置逻辑,也在这里进行。而后将封装好的 ServerWebExchange 交给内层的 WebHandlerExceptionHandlingWebHandler 继续处理。同时,从源码中能够看出,交给内层处理的 Mono 还加入了异常处理和记录响应信息的逻辑:

HttpWebHandlerAdapter.java

//交给内层处理封装好的 `ServerWebExchange`
return getDelegate().handle(exchange)
        //记录响应日志,trace 级别,通常用不上
        .doOnSuccess(aVoid -> logResponse(exchange))
        //处理内层没有处理的异常,通常不会走到这里
        .onErrorResume(ex -> handleUnresolvedError(exchange, ex))
        //在全部处理完成后,将 response 设为 complete
        .then(Mono.defer(response::setComplete));

剩下的内层的 WebHandler,咱们将在下一节中继续分析

微信搜索“个人编程喵”关注公众号,每日一刷,轻松提高技术,斩获各类offer