自研框架(Webx)整合Zuul网关工作总结

news2025/1/2 0:11:22

写在前面,最近被分配了一个技术任务,简单描述为自研框架(类比Spring)整合一个微服务网关,并且能用就行。

有人可能会问,想用微服务网关,不是直接引入zuul或者gateway相关的依赖,然后配置一下不就好了吗?为什么我还会写这篇博客。这里原因主要有两个:

  1. 框架是自研的,虽然底层也是基于Spring构建,但Spring MVC部分完全不一样,执行流程需要研究
  2. 这些微服务网关都是基于Spring Boot的自动装配机制实现的(根据名字其实我们也不难理解,毕竟都是spring-cloud-xxx),而我们的项目还停留在Spring,所以直接引入肯定不能用

于是就有了这次任务,收获还是挺大的,特此用写下这篇博客。



文章目录

    • 一、明确调研中间件范围
    • 二、阅读中间件源码
      • 1、Tomcat
      • 2、Spring MVC
      • 3、Webx
      • 4、Zuul
      • 5、spring-cloud-netflix-zuul
    • 三、确定设计方案
      • 1、Spring Boot结合netflix-zuul请求流程
      • 2、当前项目请求流程
      • 3、初版方案
      • 4、优化方案
    • 四、完成项目编码
      • 1、添加pom依赖
      • 2、使用xml配置Bean(Webx不支持@Bean)
      • 3、编写XxxPreZuulServlet

一、明确调研中间件范围

既然目的是完成类似网关一样的请求转发,那么我们就需要知道被网关请求转发的请求打进来,该请求是如何流转的,然后直接调研对应的技术栈就可以了

1、Tomcat

项目使用原生的Tomcat做为Web容器,所以这块的变化不大。


2、Spring MVC

由于Spring MVC的DispatcherServlet是一个Servlet(Tomcat的扩展点大概率要么Filter、要么Servlet),所以想看看在这里能不能发现点什么(如果要使用gateway就需要去调研Dispatcherhanding)


3、Webx

类比Spring,任务也就是整合WebX和zuul,所以Webx必不可少


4、Zuul

本以为师兄口中的调研,是想让我了解所有的微服务网关中间件,然后选择其中一个。没想到是直接调研zuul的源码实现,直接看zuul的就行(在后来看来,使用zuul做为微服务网关是最符合当前项目技术栈,也是最简单的)





二、阅读中间件源码

1、Tomcat

项目使用Tomcat做为Web容器,好在我之前研究过,有兴趣的小伙伴可以参考我之前的文章。

浅谈Tomcat的启动流程

浅谈Tomcat接收到一个请求后在其内部的执行流程

Tomcat组件架构图梳理

针对Tomcat其实重点放在Filter和Servlet上就行了。



2、Spring MVC

这个大家都不陌生。同样,如果不熟悉的小伙伴可以参考我之前的文章

浅谈SpringMVC源码的DispatcherServlet组件执行流程

我们在使用Spring Boot构建Web项目的时候,其内部都是集成了Spring MVC,所以一个请求都会经过DispatcherServlet,然后经过该组件完成请求的转发,但是它本质上只是一个Servlet,明确这一点很重要。Servlet的调用则由Tomcat决定。研究该组件的初衷是为了获得一些灵感,说不定在什么地方就有可以借鉴的思路(结果是没有)



3、Webx

由于代码太老了,Download Resources的时候提示找不到资源,后来去内部的nexus服务器上看,确实没有源码,于是只能找文章学习了。但是就像我之前说的那样,Tomcat暴露在外的除了一些参数配置,还有就是一些扩展接口,如Filter、Servlet、甚至还有Value,基本这些大差不差,Webx必然是在这些扩展点进行的整合。

于是顺藤摸瓜,终于发现了响应的处理逻辑。Webx在完成请求转发的时候,和SpringMVC不一样,它完成内部业务逻辑的入口不是一个Servlet,而是一个Filter。最开始看到还是有点担心的,毕竟和自己预期的结果有点远,知道入口就好办了。



4、Zuul

最让我意外的还是zuul。前文有说过,由于项目不是Spring Boot,所以没办法用Spring Cloud那一套,于是只能从最原生的地方动刀。可是查看了zuul的源码发现,原生的zuul并不提供什么能力(哪怕有一个请求转发也好呀),他真的就是什么都没有,我不禁陷入沉思,它凭什么呀,希望有一天能明白其中的奥秘。

在原生zuul的代码里,它只定义了一套规范。使用一个抽象类ZuulFilter串连接起了整个链路(就是一个责任链,想到责任链就可以知道原生zuul的核心骨架了)。简单理解为zuul将这些链分为pre、post、error、route四种类型,然后代码借助Groovy的能力,可以热加载对应的ZuulFilter。想具体研究zuul源码的,这里分享下我百度时找到的写的不错的讲解的博客:zuul源码分析-探究原生zuul的工作原理


那么问题来了,既然zuul只有一个模板,那请求转发的动作是谁做的呢?我只能将目光投向了spring-cloud-netflix-zuul。



5、spring-cloud-netflix-zuul

果然,在对应的代码里我找到了他们,同样,为了避免重复造轮子,直接贴图(毕竟这也不是本文的重点)

前面说到,原生zuul里面的ZuulFilter分为四类,这四类ZuulFilter在spring-cloud-netflix-zuul中的实现可以浓缩为下面这张图。具体的执行流程按照原生zuul的逻辑(pre-route-post)

上图来自别人的博客,要看详细源码分析的,可以参考该篇文章SpringCloud源码剖析-Zuul的自动配置和核心Filter详解


至此,代码看完了,接着需要想想如何设计了





三、确定设计方案

1、Spring Boot结合netflix-zuul请求流程

在阅读了上面源码的基础上,我们很容易就可以得到这张流程图。ZuulServlet替代了DispatcherServlet的能力,请求直接调用到ZuulServlet类中,然后完成相关ZuulFilter的责任链调用,这里就会调用到SimpleHostRoutingFilter,该ZuulFilter就是完成远程请求调用的具体实现类,其底层使用的是HttpClient(2.x使用了Netty)


2、当前项目请求流程

研究框架源码发现,当前框架和Tomcat接壤的地方并不是一个Servlet,而是一个Filter,业务代码在该Filter内部完成。


3、初版方案

最开始的设计方案是,我们将未来需要网关转发的请求配置到一个Filter的拦截路径中,并且该拦截器配置在WebxFilter前面,如果当前请求能够匹配到我们的ZuulServletFilter,则说明当前请求是需要网关转发的,那么就可以在内部完成响应的请求转发,即调用到我们的SimpleHostRoutingFilter类,然后远程调用获取结果,最后返回。

这一版是我给出的解决方案,确实我们最开始也是这么做的,并且代码已经实现完成,请求跳转数据获取一切正常。不过后来师兄调整了一版本,想想师兄的确实要优雅一点,尽管换汤不换药(不过这个优化需要基于不同的zuul版本,比如我自己电脑下载1.0.28就没有这个判断,但是1.3.1就有)

我们中间还尝试过想直接借助SendForwardFilter来完成本地请求的转发,后来发现不太行,所以放弃了。

这两个的区别就是如何Request上下文对象的sendZuulResponse属性是false,那么就会结束当前ZuulServletFilter,继续执行其他Filter链,基于此,师兄做了一点小优化


补充:Zuul想的还是比较全面,它既有基于Filter的ZuulServletFilter,也有基于Servlet的ZuulServlet,功能一模一样。由于当前项目的业务处理是基于Filter的,常规的Spring Boot项目是基于Servlet的(自己最开始调试的时候没有注意到这一点,进入了误区,后来师兄在用ZuulServletFilter的时候,自己才恍然大悟)



4、优化方案

同样还是新建一个ZuulServletFilter,此时不再是拦截具体路径,而是拦截所有的路径(ZuulServletFilter的位置一定WebxFilter位置的前面)。与此同时,我们新建一个pre类型的ZuulFilter,用来判断当前请求是否在我们配置的网关转发请求范围内,如果在就继续向下走,如果不在就给对应的属性设置为false,上面有说到,如果属性判断为false,那么就会跳出当前ZuulServletFilter,继而完成Tomcat中的其他Filter,这里其他的Filter就包含了处理当前项目请求接口的Filter(WebxFilter)





四、完成项目编码

前面的设计方案已经确定,那么接下来就是代码落地了。最核心的问题就是:在没有Spring Boot自动装配机制的情况下,如何初始化对象。在我看来这是最麻烦的一步,因为用到的那几个ZuulServletFilter内部像套娃一样,一个Bean又套了另一个Bean,没完没了。当然这一切很大程度是因为自己陷入了一个误区,我总担心差这差那,导致各种判断不通过,然后出现奇奇怪怪的异常。以至于我在找Bean的时候,被它源码牵着鼻子走,它缺一个Bean,我就去看它是什么地方赋值的,怎么赋值的,赋值条件又是什么,结果就是转了一大圈,把自己转晕了,甚至觉得这是个无底洞。

不得不说,前辈就是前辈,秉承着一切从简的原则,师兄在找Bean的时候就是看它能用什么Bean就用什么Bean,哪个Bean需要的参数少,哪个Bean拿着方便就用哪个类型的Bean。这一套操作下来只需要配置11个Bean就配置完成了。我忍不住感叹了一句——确实,师兄只是轻描淡写的来了一句:都是经验。

最终呈现在Git提交记录上的代码只有这三部分:

1、添加pom依赖

<dependency>
    <groupId>com.netflix.zuul</groupId>
    <artifactId>zuul-core</artifactId>
</dependency>
<dependency>
    <groupId>com.netflix.hystrix</groupId>
    <artifactId>hystrix-core</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-context</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-netflix-core</artifactId>
</dependency>
<dependency>
    <groupId>commons-configuration</groupId>
    <artifactId>commons-configuration</artifactId>
</dependency>

2、使用xml配置Bean(Webx不支持@Bean)

<bean id="checkRoute" class="org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute">
    <property name="path" value="/check/**"/>
    <property name="url" value="${zuul_routes_money_url}"/>
    <property name="stripPrefix" value="false"/>
</bean>
<bean id="moneyRoute" class="org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute">
    <property name="path" value="/money/**"/>
    <property name="url" value="${zuul_routes_money_url}"/>
    <property name="stripPrefix" value="false"/>
</bean>

<bean id="zuulRoutes" class="java.util.HashMap">
    <constructor-arg>
        <map key-type="java.lang.String"
             value-type="org.springframework.cloud.netflix.zuul.filters.ZuulProperties.ZuulRoute">
            <entry key="check" value-ref="checkRoute"/>
            <entry key="money" value-ref="moneyRoute"/>
        </map>
    </constructor-arg>
</bean>

<bean id="zuulProperties" class="org.springframework.cloud.netflix.zuul.filters.ZuulProperties">
    <property name="ignoredServices" value="*"/>
    <property name="sensitiveHeaders" value=""/>
    <property name="addHostHeader" value="true"/>
    <property name="routes" ref="zuulRoutes"/>
</bean>

<bean id="proxyRequestHelper" class="org.springframework.cloud.netflix.zuul.filters.ProxyRequestHelper"/>

<bean id="routeLocator" class="org.springframework.cloud.netflix.zuul.filters.SimpleRouteLocator">
    <constructor-arg index="0" value="/"/>
    <constructor-arg index="1" ref="zuulProperties"/>
</bean>

<bean id="preDecorationFilter" class="org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter">
    <constructor-arg index="0" ref="routeLocator"/>
    <constructor-arg index="1" value="/"/>
    <constructor-arg index="2" ref="zuulProperties"/>
    <constructor-arg index="3" ref="proxyRequestHelper"/>
</bean>

<bean id="xxxPreRoutingFilter" class="pers.mobian.web.filter.xxxPreRoutingFilter">
    <constructor-arg index="0" ref="routeLocator"/>
</bean>

<bean id="simpleHostRoutingFilter"
      class="org.springframework.cloud.netflix.zuul.filters.route.SimpleHostRoutingFilter">
    <constructor-arg index="0" ref="proxyRequestHelper"/>
    <constructor-arg index="1" ref="zuulProperties"/>
    <constructor-arg index="2">
        <bean class="org.springframework.cloud.commons.httpclient.DefaultApacheHttpClientConnectionManagerFactory"/>
    </constructor-arg>
    <constructor-arg index="3">
        <bean class="org.springframework.cloud.commons.httpclient.DefaultApacheHttpClientFactory"/>
    </constructor-arg>
</bean>

<bean id="sendResponseFilter" class="org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter">
    <constructor-arg index="0" ref="zuulProperties"/>
</bean>

<bean id="zuulFilters" class="java.util.HashMap">
    <constructor-arg>
        <map key-type="java.lang.String" value-type="com.netflix.zuul.ZuulFilter">
            <entry key="xxxPreRoutingFilter" value-ref="xxxPreRoutingFilter"/>
            <entry key="preRoutingFilter" value-ref="preRoutingFilter"/>
            <entry key="simpleHostRoutingFilter" value-ref="simpleHostRoutingFilter"/>
            <entry key="sendResponseFilter" value-ref="sendResponseFilter"/>
        </map>
    </constructor-arg>
</bean>

<bean id="zuulFilterInitializer" class="org.springframework.cloud.netflix.zuul.ZuulFilterInitializer">
    <constructor-arg index="0" ref="zuulFilters"/>
    <constructor-arg index="1">
        <bean class="org.springframework.cloud.netflix.zuul.metrics.EmptyCounterFactory"/>
    </constructor-arg>
    <constructor-arg index="2">
        <bean class="org.springframework.cloud.netflix.zuul.metrics.EmptyTracerFactory"/>
    </constructor-arg>
    <constructor-arg index="3">
        <bean class="com.netflix.zuul.FilterLoader" factory-method="getInstance"/>
    </constructor-arg>
    <constructor-arg index="4">
        <bean class="com.netflix.zuul.filters.FilterRegistry" factory-method="instance"/>
    </constructor-arg>
</bean>

3、编写XxxPreZuulServlet

public class XxxPreRoutingFilter extends ZuulFilter {

    private final RouteLocator routeLocator;

    private final UrlPathHelper urlPathHelper = new UrlPathHelper();

    public PreRoutingFilter(RouteLocator routeLocator) {
        this.routeLocator = routeLocator;
    }

    @Override
    public String filterType() {
        return PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return PRE_DECORATION_FILTER_ORDER + 1;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        String requestURI = this.urlPathHelper.getPathWithinApplication(ctx.getRequest());
        Route route = this.routeLocator.getMatchingRoute(requestURI);
        if (route == null || route.getLocation() == null) {
            ctx.setRouteHost(null);
            ctx.setSendZuulResponse(false);
        }

        return null;
    }
}



回头看,虽然需要写的代码并不多,但自认为需要研究的东西还是挺多的。好在自己之前简单研究过Tomcat、Spring MVC、Spring Boot的自动装配以及Spring Cloud Gateway的源码,并且涉及zuul部分的源码并不复杂,当然还有最重要的一点是代码都是师兄写的,我就是跟在旁边指指点点打下手(直接一手云开发),所以整个过程还算比较顺利。师兄仰慕值又+1。

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

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

相关文章

【Pandas入门教程】如何合并多个表中的数据

如何合并多个表中的数据 来源&#xff1a;Pandas官网&#xff1a;https://pandas.pydata.org/docs/getting_started/intro_tutorials/index.html 笔记托管&#xff1a;https://gitee.com/DingJiaxiong/machine-learning-study 文章目录如何合并多个表中的数据导包数据准备【1】…

Linux系统基础——文件子系统

title: Linux系统文件子系统 date: 2022-12-18 15:48:24 modify: 2022-12-18 16:48:43 author: wangjianfeng tags: 001-computer-technology, OS, Linux aliases: Linux系统文件子系统 特此说明: 刘超的趣谈linux操作系统是比较重要的参考资料&#xff0c;本文大部分内容和图…

腾讯云轻量应用服务器搭建LAMP 开发环境

LAMP&#xff08;LinuxApacheMySQLPHP&#xff09;是目前国际流行的 Web 应用框架&#xff0c;包括了 Linux 操作系统、Apache Web 服务器、MySQL/MariaDB 数据库和 PHP 编程语言环境以及相关组件支持。 说明 LAMP 应用镜像底层基于 CentOS 7.6 64位操作系统。 登录 轻量应用服…

做一个极简 UI 库之代码 lint

eslint, prettier, stylelint 的配置 这三个规则的配置思路&#xff1a;代码美化用 prettier&#xff0c;逻辑代码用 eslint 校验&#xff0c;样式代码用 stylelint 校验。有跟代码美化冲突的以 prettier 为主 为什么要用这么多呢&#xff0c;因为 eslint 不能解析样式代码&a…

数据结构---LRU算法

LRU算法哈希链表自己的JAVA实现LRU全称Least Recently Used&#xff0c;也就是 最近最少使用的意思&#xff0c;是一种内存管理算法&#xff0c;该算法最早应用于Linux操作系统。这个算法基于一种假设&#xff1a;长期不被使用的数据&#xff0c;在未来被用到的几率也不大。因此…

【LeetCode】1754. 构造字典序最大的合并字符串

构造字典序最大的合并字符串 题目描述 给你两个字符串 word1 和 word2 。你需要按下述方式构造一个新字符串 merge &#xff1a;如果 word1 或 word2 非空&#xff0c;选择 下面选项之一 继续操作&#xff1a; 如果 word1 非空&#xff0c;将 word1 中的第一个字符附加到 mer…

node.js+uni计算机毕设项目基于微信小程序校园生活管理LW(程序+小程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等…

基于形态学处理的不规则形状图像的几何参数统计,包括输出面积,周长,圆度,矩形度,伸长度

up目录 一、理论基础 二、核心程序 三、测试结果 一、理论基础 形态学是图像处理中应用最为广泛的技术之一&#xff0c;主要用于从图像中提取对表达和描绘区域形状有意义的图像分量&#xff0c;使后续的识别工作能够抓住目标对象最为本质的形状特征&#xff0c;如边界和连通…

C#语言实例源码系列-实现文件分割和合并

专栏分享点击跳转>Unity3D特效百例点击跳转>案例项目实战源码点击跳转>游戏脚本-辅助自动化点击跳转>Android控件全解手册 &#x1f449;关于作者 众所周知&#xff0c;人生是一个漫长的流程&#xff0c;不断克服困难&#xff0c;不断反思前进的过程。在这个过程中…

腾讯云轻量应用服务器使用 WooCommerce 应用镜像搭建电商独立站

WooCommerce 是当前很受欢迎的电商独立站建站工具&#xff0c;具备开源、免费、使用简单且功能强大等特点&#xff0c;您可通过该镜像快速搭建基于 WordPress 的电商独立站。该镜像已预装 WordPress&#xff08;包含 WooCommerce 插件&#xff09;、Nginx、MariaDB、PHP 软件。…

数据结构之排序【直接选择排序和堆排序的实现及分析】内含动态演示图

文章目录引言&#xff1a;1.直接选择排序2.堆排序3.直接选择排序和堆排序的测试引言&#xff1a; 感觉今天更冷了&#xff0c;码字更加的不易&#xff0c;所以引言就简单的写一下啦&#xff01;今天我们就来了解一下什么是直接选择排序和堆排序。 1.直接选择排序 时间复杂度…

RabbitMQ 第一天 基础 4 RabbitMQ 的工作模式 4.1 Work queues 工作队列模式

RabbitMQ 【黑马程序员RabbitMQ全套教程&#xff0c;rabbitmq消息中间件到实战】 文章目录RabbitMQ第一天 基础4 RabbitMQ 的工作模式4.1 Work queues 工作队列模式4.1.1 模式说明4.1.2 代码编写4.1.3 小结第一天 基础 4 RabbitMQ 的工作模式 4.1 Work queues 工作队列模式 …

ELK第四讲之【docker安装Logstash8.4.3、集成springboot】

docker安装elasticsearch8.4.3 docker安装kibana8.4.3 一、docker安装logstash8.4.3 官方地址 https://github.com/elastic/logstash/releases 1、拉取镜像 docker pull elastic/logstash:8.4.3 2、启动容器 docker run -it -d --name logstash -p 9600:9600 -p 5044:…

十六、Docker Compose容器编排第一篇

1、概述 Compose 是一个用于定义和运行多容器 Docker 应用程序的工具。使用 Compose&#xff0c;您可以使用 YAML 文件来配置应用程序的服务。然后&#xff0c;使用一个命令&#xff0c;您可以从您的配置中创建并启动所有服务。 Compose 适用于所有环境&#xff1a;生产、暂存、…

node.js+uni计算机毕设项目高校自习室座位网上预约小程序(程序+小程序+LW)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程。欢迎交流 项目运行 环境配置&#xff1a; Node.js Vscode Mysql5.7 HBuilderXNavicat11VueExpress。 项目技术&#xff1a; Express框架 Node.js Vue 等等组成&#xff0c;B/S模式 Vscode管理前后端分离等…

获取淘宝价格区间l-r的商品a的详细信息(商品名等)

看了一眼&#xff0c;上次更新距今2个月&#xff0c;看起来我好咕咕啊&#xff08;感叹&#xff09;&#xff0c;可是感觉这两个月也没闲着捏&#xff08;比赛&#xff0c;cf&#xff0c;期末等等&#xff0c;幸亏期末考延期了&#xff0c;我这被期末作业都整死了快&#xff09…

SpringBoot+Vue项目艺术摄影预约系统设计与实现

文末获取源码 开发语言&#xff1a;Java 使用框架&#xff1a;spring boot 前端技术&#xff1a;JavaScript、Vue.js 、css3 开发工具&#xff1a;IDEA/MyEclipse/Eclipse、Visual Studio Code 数据库&#xff1a;MySQL 5.7/8.0 数据库管理工具&#xff1a;phpstudy/Navicat JD…

Python pandas有几千个库函数,你用过几个?(2)

上一篇链接&#xff1a; Python pandas有几千个库函数&#xff0c;你用过几个&#xff1f;&#xff08;1&#xff09;_Hann Yang的博客-CSDN博客 I~Q&#xff1a; Function10~25 Types[Function][9:25] [infer_freq, interval_range, isna, isnull, json_normalize, lreshap…

微信HOOK 协议接口 实战开发篇 1.登录

使用HOOK也有不短的时间&#xff0c;写的各类接口杂乱无章 于是便有了将所有接口重构&#xff0c;整理一下的想法 顺手将整理的要点作为日志记录下来 预计每类接口写一篇日志&#xff0c;本次使用的是2022.12.24&#xff0c;当前微信最新版3.8.1.26版 言归正传&#xff0c;开始…

【秋招总结】双非本小菜鸡的坎坷秋招之路(附面经)

前言 因为大环境的影响&#xff0c;今年秋招hc骤缩&#xff0c;导致竞争的激烈程度比往年高了不少。 在秋招的时候&#xff0c;经历过简历石沉大海的无奈&#xff0c;也体验过人家收割offer而自己却依旧0offer的焦虑&#xff0c;不过好在最终也拿到了还算满意的结果。 如今我…