本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent

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

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

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

  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 包含的组件,请求是如何转发出去,回来后又经过了哪些处理,这些我们都会逐一分析。

创建一个简单的 API 网关

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

首先创建依赖:

pom.xml

<?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 的版本

然后,我们开始编写配置文件:

application.yml

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 处理。

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

SpringCloud升级之路2020.0.x版-41. SpringCloudGateway 基本流程讲解(1)的更多相关文章

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

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 我们继续分析上一节提到的 WebHandler,经过将请求封装成 ServerWebExc ...

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

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 我们继续分析上一节提到的 WebHandler.加入 Spring Cloud Sleut ...

  3. SpringCloud升级之路2020.0.x版-42.SpringCloudGateway 现有的可供分析的请求日志以及缺陷

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 网关由于是所有外部用户请求的入口,记录这些请求中我们需要的元素,对于线上监控以及业务问题定 ...

  4. SpringCloud升级之路2020.0.x版-1.背景

    本系列为之前系列的整理重启版,随着项目的发展以及项目中的使用,之前系列里面很多东西发生了变化,并且还有一些东西之前系列并没有提到,所以重启这个系列重新整理下,欢迎各位留言交流,谢谢!~ Spring ...

  5. SpringCloud升级之路2020.0.x版-6.微服务特性相关的依赖说明

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford spring-cl ...

  6. SpringCloud升级之路2020.0.x版-10.使用Log4j2以及一些核心配置

    本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 我们使用 Log4 ...

  7. SpringCloud升级之路2020.0.x版-43.为何 SpringCloudGateway 中会有链路信息丢失

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在开始编写我们自己的日志 Filter 之前,还有一个问题我想在这里和大家分享,即在 Sp ...

  8. SpringCloud升级之路2020.0.x版-29.Spring Cloud OpenFeign 的解析(1)

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在使用云原生的很多微服务中,比较小规模的可能直接依靠云服务中的负载均衡器进行内部域名与服务 ...

  9. SpringCloud升级之路2020.0.x版-34.验证重试配置正确性(1)

    本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在前面一节,我们利用 resilience4j 粘合了 OpenFeign 实现了断路器. ...

随机推荐

  1. NOI 2017 Day1 题解

    被虐爆了... T1 整数 题目传送门 Description 有一个整数 \(x\),有 \(n\) 此操作,每次操作为以下两种情况: 给出 \(a,b\),将 \(x\) 加上 \(a\times ...

  2. Markdown数学公式

    1. 显示位置与大小 正文(inline)中的LaTeX公式用$...$定义, 显示在当前行内. $\sum_{i=0}^N\int_{a}^{b}g(t,i)\text{d}t$ \(\sum_{i ...

  3. ❤️这应该是Postman最详细的中文使用教程了❤️(新手使用,简单明了)

    ️这应该是Postman最详细的中文使用教程了️(新手使用,简单明了) 在前后端分离开发时,后端工作人员完成系统接口开发后,需要与前端人员对接,测试调试接口,验证接口的正确性可用性.而这要求前端开发进 ...

  4. 7-Zip

    7-Zip https://www.7-zip.org/

  5. Wireshark 过滤器的使用

    符号 例子 = = tcp.port = = 80 过滤出来TCP包含80端口的数据包 != ip.src != 127.0.0.1 ip的原地址不是127.0.0.1过滤出来 > lp.len ...

  6. HttpClient.PatchAsJsonAsync - dotnet/runtime 项目贡献小记

    TL;DR 迫于 PatchAsJsonAsync 方法缺失,我给 dotnet/runtime 项目贡献了相关的 API,可惜要到 .NET7 才能用上. https://github.com/do ...

  7. python的虚拟环境Anaconda使用

    Anaconda 使用conda常用命令   1.首先在所在系统中安装Anaconda.可以打开命令行输入conda -V检验是否安装以及当前conda的版本. 2.conda常用的命令. 1)con ...

  8. JVM:内存模型

    JVM:内存模型 说明:这是看了 bilibili 上 黑马程序员 的课程 JVM完整教程 后做的笔记 1. java 内存模型 很多人将[java 内存结构]与[java 内存模型]傻傻分不清,[j ...

  9. [对对子队]会议记录4.19(Scrum Meeting10)

    今天已完成的工作 何瑞 ​ 工作内容:搭建第2关,基本完成第3关 ​ 相关issue:搭建关卡2.3 ​ 相关签入:4.19签入1 4.19签入2 刘子航 ​ 工作内容:完成关卡选择界面的设计图 ​ ...

  10. 洛谷 P3147 [USACO16OPEN]262144 P

    链接: P3147 P3146双倍经验 前言: 今天发现的一道很有意思的DP题 分析: 第一眼以为是区间DP,于是设f[i][j]为从第i个数到第j个数可以合出的最大值,但思考后发现并不能简单合并,并 ...