Springcloud源码学习笔记1—— Zuul网关原理
源码基于 spring-cloud-netflix-zuul-2.2.6.RELEASE.jar
需要具备SpringMVC源码功底 推荐学习https://www.cnblogs.com/cuzzz/p/16538064.html
零丶概述
Zuul是netflix旗下开源网关,作为微服务系统的网关组件,是微服务请求的前门。微服务网关的作用有点类似于AOP
,将负载均衡
,限流
,熔断
等"横切关注点"都集中于此,避免每一个服务都需要写重复的功能(解决不了的问题,我们就加一层doge)
且微服务网关是统一入口
,系统有多个服务,不能每个服务一个对外地址,用户需要一个统一的系统入口进行操作,故zuul网关是系统的统一入口。
为微服务云平台提供统一的入口是API网关最主要的用途,除此之外,网关还可承担认证授权、访问控制、路由、负载均衡、缓存、日志、限流限额、转换、映射、过滤、熔断、注册、服务编排、API管理、监控、统计分析等等非业务性的功能。
下面是zuul的架构图,结合图我们开始Zuul源码之旅。
一丶ZuulHandlerMapping 处理器映射器
zuul和spring的结合,请求到达服务的时候,tomcat会根据请求路径调用到指定的servlet,这里会调用到DispatcherServlet
,DispatcherServlet
会使用HandlerMapping处理器映射器找到当前请求的处理器Handler,这里便会找到ZuulHandlerMapping
ZuulHandlerMapping是一个HandlerMapping,HandlerMapping称为处理器映射器,这里的处理器表示处理http请求,映射器表示根据请求中的一些特征(一般是url,请求头等)映射到一个处理器。当一个请求交由DispatcherServlet
处理的时候,会首先通过处理器映射器找到合适的处理器
1.DispatcherServlet
根据请求找到合适的处理器
DispatcherServlet
会遍历spring容器中的所有HandlerMapping的实现调用其getHandler方法找到处理器,然后和HandlerInterceptor
一起包装成HandlerExecutionChain
(后续会先调用HandlerInterceptor
的preHandle前置处理,postHandle后置处理,请求处理完毕后调用afterCompletion)
2.ZuulHandlerMapping 发光发热
当一个请求调用到我们的zuul网关的时候,DispatcherServlet
根据请求找到合适的处理器的这一步,会调用到zuul自动配置注入到spring容器中的ZuulHandlerMapping
,那么ZuulHandlerMapping 做了写什么呢
2.1利用RouteLocator
注册Handler
在ZuulHandlerMapping中,根据请求找合适的处理器最终交给lookupHandler
方法,此方法调用registerHandlers
注册请求处理器
这里出现一个新的类RouteLocator
路由定位器,顾名思义就是用来获取路由的方法
这里的路由定位器是CompositeRouteLocator,其内部使用一个集合保存其他的RouteLocator,一般zuul自动配置会为我们注入一个DiscoveryClientRouteLocator(父类是SimpleRouteLocator,会读取配置文件中的路由配置)其基于服务发现(DiscoveryClient#getServices
方法)并且将微服务的serviceId如A注册成路由/A/**
,意味着A开头的请求后续都将路由到微服务A
最终会将这些路由的url注册到一个map当中,map的value是ZuulController
2.2.路径匹配获取请求处理器ZuulController
ZuulHandlerMapping是一个AbstractUrlHandlerMapping,顾名思义会根据url找对应的请求处理器,逻辑如下
这里找到的请求处理器,必然是ZuulController(如果路径和配置,或者服务发现中serviceId匹配上的话)
二丶ZuulController处理请求
1.将zuulController组装成HandlerExecutionChain
HandlerExecutionChain内部使用数组保存拦截器HandlerInterceptor
,在ZuulHandlerMapping找到对应的ZuulController(其实是一个单例对象,都是同一个),会从容器中拿到HandlerInterceptor
类型的bean,和ZuulController包装到一起生成HandlerExecutionChain
2.获取ZuulController匹配的HandlerAdapter
想调用ZuulController?还需要其对应的HandlerAdapter,由于SpringMVC定义了多种处理器(比如RequestMappingHandlerAdapter(我们最常用的,基于@RequestMapping注解,反射调用Controller方法的处理器对应的适配器)SimpleServletHandlerAdapter(适配Servlet),SimpleControllerHandlerAdapter(适配Controller接口对象)),为了屏蔽这种差异springmvc又定义了一个HandlerAdapter——处理器适配器,使用适配器模式,如同一个多功能转接头来适配多种处理器
找到合适处理器适配器的代码如下
这里自然找到的是SimpleControllerHandlerAdapter
随后会执行拦截器HandlerInterceptor#preHandle
方法
3.适配器调用ZuulController处理请求
在拦截器的preHandler执行完成后,接下来会调用ZuulController#handleRequest
,这一步是使用SimpleControllerHandlerAdapter
进行的方法调用,我们看下ZuulController是如何处理请求的
ZuulController
是一个ServletWrappingController
(内部包装servlet),对请求的处理最终交给ZuulServlet
兜兜转转最终还是来到了ZuulServlet,其本质是一个Servlet对象,看来Zuul处理Spring框架下可以用,普通的Servlet应用也可以使用呀
三丶ZuulServlet处理请求
1.ZuulServlet初始化ZuulRunner
在zuulServlet真正处理器请求之前,会初始化一个zuulRunner,ZuulRunner的init方法会被调用,zuul在这里面初始化了一个基于ThreadLocal的请求上下文,ZuulRunner提供了preRoute
,postRoute
,route
,error
方法,顾名思义分别在路由前,路由后,路由,以及发生错误的时候进行调用
2.ZuulServlet处理请求
可以看到ZuulServlet存在三个阶段,preRoute,route,postRoute,如果出现异常那么将调用errorRoute
这些方法的调用都委托给ZuulRunner进行,ZuulRunner会调用FilterProcessor.getInstance()
对应的方法
最终是FilterLoader.getInstance()
负责找到合适的Filter并且排序得到先后顺序之后依次调用。
具体存在哪些Filter做了什么事情,我们在ZuulFilter章节细说
3.ZuulFilter
ZuulFilter实现了Comparable 和 IZuulFilter
实现Comparable ,并且定义了方法filterOrder,在比较方法中,起始是调用filterOrder得到顺序然后进行比较
IZuulFilter 是Zuul定义的过滤器接口(注意不是Servlet规范中的过滤器)
但是据我看到的源码,
FilterLoader
拿到的过滤器都是实现了ZuulFilter的
3.1 preRoute
上面源码我们说到了,对过滤器的调用,最终是委托给FilterLoader,在FilterRegistry中找到对应类型的过滤器,并且进行排序,然后依次调用。下面我们看下pre
类型的过滤器有哪些,都做了什么事情
3.1.1 ServletDetectionFilter
这个过滤器,负责调用ServletRequest#getAttribute
方法判断,请求是否通过DispatcherServlet处理,然后来到ZuulController,接着调用ZuulServlet来到此,判断的依据就是经过DispatcherServlet处理的请求,会被调用DispatcherServlet#setAttribute(常量字符串,WebApplicationContext web环境下spring上下文)
3.1.2 Servlet30WrapperFilter
负责将请求适配成Servlet3.0所规范的样子
使用Servlet30RequestWrapper
包装原有的请求来实现,有点适配器,又有点装饰器设计模式的意思
3.1.3 FormBodyWrapperFilter
如果请求的content-type
中带有application/x-www-form-urlencoded
或multipart/form-data
- application/x-www-form-urlencoded:表示数据发送到服务器之前,所有字符都会进行编码。属于比较常用的编码方式。
- multipart/form-data:指表单数据有多部分构成,既有文本数据,又有文件等二进制数据的意思。
那么FormBodyWrapperFilter 会生效,将对原本请求使用FormBodyRequestWrapper
进行包装,并提取原始请求的contentData、contentLength、contentType赋值到FormBodyRequestWrapper
的属性上
3.1.4 DebugFilter
如果请求中zuul.debug.parameter
参数对应的值是true 那么,将调用RequestContext#setDebugRouting(true)
和RequestContext#setDebugRequest(true)
这两个设置的作用应该是让zuul打印出一些帮助调试的日志信息
3.1.5 PreDecorationFilter
根据提供的RouteLocator
确定路由的位置和方式。还为下游请求设置各种与代理相关的标头。此类运行逻辑分支很多
3.2 route
route类型网关负责,将请求路由到对应的服务,其中最为关键的便是RibbonRoutingFilter
,它基于Ribbon负载均衡
3.2.1 RibbonRoutingFilter
基于Ribbon负载均衡,实现服务调用的过滤器。
构建RibbonCommandContext,就是对下游微服务的请求的上下文数据的封装,会记录目标服务的id,请求方式,请求资源uri,请求头,请求参数,请求体等等(配置中通过sensitiveHeaders指定哪些请求头不透传)
forward是根据serviceId,和微服务中注册的应用,依据负载均衡的策略,挑选一个健康的实例发送请求
- RibbonCommandFactory#Create
其中
FallbackProvider
在调用失败后,将调用fallbackResponse
来进行服务降级RibbonLoadBalancingHttpClient
使用ribbon实现负载均衡的Http客户端- RibbonCommand#execute
其会根据ILoadBalance调用到IRule(ribbon负载均衡策略)选择一个健康的实例,然后使用HttpClient发送请求,包装结果然后返回
setResponse就是在上下文中记录下,forward调用得到的请求结果。
3.2.2 SendForwardFilter
使用RequestDispatcher转发请求的Route ZuulFilter。一般是在配置文件中配置 location:forward.to
或者配置url:forward:xxxx
的时候会生效。会使用RequestDispatcher#forward
来进行转发
3.3 post
3.3.1 SendResponseFilter
负责将路由请求结果写入到HttpServletResponse
中
- 写请求头 调用
HttpServletResponse#addHeader
将请求头加到HttpServletResponse
对象中 - 写请求内容,就是使用流拷贝route阶段的请求结果到
HttpServletResponse
中
3.4 error
3.4.1 SendErrorFilter
如果前面阶段的过滤器出现错误,将调用此过滤器,它负责使用RequestDispatcher
将请求路由到/error
至此我们看完了zuul转发请求,写请求内容到`HttpServletResponse`的流程
接下来将回到 DispatchServlet 中
四丶DispatchServlet收尾工作
接下来便是调用HandlerInterceptor#postHandle
然后调用HandlerInterceptor#afterCompletion
,并推送一个ServletRequestHandledEvent
事件其中记录了请求信息和处理时间等等信息
最后DispatchServlet处理结束,tomcat负责将请求结果返回给调用方。
五丶扩展自己的ZuulFilter
实现ZuulFilter,并将自己的ZuulFilter注册到Spring容器中,可以实现一些自定义操作。
ZuulFilter
需要实现filterType(过滤器类型,字符串,可以选择pre route post)
,filterOrder(决定Filter的顺序,值越大顺序越后)
在公司我见过使用自定义的zuulFilter将Tracer注入到请求中,实现分布式链路日志
Springcloud源码学习笔记1—— Zuul网关原理的更多相关文章
- JDK源码学习笔记——Iterable/Iterator实现原理
Collection接口继承java.lang.Iterable接口,集合类重写了iterator()方法 public interface Iterable<T> { Iterator& ...
- Underscore.js 源码学习笔记(下)
上接 Underscore.js 源码学习笔记(上) === 756 行开始 函数部分. var executeBound = function(sourceFunc, boundFunc, cont ...
- Underscore.js 源码学习笔记(上)
版本 Underscore.js 1.9.1 一共 1693 行.注释我就删了,太长了… 整体是一个 (function() {...}()); 这样的东西,我们应该知道这是一个 IIFE(立即执行 ...
- AXI_LITE源码学习笔记
AXI_LITE源码学习笔记 1. axi_awready信号的产生 准备接收写地址信号 // Implement axi_awready generation // axi_awready is a ...
- Hadoop源码学习笔记(6)——从ls命令一路解剖
Hadoop源码学习笔记(6) ——从ls命令一路解剖 Hadoop几个模块的程序我们大致有了点了解,现在我们得细看一下这个程序是如何处理命令的. 我们就从原头开始,然后一步步追查. 我们先选中ls命 ...
- Hadoop源码学习笔记(5) ——回顾DataNode和NameNode的类结构
Hadoop源码学习笔记(5) ——回顾DataNode和NameNode的类结构 之前我们简要的看过了DataNode的main函数以及整个类的大至,现在结合前面我们研究的线程和RPC,则可以进一步 ...
- Hadoop源码学习笔记(4) ——Socket到RPC调用
Hadoop源码学习笔记(4) ——Socket到RPC调用 Hadoop是一个分布式程序,分布在多台机器上运行,事必会涉及到网络编程.那这里如何让网络编程变得简单.透明的呢? 网络编程中,首先我们要 ...
- Hadoop源码学习笔记(3) ——初览DataNode及学习线程
Hadoop源码学习笔记(3) ——初览DataNode及学习线程 进入了main函数,我们走出了第一步,接下来看看再怎么走: public class DataNode extends Config ...
- Hadoop源码学习笔记(2) ——进入main函数打印包信息
Hadoop源码学习笔记(2) ——进入main函数打印包信息 找到了main函数,也建立了快速启动的方法,然后我们就进去看一看. 进入NameNode和DataNode的主函数后,发现形式差不多: ...
- Hadoop源码学习笔记(1) ——第二季开始——找到Main函数及读一读Configure类
Hadoop源码学习笔记(1) ——找到Main函数及读一读Configure类 前面在第一季中,我们简单地研究了下Hadoop是什么,怎么用.在这开源的大牛作品的诱惑下,接下来我们要研究一下它是如何 ...
随机推荐
- jsp和java的结合使用显示学生信息
package com.zyz; public class Student { private String ID; // 学号 private String name; // 姓名 private ...
- Linux系统文件与启动流程
Linux系统文件与启动流程 /etc初始化系统重要文件 /etc/sysconfig/network-scripts/ifcfg-eth0:网卡配置文件 /etc/resolv.conf:Linux ...
- C# 8.0 添加和增强的功能【基础篇】
.NET Core 3.x和.NET Standard 2.1支持C# 8.0. 一.Readonly 成员 可将 readonly 修饰符应用于结构的成员,来限制成员为不可修改状态.这比在C# 7. ...
- Excel中的VLOOKUP函数
VLOOKUP函数是Excel中的一个纵向查找函数,功能是按列查找,最终返回该列所需查询序列所对应的值. 该函数的语法规则如下: VLOOKUP(lookup_value,table_array,co ...
- 执行xxx.sh脚本的两种方式
因公司测试环境的登录模式有2种,大佬们直接写了个脚本完成一键切换,看了其中的脚本文件,其中出现了send "sh out.sh\r":一直疑惑这里的sh out.sh的意思...查 ...
- kubeedge的云边协同通道
1. CloudHub安全认证流程 2. EdgeHub安全认证流程 3. Edged节点纳管
- 某厂面试:如何优雅使用 SPI 机制
代码不多,文章可能有点长.朋友面试某厂问到的 SPI 机制,联想到自己项目最近写到的 SPI 场景,文章简要描述下 SPI 机制的发展历程 产出背景 因为最近项目中使用分库分表以及数据加密使用到了 S ...
- 关于python统计一个列表中每个元素出现的频率
第一种写法: a = ['h','h','e','a','a'] result = {} for i in a: if i not in result: result[i] = 1 else: res ...
- 如何使用 LinkedHashMap 实现 LRU 缓存?
本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 提问. 大家好,我是小彭. 在上一篇文章里,我们聊到了 HashMap 的实现原理和源码分析,在源码分析的过程中,我 ...
- 兼容IE全版本及所有市面浏览器的网页变黑白处理方式
大家应该有发现最近几天不少网站变成了黑白色,在哀悼日时,很多网站都需要全站变成黑白配色,今天对这个实现的技术做了一些探索性了解,在此进行一个记录分享. 使用的样式部分:下面的css部分想必大家应该都可 ...