Spring Cloud Gateway一次请求调用源码解析

简介: 最近通过深入学习Spring Cloud Gateway发现这个框架的架构设计非常简单、有效,很多组件的设计都非常值得学习,本文就Spring Cloud Gateway做一个简单的介绍,以及针对一次请求Spring Cloud Gateway的处理流程做一个较为详细的分析。

image.png

作者 | 寻筝
来源 | 阿里技术公众号

一 前言

最近通过深入学习Spring Cloud Gateway发现这个框架的架构设计非常简单、有效,很多组件的设计都非常值得学习,本文就Spring Cloud Gateway做一个简单的介绍,以及针对一次请求Spring Cloud Gateway的处理流程做一个较为详细的分析。

二 简介

Spring Cloud Gateway 即Spring官方推出的一款API网关,该框架包含了Spring5、SpringBoot2、Project Reactor,其中底层通信框架用的netty。Spring Cloud Gateway在推出之初的时候,Netflix公司已经推出了类似功能的API网关框架ZUUL,但ZUUL有一个缺点是通信方式是阻塞的,虽然后来升级到了非阻塞式的ZUUL2,但是由于Spring Cloud Gateway已经推出一段时间,同时自身也面临资料少、维护性较差的因素没有被广泛应用。

1 关键术语

在使用Spring Cloud Gateway的时候需要理解三个模块,即

Route:

即一套路由规则,是集URI、predicate、filter等属性的一个元数据类。

Predicate:

这是Java8函数式编程的一个方法,这里可以看做是满足什么条件的时候,route规则进行生效。

Filter:

filter可以认为是Spring Cloud Gateway最核心的模块,熔断、安全、逻辑执行、网络调用都是filter来完成的,其中又细分为gateway filter和global filter,区别在于是具体一个route规则生效还是所有route规则都生效。

可以先上一段代码来看看:

 @RequestMapping("/paramTest")public Object paramTest(@RequestParam Map<String,Object> param) {return param.get("name");}@Beanpublic RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes().route("path_route", r ->r.path("/get").filters(f -> f.addRequestParameter("name", "value")).uri("forward:///paramTest")).build();}
  • route方法代表的就是一个路由规则;
  • path方法代表的就是一个predicate,背后的现实是PathRoutePredicateFactory,在这段代码的含义即当路径包含/get的时候,当前规则生效。
  • filters方法的意思即给当前路由规则添加一个增加请求参数的filter,每次请求都对参数里添加 name:value 的键值对;
  • uri 方法的含义即最终路由到哪里去,这里的forward前缀会将请求交给spring mvc的DispatcherHandler进行路由,进行本机的逻辑调用,除了forward以外还可以使用http、https前缀进行http调用,lb前缀可以在配置注册中心后进行rpc调用。

image.png

上图是Spring Cloud Gateway官方文档给出的一个工作原理图,Spring Cloud Gateway 接收到请求后进行路由规则的匹配,然后交给web handler 进行处理,web handler 会执行一系列的filter逻辑。

三 流程分析

1 接受请求

Spring Cloud Gateway的底层框架是netty,接受请求的关键类是ReactorHttpHandlerAdapter,做的事情很简单,就是将netty的请求、响应转为http的请求、响应并交给一个http handler执行后面的逻辑,下图为该类的源码仅保留核心逻辑。

    @Overridepublic Mono< Void> apply(HttpServerRequest request, HttpServerResponse response) {NettyDataBufferFactory bufferFactory = new NettyDataBufferFactory(response.alloc());ServerHttpRequest adaptedRequest;ServerHttpResponse adaptedResponse;//转换请求try {adaptedRequest = new ReactorServerHttpRequest(request, bufferFactory);adaptedResponse = new ReactorServerHttpResponse(response, bufferFactory);}catch (URISyntaxException ex) {if (logger.isWarnEnabled()) {...}...return this.httpHandler.handle(adaptedRequest, adaptedResponse).doOnError(ex -> logger.warn("Handling completed with error: " + ex.getMessage())).doOnSuccess(aVoid -> logger.debug("Handling completed with success"));}

2 WEB过滤器链

http handler做的事情第一是将request 和 response转为一个exchange,这个exchange非常核心,是各个filter之间参数流转的载体,该类包含request、response、attributes(扩展字段),接着做的事情就是web filter链的执行,其中的逻辑主要是监控。

image.png

其中WebfilterChainParoxy 又会引出新的一条filter链,主要是安全、日志、认证相关的逻辑,由此可见Spring Cloud Gateway的过滤器设计是层层嵌套,扩展性很强。

image.png

3 寻找路由规则

核心类是RoutePredicateHandlerMapping,逻辑也非常简单,就是把所有的route规则的predicate遍历一遍看哪个predicate能够命中,核心代码是:

return this.routeLocator.getRoutes().filter(route -> {...return route.getPredicate().test(exchange);})

因为我这里用的是path进行过滤,所以背后的逻辑是PathRoutePredicateFactory来完成的,除了PathRoutePredicateFactory还有很多predicate规则。

image.png

这些路由规则都能从官方文档上找到影子。

image.png

4 核心过滤器链执行

找到路由规则后下一步就是执行了,这里的核心类是FilteringWebHandler,其中的源码为:

image.png

做的事情很简单:

  1. 获取route级别的过滤器
  2. 获取全局过滤器
  3. 两种过滤器放在一起并根据order进行排序
  4. 执行过滤器链

image.png

因为我的配置里包含了一个添加请求参数的逻辑,所以红线箭头处就是我配置的gateway filter名为 AddRequestParameterGatewayFilterFactory,其余全是Gloabl Filter,这些过滤器的功能主要是url解析,请求转发,响应回写等逻辑,因为我们这里用的是forward schema,所以请求转发会由ForwardRoutingFilter进行执行。

5 请求转发

ForwardRoutingFilter做的事情也很简单,直接复用了spring mvc的能力,将请求提交给dispatcherHandler进行处理,dispatcherHandler会根据path前缀找到需要目标处理器执行逻辑。

@Override
public Mono< Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);String scheme = requestUrl.getScheme();if (isAlreadyRouted(exchange) || !"forward".equals(scheme)) {return chain.filter(exchange);}setAlreadyRouted(exchange);//TODO: translate url?if (log.isTraceEnabled()) {log.trace("Forwarding to URI: "+requestUrl);}return this.dispatcherHandler.handle(exchange);
}

6 响应回写

响应回写的核心类是NettyWriteResponseFilter,但是大家可以注意到执行器链中NettyWriteResponseFilter的排序是在最前面的,按道理这种响应处理的类应该是在靠后才对,这里的设计比较巧妙。大家可以看到chain.filter(exchange).then(),意思就是执行到我的时候直接跳过下一个,等后面的过滤器都执行完后才执行这段逻辑,这种行为控制的方法值得学习。

@Override
public Mono< Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {// NOTICE: nothing in "pre" filter stage as CLIENT_RESPONSE_ATTR is not added// until the WebHandler is runreturn chain.filter(exchange).then(Mono.defer(() -> {HttpClientResponse clientResponse = exchange.getAttribute(CLIENT_RESPONSE_ATTR);if (clientResponse == null) {return Mono.empty();}log.trace("NettyWriteResponseFilter start");ServerHttpResponse response = exchange.getResponse();NettyDataBufferFactory factory = (NettyDataBufferFactory) response.bufferFactory();//TODO: what if it's not nettyfinal Flux< NettyDataBuffer> body = clientResponse.receive().retain() //TODO: needed?.map(factory::wrap);MediaType contentType = response.getHeaders().getContentType();return (isStreamingMediaType(contentType) ?response.writeAndFlushWith(body.map(Flux::just)) : response.writeWith(body));}));
}

四 总结

整体读完Spring Cloud Gateway请求流程代码后,有几点感受:

  1. 过滤器是Spring Cloud Gateway最核心的设计,甚至于可以夸张说Spring Cloud Gateway是一个过滤器链执行框架而不是一个API网关,因为API网关实际的请求转发、请求响应回写都是在过滤器中做的,这些是Spring Cloud Gateway感知不到的逻辑。
  2. Spring Cloud Gateway路由规则获取的模块具备优化的空间,因为是循环遍历进行获取的,如果每个route规则较多,predicate规则较复杂,就可以考虑用map进行优化了,当日route规则,predicate规则也不会很复杂,兼顾到代码的可读性,当前方式也没有什么问题。
  3. 作为API网关框架,内置了非常多的过滤器,如果有过滤器的卸载功能可能会更好,用户可用根据实际情况卸载不必要的功能,背后减少的逻辑开销,在调用量极大的API网关场景,收益也会很可观。

原文链接
本文为阿里云原创内容,未经允许不得转载。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/512005.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

函数计算GB镜像秒级启动:下一代软硬件架构协同优化

简介&#xff1a; 优化镜像加速冷启动大致分为两种做法&#xff1a;降低绝对延迟和降低冷启动概率。自容器镜像上线以来我们已经通过镜像加速技术&#xff0c;分阶段降低了绝对延迟。本文在此基础上&#xff0c;介绍借助函数计算下一代IaaS底座神龙裸金属和安全容器&#xff0c…

源码级别的广播与监听实现

作者 | 阿Q来源 | 阿Q说代码闲来无事&#xff0c;又翻了遍Spring的源码。不翻不知道&#xff0c;一翻吓一跳&#xff0c;之前翻过的源码已经吃进了肚子里&#xff0c;再见亦是陌生人。今天就带大家从源码的角度来分析一下广播与监听的底层实现原理。源码解析 为了实现广播与监听…

用代码玩剧本杀?第3届83行代码大赛剧情官方解析

简介&#xff1a; 由阿里云云效主办的2021年第3届83行代码挑战赛已经收官。超2万人围观&#xff0c;近4000人参赛&#xff0c;85个团队组团来战。大赛采用游戏闯关玩儿法&#xff0c;融合元宇宙科幻和剧本杀元素&#xff0c;让一众开发者玩得不亦乐乎。 说到剧本杀&#xff0c…

阿里大规模业务混部下的全链路资源隔离技术演进

简介&#xff1a; 本文作为混部实践系列开篇&#xff0c;本篇文章将介绍资源隔离技术在混部中的重要性、其落地挑战及我们的应对思路。 作者&#xff1a;钱君、南异 混部顾名思义&#xff0c;就是将不同类型的业务在同一台机器上混合部署起来&#xff0c;让它们共享机器上的 …

探索PCIe 3.0峰值性能,长江存储推新消费级固态硬盘致态TiPlus5000

2022年4月8日&#xff0c;长江存储推出新款消费级固态硬盘产品致态TiPlus5000。该产品采用基于晶栈 2.0&#xff08;Xtacking 2.0&#xff09;架构的长江存储第三代三维闪存芯片&#xff0c;支持PCIe Gen3x4接口、NVMe 1.3协议&#xff0c;顺序读取速度高达3500 MB/s&#xff0…

“敏捷版”全链路压测

简介&#xff1a; PTS 结合 10 多年来阿里的全链路压测的经验&#xff0c;让阿里云的用户可以如同享用满汉全席般的享用全套标准的全链路压测&#xff0c;也可以根据自己的需求&#xff0c;选择最适合自己的方式。 作者&#xff1a;子矜 客户的故事 全链路压测被誉为大促备战…

linux传输tcp命令,Linux tcpdump命令帮助和示例

Tcpdump是用于网络数据包分析的基本命令行实用工具。它显示网络上的TCP/IP和其他传输的网络数据包&#xff0c;tcpdump 适用于大多数的类Unix系统操作系统(如Linux,BSD等)。类Unix系统的 tcpdump 需要使用libpcap这个捕捉数据的库就像 Windows下的WinPcap。Tcpdump使用libpcap库…

离线实时一体化数仓与湖仓一体—云原生大数据平台的持续演进

简介&#xff1a; 阿里云智能研究员 林伟 &#xff1a;阿里巴巴从湖到仓的演进给我们带来了湖仓一体的思考&#xff0c;使得湖的灵活性、数据种类丰富与仓的可成长性和企业级管理得到有机融合&#xff0c;这是阿里巴巴最佳实践的宝贵资产&#xff0c;是大数据的新一代架构。 林…

Kubernetes 入门教程

简介&#xff1a;本文是一篇 kubernetes&#xff08;下文用 k8s 代替&#xff09;的入门文章&#xff0c;将会涉及 k8s 的架构、集群搭建、一个 Redis 的例子&#xff0c;以及如何使用 operator-sdk 开发 operator 的教程。在文章过程中&#xff0c;会穿插引出 Pod、Deployment…

linux格式化usb设备,如何在 usb linux下格式化磁盘

慕田峪7331174以格式化 /dev/sda1 分区为例&#xff1a;$ sudo umount /dev/sda1# 必须先卸载该分区# 格式化为 FAT 分区$ sudo mkfs.vfat -F 32 /dev/sda1# -F 参数必须大写&#xff0c;参数有 12&#xff0c;16 和 32&#xff0c;分别对应 FAT12&#xff0c;FAT16&#xff0c…

通过浪潮AIStation实现细粒度高性能的GPU资源共享

作者 | 张荣国 供稿 | 浪潮 GPU&#xff08;Graphics Processing Unit&#xff09;&#xff0c;即图形处理器&#xff0c;是一种由大量核心组成的大规模并行计算架构&#xff0c;专为同时处理多重任务而设计。GPU在大规模并行运算上具有巨大优势&#xff0c;为大数据、人工智能…

阿里云发布云原生加速器,携手生态企业拥抱数字时代

简介&#xff1a; 继去年推出云原生合作伙伴计划之后&#xff0c;阿里云正式发布云原生加速器&#xff0c;携手生态企业拥抱数字时代。 今天&#xff0c;千行百业都在拥抱云计算、拥抱云原生&#xff0c;进行数字化创新升级。作为国内最早实践云原生的企业&#xff0c;阿里巴巴…

网不好怎么办?TLS握手带宽直降80%,BabaSSL是怎么做到的?| 龙蜥技术

简介&#xff1a; 为了保障数据的安全性&#xff0c;客户端会先和服务器进行 TLS 握手&#xff0c;有什么办法可以减少 TLS 握手的带宽消耗呢&#xff1f; 编者按&#xff1a;BabaSSL 是一款开源的密码库产品&#xff0c;在 GitHub 和龙蜥社区开源&#xff0c;并加入到龙蜥社区…

企业级数据湖实践

简介&#xff1a;2021云栖大会云原生企业级数据湖专场&#xff0c;阿里云智能高级解决方案架构师周皓为我们带来《企业级数据湖最佳实践》的分享。 本文主要分享了数据湖的核心能力及几个最佳实践案例。 以下是精彩视频内容整理 一、统一数据存储&#xff0c;多引擎对接&…

浅述 Docker 的容器编排

作者 | 天元浪子来源 | CSDN博客概述作为容器引擎&#xff0c;Docker为容器化的应用程序提供了开放标准&#xff0c;使得开发者可以用管理应用程序的方式来管理基础架构&#xff0c;实现快速交付、测试和部署代码。随着容器的大量使用&#xff0c;又产生了如何协调、调度和管理…

Cloudera CDP 企业数据云测试开通指导

简介&#xff1a; 基于阿里云部署的 Cloudera CDP 企业数据云平台已经进入公测阶段&#xff0c;本文详细介绍了相关试用/试用流程。 基于阿里云部署的 Cloudera CDP 企业数据云平台已经进入公测阶段&#xff0c;如对该平台感兴趣&#xff0c;可以使用下面的流程进行试用。 如需…

重装linux之后gcc等下载不了,Redhat linux下安装gcc

一、安装步骤1、使用whichgcc命令发现gcc没有安装2、拷贝gcc-3.2.2-5.i386.rpm 尝试安装说明在这之前还需要先装binutils、cpp、glibc-devel这三个包3、拷贝binutils-2.13.90.0.18-9.i386.rpmcpp-3.2.2-5.i386.rpmglibc-devel-2.3.2-11.9.i386.rpm 到install目录分别安装rpm -i…

解决 Serverless 落地困难的关键,是给开发者足够的“安全感”

简介&#xff1a;越来越多的云产品都会向全托管、Serverless 形态演进。当云的产品体系 Serverless 化达到一个临界值&#xff0c;通过函数计算这样的 Serverless 计算服务结合其他 Serverless 形态的云服务&#xff0c;能够完整的实现整个应用时&#xff0c;Serverless 就会变…

如何通过任务调度实现百万规则报警

简介&#xff1a;报警是一个公司的日常需求&#xff0c;常见的形态除了满足运维过程中的基础设施监控报警&#xff08;CPU/内存/磁盘等&#xff09;之外&#xff0c;部分公司也会在应用指标&#xff08;如 QPS、RT 等&#xff09;及业务指标&#xff08;如 GMV/日活 等&#xf…

linux无法关机 grub2,Ubuntu关机卡住无法关机如何解决?

电脑无法关机是最让人头疼的事&#xff0c;在Ubuntu系统中&#xff0c;有时会遇到关机卡住的情况&#xff0c;导致关不了机&#xff0c;遇到这种情况千万不要强制关机&#xff0c;下面小编就教你如何解决这个问题。解决方案&#xff1a;在终端用sudo vi打开/boot/grub/grub.cfg…