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

接下来,将进入我们升级之路的又一大模块,即网关模块。网关模块我们废弃了已经进入维护状态的 zuul,选用了 Spring Cloud Gateway 作为内部网关。为何选择 Spring Cloud Gateway 而不是 nginx 还有 Kong 的原因是:
- 项目组对于 Java 更加熟悉,并且对于 Project Reactor 异步编程也比较熟悉,这个比较重要
- 需要在网关中使用我们之前实现的基于请求的有状态重试的压力敏感的负载均衡器
- 需要在网关中实现重试
- 需要在网关中实现实例路径断路
- 需要在网关中进行业务统一加解密
- 需要在网关中实现 BFF(Backends For Frontends)接口,即根据客户端请求,将某几个不同接口的请求一次性组合返回
- 需要在网关中使用 Redis 记录一些与 Token 相关的值
因此,我们使用了 Spring Cloud Gateway 作为内部网关,接下来,我们就来依次实现上面说的这些功能。同时在本次升级使用过程中, Spring Cloud Gateway 也有一些坑,例如:
- 结合使用 spring-cloud-sleuth 会有链路信息追踪,但是某些情况链路信息会丢失。
- 对于三方 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 request 和 ServerHttpResponse response(都在 org.springframework.http.server.reactive 这个包下)。
然后,会交由 WebHandler 进行处理。WebHandler 的实现,其实是一种责任链装饰模式,如下图所示。每一层的 WebHandler 会将 request 和 response 进行对应自己责任的装饰,然后交给内层的 WebHandler 处理。

HttpWebHandlerAdapter - 将请求封装成 ServerWebExchange
WebHandler 的接口定义是:
public interface WebHandler {
Mono<Void> handle(ServerWebExchange exchange);
}
但是最外层传进来的参数是 request 和 response,需要将他们封装成 ServerWebExchange,这个工作就是在 HttpWebHandlerAdapter 中做的。HttpWebHandlerAdapter 其实主要任务就是将各种参数封装成 ServerWebExchange(除了和本次请求相关的 request 和 response,还有会话管理器 SessionManager,编码解码器配置,国际化配置还有 ApplicationContext 用于扩展)。
除了这些,处理 Forwarded 还有 X-Forwarded* 相关的 Header 的配置逻辑,也在这里进行。然后将封装好的 ServerWebExchange 交给内层的 WebHandler 即 ExceptionHandlingWebHandler 继续处理。同时,从源码中可以看出,交给内层处理的 Mono 还加入了异常处理和记录响应信息的逻辑:
//交给内层处理封装好的 `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)的更多相关文章
- SpringCloud升级之路2020.0.x版-41. SpringCloudGateway 基本流程讲解(2)
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 我们继续分析上一节提到的 WebHandler,经过将请求封装成 ServerWebExc ...
- SpringCloud升级之路2020.0.x版-41. SpringCloudGateway 基本流程讲解(3)
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 我们继续分析上一节提到的 WebHandler.加入 Spring Cloud Sleut ...
- SpringCloud升级之路2020.0.x版-42.SpringCloudGateway 现有的可供分析的请求日志以及缺陷
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 网关由于是所有外部用户请求的入口,记录这些请求中我们需要的元素,对于线上监控以及业务问题定 ...
- SpringCloud升级之路2020.0.x版-1.背景
本系列为之前系列的整理重启版,随着项目的发展以及项目中的使用,之前系列里面很多东西发生了变化,并且还有一些东西之前系列并没有提到,所以重启这个系列重新整理下,欢迎各位留言交流,谢谢!~ Spring ...
- SpringCloud升级之路2020.0.x版-6.微服务特性相关的依赖说明
本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford spring-cl ...
- SpringCloud升级之路2020.0.x版-10.使用Log4j2以及一些核心配置
本系列代码地址:https://github.com/HashZhang/spring-cloud-scaffold/tree/master/spring-cloud-iiford 我们使用 Log4 ...
- SpringCloud升级之路2020.0.x版-43.为何 SpringCloudGateway 中会有链路信息丢失
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在开始编写我们自己的日志 Filter 之前,还有一个问题我想在这里和大家分享,即在 Sp ...
- SpringCloud升级之路2020.0.x版-29.Spring Cloud OpenFeign 的解析(1)
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在使用云原生的很多微服务中,比较小规模的可能直接依靠云服务中的负载均衡器进行内部域名与服务 ...
- SpringCloud升级之路2020.0.x版-34.验证重试配置正确性(1)
本系列代码地址:https://github.com/JoJoTec/spring-cloud-parent 在前面一节,我们利用 resilience4j 粘合了 OpenFeign 实现了断路器. ...
随机推荐
- 3D Analyst Tools(3D Analyst 工具)
3D Analyst 工具 工具里有又细分如下分类: 注:以下代码的参数需要另行配置,不能直接执行:Python2不支持中文变量! 1.3D 要素 # Process: 3D 内部 arcpy.Ins ...
- Java初步学习——2021.09.23每日报告,第三周周四
(1)今天做了什么: (2)明天准备做什么? (3)遇到的问题,如何解决? 学习数组,编写了一个随机选牌的代码.自己最开始一直想只设置一个字符串数组,利用随机数来输出,但那样对字符串赋值会比较麻烦.可 ...
- 洛谷4455 [CQOI2018]社交网络 (有向图矩阵树定理)(学习笔记)
sro_ptx_orz qwq算是一个套路的记录 对于一个有向图来说 如果你要求一个外向生成树的话,那么如果存在一个\(u\rightarrow v\)的边 那么\(a[u][v]--,a[v][v] ...
- spring boot log4j2 最佳实践
为什么选择 log4j2 Log4j2 使用了 LMAX Disruptor 库.在多线程场景中,异步 Logger 的吞吐量比 Log4j 1.x 和 Logback 高 18 倍,延迟低几个数量级 ...
- javascript-原生-函数
本节呢讲解js的函数部分,js函数部分总共分为两大类:1.自定义函数.2.系统函数 说白了,系统函数就是js自己内置的函数,其他的都属于自定义函数. 1.自定义函数 函数是完成指定功能的程序段,可以反 ...
- leetcode347 —— n中topK && PriorityQueue(Heap) && Map遍历
题目要求:求前K个最频繁出现的数字. 1.很容易想到,使用HashMap<Integer,Integer>来存储<number,frequency>键值对 1 int n = ...
- k8s replicaset controller分析(1)-初始化与启动分析
replicaset controller分析 replicaset controller简介 replicaset controller是kube-controller-manager组件中众多控制 ...
- python反序列化1(__reduce__)
part1:不求甚解的复现 对于服务端源码: 编写恶意序列化对象生成程序: 将生成的恶意序列化对象输入服务端user,使其执行系统命令.(上面那俩其实都行) part2:原理解释 b'xxx'是 ...
- PHP伪协议与文件包含漏洞1
PHP文件包含漏洞花样繁多,需配合代码审计. 看能否使用这类漏洞时,主要看: (1)代码中是否有include(),且参数可控: 如: (2)php.ini设置:确保 allow_url_fopen= ...
- LeetCode:并查集
并查集 这部分主要是学习了 labuladong 公众号中对于并查集的讲解,文章链接如下: Union-Find 并查集算法详解 Union-Find 算法怎么应用? 概述 并查集用于解决图论中「动态 ...