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

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

一 前言

最近通过深入学习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调用。

上图是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链的执行,其中的逻辑主要是监控。

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

3 寻找路由规则

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

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

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

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

4 核心过滤器链执行

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

做的事情很简单:

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

因为我的配置里包含了一个添加请求参数的逻辑,所以红线箭头处就是我配置的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网关场景,收益也会很可观。

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

Spring Cloud Gateway一次请求调用源码解析相关推荐

  1. Spring Cloud微服务系列-Eureka Client源码解析(二)

    导语   上一篇博客中介绍了关于Eureka Client源码的基础部分,如果对于基础部分不是很了解的读者可以点击下面的连接进入到源码分析一中,从头开始学习 Spring Cloud微服务系列 Dis ...

  2. Spring Cloud微服务系列-Eureka Client源码解析(一)

    导语   Eureka Client 是为了简化开发人员的开发工作,将很多的Eureka Server交互的工作进行了封装,在使用的时候自动完成,在应用的不同阶段来完成不同的功能实现.下面就来了解一下 ...

  3. Spring Cloud Gateway网关实现短网址生成、解析、转发

    Spring Cloud Gateway网关实现短网址生成.解析.转发 1.概述 2.基础实现 3.路由处理HandlerFunction 4.配置路由 5.测试 1.概述 在一些生成二维码等场景中, ...

  4. spring boot2.x设置session有效时间_Spring 源码解析 Scopes 之 Request 、Session 、Application...

    (给ImportNew加星标,提高Java技能) 转自:开源中国,作者:麦克斯 链接:my.oschina.net/wang5v/blog/3017934 Request.Session.Applic ...

  5. Spring5源码 - 13 Spring事件监听机制_@EventListener源码解析

    文章目录 Pre 概览 开天辟地的时候初始化的处理器 @EventListener EventListenerMethodProcessor afterSingletonsInstantiated 小 ...

  6. axios网络请求框架源码解析

    早期axios0.1.0版本做了对IE浏览器与包含XmlHttpRequest的浏览器的支持.并且做了对请求参数拼接.Json对象序列化等基本功能. 到0.19.0版本时,内部请求已经变为了在Node ...

  7. Spring Bean的生命周期以及IOC源码解析

    IOC源码这一块太多只能讲个大概吧,建议还是去买本Spring IOC源码解析的书来看比较好,我也是自己看源代码以及视频整理的笔记 Bean的生命周期大概可以分为四个阶段,具体的等会再说,先看看IOC ...

  8. Spring Cloud Gateway 之获取请求体的几种方式

    一.直接在全局拦截器中获取 伪代码如下 private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest){Flux& ...

  9. Spring Cloud分布式微服务云架构—源码结构图

    分布式.微服务.云架构 JAVA语言开发.跨平台.高性能.高可用.安全.服务化.模块化.组件化.驱动式开发模式 源码结构 JAVA开发.maven模块化构建.服务独立打包.分布式部署.持续集成.版本控 ...

最新文章

  1. Ubuntu下串口通信之cutecom
  2. 深入解析 Dubbo 3.0 服务端暴露全流程
  3. 100-48微软(运算)
  4. 分布的距离(Distance of Distributions)
  5. 实用Python识别图片上的数字(转载)
  6. pytorch 模型微调
  7. Sourcetree和Bitbucket的使用
  8. 鼠标计算机无法识别,如何解决usb鼠标和键盘无法识别问题
  9. Javase尚硅谷笔记
  10. springboot整合poi解析excel
  11. 理解AsyncTask
  12. 2022年全新数据仓库面试总结大全
  13. receptive field,即感受野
  14. 众安保险 x StarRocks | 全新实时分析能力开启数字化经营新局面
  15. 使用github制作简历
  16. 搜狗输入法繁简体切换
  17. Java中单引号和双引号的区别
  18. 福利 | 春节了,如何向七大姑八大姨解释我的程序员职业?
  19. 基于多普勒效应的动作检测
  20. 微信后台架构浅析--读写扩散技术

热门文章

  1. python调用函数怎么表示_Python---7函数(调用定义函数)
  2. db2嵌套查询效率_db2性能优化
  3. 2019年Java编程开发值得学习的10大技术
  4. 新松机器人发行价_知识创造财富,“机器人第一股”背后的院士是怎样炼成的?...
  5. 【LeetCode笔记】51. N 皇后(DFS、经典题)
  6. mybatis依赖_Spring Boot2 系列教程(二十一)整合 MyBatis
  7. java 格式化字符串_Java入门 - 语言基础 - 14.String类
  8. 电脑按f8无法进入安全模式_自已有电脑的人,都会遇到系统死机问题,教大家实用一招自已解决...
  9. java 2d划线 刷子_月光软件站 - 编程文档 - Java - Java图形设计中,利用Bresenham算法实现直线线型,线宽的控制(NO 2D GRAPHICS)...
  10. android contacts 编辑,如何在Android中的.csv文件中逐行编写contactn...