SpringCloud 网关组件 Zuul-1.0 原理深度解析

news/2024/5/11 19:04:47/文章来源:https://blog.csdn.net/BASK2311/article/details/128426203

为什么要使用网关?

在当下流行的微服务架构中,面对多端应用时我们往往会做前后端分离:如前端分成 APP 端、网页端、小程序端等,使用 Vue 等流行的前端框架交给前端团队负责实现;后端拆分成若干微服务,分别交给不同的后端团队负责实现。

不同的微服务一般会有不同的服务地址,客户端在访问这些地址的时候,需要记录几十甚至几百个地址,客户端会请求多个不同的服务,需要维护不同的请求地址,增加开发难度。而且这样的机制会增加身份认证的难度,每个微服务需要独立认证,微服务网关就应运而生。

微服务网关 介于客户端与服务器之间的中间层,是系统对外的唯一入口:所有的外部请求都会先经过微服务网关,客户端只需要与网关交互,只知道一个网关地址即可。

网关是 SpringCloud 生态体系中的基础组件之一,它的主流实现方案有两个:

  1. Spring Cloud Netflix Zuul
  2. Spring Cloud Gateway

两者的主要作用都是一样的,都是代理和路由,本文主要聚焦于 Spring Cloud Netflix Zuul。

1. Zuul 网关简介

Zuul 是 Spring Cloud 中的微服务网关,是为微服务架构中的服务提供了统一的访问入口。 Zuul 本质上是一个Web servlet应用,为微服务架构中的服务提供了统一的访问入口,客户端通过 API 网关访问相关服务。

Zuul 网关的作用

网关在整个微服务的系统中角色是非常重要的,网关的作用非常多,比如路由、限流、降级、安全控制、服务聚合等。

  • 统一入口:唯一的入口,网关起到外部和内部隔离的作用,保障了后台服务的安全性;
  • 身份验证和安全性:对需要身份验证的资源进行过滤,拒绝处理不符合身份认证的请求;
  • 动态路由:动态的将请求路由到不同的后端集群中;
  • 负载均衡:设置每种请求的处理能力,删除那些超出限制的请求;
  • 静态响应处理:提供静态的过滤器,直接响应一些请求,而不将它们转发到集群内部;
  • 减少客户端与服务端的耦合:服务可以独立发展,通过网关层来做映射。

2. Zuul 架构总览

整体架构上可以分为两个部分,即 Zuul CoreSpring Cloud Netflix Zuul

其中 Zuul Core 部分即 Zuul 的核心,负责网关核心流程的实现;Spring Cloud Netflix Zuul 负责包装Zuul Core,其中包括 Zuul 服务的初始化、过滤器的加载、路由过滤器的实现等。

3. Zuul 工作原理

  1. 容器启动时,Spring Cloud 初始化 Zuul 核心组件,如 ZuulServlet、过滤器等。
  2. ZuulServlet 处理外部请求:
    • 初始化 RequestContext;
    • ZuulRunner 发起执行 Pre 过滤器,并最终通过 FilterProcessor 执行;
    • ZuulRunner 发起执行 Route 过滤器,并最终通过 FilterProcessor 执行;
    • ZuulRunner 发起执行 Post 过滤器,并最终通过 FilterProcessor 执行;
    • 返回 Http Response。

Zuul 初始化过程

Spring Cloud Netflix Zuul中初始化网关服务有两种方式: @EnableZuulServer@EnableZuulProxy

这两种方式都可以启动网关服务,不同的主要地方是:

  1. @EnableZuulProxy 是 @EnableZuulServer 的超集,即使用 @EnableZuulProxy 加载的组件除了包含使用 @EnableZuulServer 加载的组件外,还增加了其他组件和功能;
  2. @EnableZuulServer 是纯净版的网关服务,不具备代理功能,只实现了简单的请求转发、响应等基本功能,需要自行添加需要的组件;
  3. @EnableZuulProxy 在 @EnableZuulServer 的基础上实现了代理功能,并可以通过服务发现来路由服务。

如图所示,@EnableZuulServer 和 @EnableZuulProxy 的初始化过程一致,最大的区别在于加载的过滤器不同。其中蓝色是 @EnableZuulServer 加载的过滤器;红色是 @EnableZuulProxy 额外添加的过滤器。

Zuul 初始化源码分析

在程序的启动类加上 @EnableZuulProxy:

@EnableCircuitBreaker
@EnableDiscoveryClient
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulProxyConfiguration.class)
public @interface EnableZuulProxy {
}
复制代码

引用了 ZuulProxyConfiguration,跟踪 ZuulProxyConfiguration,该类注入了 DiscoveryClient、RibbonCommandFactoryConfiguration 用作负载均衡相关的。注入了一些列的 filters,比如 PreDecorationFilter、RibbonRoutingFilter、SimpleHostRoutingFilter,代码如如下:

ZuulProxyConfiguration.java

    @Beanpublic PreDecorationFilter preDecorationFilter(RouteLocator routeLocator, ProxyRequestHelper proxyRequestHelper) {return new PreDecorationFilter(routeLocator, this.server.getServletPrefix(), this.zuulProperties, proxyRequestHelper);}@Beanpublic RibbonRoutingFilter ribbonRoutingFilter(ProxyRequestHelper helper, RibbonCommandFactory<?> ribbonCommandFactory) {RibbonRoutingFilter filter = new RibbonRoutingFilter(helper, ribbonCommandFactory, this.requestCustomizers);return filter;}@Beanpublic SimpleHostRoutingFilter simpleHostRoutingFilter(ProxyRequestHelper helper, ZuulProperties zuulProperties) {return new SimpleHostRoutingFilter(helper, zuulProperties);}
复制代码

父类 ZuulConfiguration ,引用了一些相关的配置。在缺失 zuulServlet bean 的情况下注入了 ZuulServlet,该类是 zuul 的核心类。

ZuulConfiguration.java

    @Bean@ConditionalOnMissingBean(name = "zuulServlet")public ServletRegistrationBean zuulServlet() {ServletRegistrationBean servlet = new ServletRegistrationBean(new ZuulServlet(),this.zuulProperties.getServletPattern());// The whole point of exposing this servlet is to provide a route that doesn't// buffer requests.servlet.addInitParameter("buffer-requests", "false");return servlet;}
复制代码

同时也注入了其他的过滤器,比如 ServletDetectionFilter、DebugFilter、Servlet30WrapperFilter,这些过滤器都是 pre 类型 的。

    @Beanpublic ServletDetectionFilter servletDetectionFilter() {return new ServletDetectionFilter();}@Beanpublic FormBodyWrapperFilter formBodyWrapperFilter() {return new FormBodyWrapperFilter();}@Beanpublic DebugFilter debugFilter() {return new DebugFilter();}@Beanpublic Servlet30WrapperFilter servlet30WrapperFilter() {return new Servlet30WrapperFilter();}
复制代码

同时还注入了 post 类型 的,比如 SendResponseFilter,error 类型,比如 SendErrorFilter,route 类型比如 SendForwardFilter,代码如下:

    @Beanpublic SendResponseFilter sendResponseFilter() {return new SendResponseFilter();}@Beanpublic SendErrorFilter sendErrorFilter() {return new SendErrorFilter();}@Beanpublic SendForwardFilter sendForwardFilter() {return new SendForwardFilter();}
复制代码

初始化 ZuulFilterInitializer 类,将所有的 filter 向 FilterRegistry 注册:

    @Configurationprotected static class ZuulFilterConfiguration {@Autowiredprivate Map<String, ZuulFilter> filters;@Beanpublic ZuulFilterInitializer zuulFilterInitializer(CounterFactory counterFactory, TracerFactory tracerFactory) {FilterLoader filterLoader = FilterLoader.getInstance();FilterRegistry filterRegistry = FilterRegistry.instance();return new ZuulFilterInitializer(this.filters, counterFactory, tracerFactory, filterLoader, filterRegistry);}}
复制代码

FilterRegistry 管理了一个 ConcurrentHashMap,用作存储过滤器的,并有一些基本的 CURD 过滤器的方法,代码如下:

FilterRegistry.java

 public class FilterRegistry {private static final FilterRegistry INSTANCE = new FilterRegistry();public static final FilterRegistry instance() {return INSTANCE;}private final ConcurrentHashMap<String, ZuulFilter> filters = new ConcurrentHashMap<String, ZuulFilter>();private FilterRegistry() {}public ZuulFilter remove(String key) {return this.filters.remove(key);}public ZuulFilter get(String key) {return this.filters.get(key);}public void put(String key, ZuulFilter filter) {this.filters.putIfAbsent(key, filter);}public int size() {return this.filters.size();}public Collection<ZuulFilter> getAllFilters() {return this.filters.values();}}
复制代码

FilterLoader 类持有 FilterRegistry,FilterFileManager 类持有 FilterLoader,所以最终是由FilterFileManager 注入 filterFilterRegistry 的 ConcurrentHashMa p的。FilterFileManager 到开启了轮询机制,定时的去加载过滤器,代码如下:

FilterFileManager.java

  void startPoller() {poller = new Thread("GroovyFilterFileManagerPoller") {public void run() {while (bRunning) {try {sleep(pollingIntervalSeconds * 1000);manageFiles();} catch (Exception e) {e.printStackTrace();}}}};poller.setDaemon(true);poller.start();}
复制代码

Zuul 请求处理过程

  1. 初始化 RequestContext;
  2. ZuulRunner 发起执行 Pre 过滤器,并最终通过 FilterProcessor 执行;
  3. ZuulRunner 发起执行 Route 过滤器,并最终通过 FilterProcessor 执行;
  4. ZuulRunner 发起执行 Post 过滤器,并最终通过 FilterProcessor 执行;
  5. 返回 Http Response。

Zuul 默认注入的过滤器,它们的执行顺序在 FilterConstants 类,我们可以先定位在这个类,然后再看这个类的过滤器的执行顺序以及相关的注释,可以很轻松定位到相关的过滤器。

过滤器顺序描述类型
ServletDetectionFilter-3检测请求是用 DispatcherServlet 还是 ZuulServletpre
Servlet30WrapperFilter-2在 Servlet 3.0 下,包装 requestspre
FormBodyWrapperFilter-1解析表单数据pre
SendErrorFilter0如果中途出现错误error
DebugFilter1设置请求过程是否开启 debugpre
PreDecorationFilter5根据 uri 决定调用哪一个 route 过滤器pre
RibbonRoutingFilter10如果写配置的时候用 ServiceId 则用这个 route 过滤器,该过滤器可以用Ribbon 做负载均衡,用hystrix做熔断route
SimpleHostRoutingFilter100如果写配置的时候用 url 则用这个 route 过滤route
SendForwardFilter500用 RequestDispatcher 请求转发route
SendResponseFilter1000用 RequestDispatcher 请求转发post

过滤器的 order 值越小,就越先执行。并且在执行过滤器的过程中,它们 共享了一个 RequestContext 对象,该对象的生命周期贯穿于请求。

可以看出优先执行了 pre 类型的过滤器,并将执行后的结果放在 RequestContext 中,供后续的 filter 使用,比如在执行 PreDecorationFilter 的时候,决定使用哪一个 route,它的结果的是放在 RequestContext 对象中,后续会执行所有的 route 的过滤器,如果不满足条件就不执行该过滤器的 run() 方法,最终达到了就执行一个 route 过滤器的 run() 方法。

  • error 类型的过滤器,是在程序发生异常的时候执行的。
  • post 类型的过滤,在默认的情况下,只注入了 SendResponseFilter,该类型的过滤器是将最终的请求结果以流的形式输出给客户端。

Zuul 请求处理源码分析

Zuulservlet 作为类似于 Spring MVC 中的 DispatchServlet,起到了前端控制器的作用,所有的请求都由它接管。它的核心代码如下:

Zuulservlet.java

 @Overridepublic void service(javax.servlet.ServletRequest servletRequest, javax.servlet.ServletResponse servletResponse) throws ServletException, IOException {try {init((HttpServletRequest) servletRequest, (HttpServletResponse) servletResponse);// Marks this request as having passed through the "Zuul engine", as opposed to servlets// explicitly bound in web.xml, for which requests will not have the same data attachedRequestContext context = RequestContext.getCurrentContext();context.setZuulEngineRan();try {preRoute();} catch (ZuulException e) {error(e);postRoute();return;}try {route();} catch (ZuulException e) {error(e);postRoute();return;}try {postRoute();} catch (ZuulException e) {error(e);return;}} catch (Throwable e) {error(new ZuulException(e, 500, "UNHANDLED_EXCEPTION_" + e.getClass().getName()));} finally {RequestContext.getCurrentContext().unset();}}复制代码

跟踪 init() 方法,可以发现这个方法 init() 为每个请求生成了 RequestContext(底层使用 ThreadLocal 保存数据),RequestContext 继承了 ConcurrentHashMap:

public void init(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {RequestContext ctx = RequestContext.getCurrentContext();if (bufferRequests) {ctx.setRequest(new HttpServletRequestWrapper(servletRequest));} else {ctx.setRequest(servletRequest);}ctx.setResponse(new HttpServletResponseWrapper(servletResponse));}public void preRoute() throws ZuulException {FilterProcessor.getInstance().preRoute();}复制代码

FilterProcessor 类为调用 filters 的类,比如调用 pre 类型所有的过滤器,route、post 类型的过滤器的执行过程和 pre 执行过程类似:

FilterProcessor.java

  public void preRoute() throws ZuulException {try {runFilters("pre");} catch (ZuulException e) {throw e;} catch (Throwable e) {throw new ZuulException(e, 500, "UNCAUGHT_EXCEPTION_IN_PRE_FILTER_" + e.getClass().getName());}}
复制代码

跟踪 runFilters() 方法,可以发现,它最终调用了 FilterLoader 的 getFiltersByType(sType) 方法来获取同一类的过滤器,然后用 for 循环遍历所有的 ZuulFilter,执行了 processZuulFilter() 方法,跟踪该方法可以发现最终是执行了 ZuulFilter 的方法,最终返回了该方法返回的 Object 对象:

    public Object runFilters(String sType) throws Throwable {if (RequestContext.getCurrentContext().debugRouting()) {Debug.addRoutingDebug("Invoking {" + sType + "} type filters");}boolean bResult = false;List<ZuulFilter> list = FilterLoader.getInstance().getFiltersByType(sType);if (list != null) {for (int i = 0; i < list.size(); i++) {ZuulFilter zuulFilter = list.get(i);Object result = processZuulFilter(zuulFilter);if (result != null && result instanceof Boolean) {bResult |= ((Boolean) result);}}}return bResult;}
复制代码

SimpleHostRoutingFilter

现在来看一下 SimpleHostRoutingFilter 是如何工作的。进入到 SimpleHostRoutingFilter 类的 run() 方法,核心代码如下:

    @Overridepublic Object run() {RequestContext context = RequestContext.getCurrentContext();// 省略代码String uri = this.helper.buildZuulRequestURI(request);this.helper.addIgnoredHeaders();try {CloseableHttpResponse response = forward(this.httpClient, verb, uri, request,headers, params, requestEntity);setResponse(response);}catch (Exception ex) {throw new ZuulRuntimeException(ex);}return null;}
复制代码

查阅这个类的全部代码可知,该类创建了一个 HttpClient 作为请求类,并重构了 url,请求到了具体的服务,得到的一个 CloseableHttpResponse 对象,并将 CloseableHttpResponse 对象的保存到 RequestContext 对象中。并调用了 ProxyRequestHelper 的 setResponse 方法,将请求状态码,流等信息保存在 RequestContext 对象中。

    private void setResponse(HttpResponse response) throws IOException {RequestContext.getCurrentContext().set("zuulResponse", response);this.helper.setResponse(response.getStatusLine().getStatusCode(),response.getEntity() == null ? null : response.getEntity().getContent(),revertHeaders(response.getAllHeaders()));}
复制代码

SendResponseFilter

这个过滤器的 order 为 1000,在默认且正常的情况下,是最后一个执行的过滤器,该过滤器是最终将得到的数据返回给客户端的请求。在它的 run() 方法里,有两个方法:addResponseHeaders() 和 writeResponse(),即添加响应头和写入响应数据流。

    public Object run() {try {addResponseHeaders();writeResponse();}catch (Exception ex) {ReflectionUtils.rethrowRuntimeException(ex);}return null;}
复制代码

其中 writeResponse() 方法是通过从 RequestContext 中获取 ResponseBody 获或者 ResponseDataStream 来写入到 HttpServletResponse 中的,但是在默认的情况下 ResponseBody 为 null,而 ResponseDataStream 在 route 类型过滤器中已经设置进去了。具体代码如下:

private void writeResponse() throws Exception {RequestContext context = RequestContext.getCurrentContext();HttpServletResponse servletResponse = context.getResponse();// 省略代码OutputStream outStream = servletResponse.getOutputStream();InputStream is = null;try {if (RequestContext.getCurrentContext().getResponseBody() != null) {String body = RequestContext.getCurrentContext().getResponseBody();writeResponse(new ByteArrayInputStream(body.getBytes(servletResponse.getCharacterEncoding())),outStream);return;}// 省略代码is = context.getResponseDataStream();InputStream inputStream = is;// 省略代码writeResponse(inputStream, outStream);// 省略代码}}// 省略代码}
复制代码

4. Zuul-2.0 和 Zuul-1.0 对比

Zuul1.0 设计比较简单,代码很少也比较容易读懂,它本质上就是一个同步 Servlet,采用多线程阻塞模型。 Zuul2.0 的设计相对比较复杂,代码也不太容易读懂,它采用了 Netty 实现异步非阻塞编程模型。比较明确的是,Zuul2.0 在链接数方面表现要好于 Zuul1.0,也就是说 Zuul2.0 能接受更多的链接数。

Netflix 给出了一个比较模糊的数据,大体 Zuul2.0 的性能比 Zuul1.0 好 20% 左右,这里的性能主要指每节点每秒处理的请求数。为何说模糊呢?由于这个数据受实际测试环境,流量场景模式等众多因素影响,你很难复现这个测试数据。即使这个 20% 的性能提高是确实的,其实这个性能提高也并不大,和异步引入的复杂性相比,这 20 %的提高是否值得是个问题。

两者架构上的差异

Zuul2.0 的架构,和 Zuul1.0 没有本质区别,两点变化:

  • 前端用 Netty Server 代替 Servlet,目的是支持前端异步。后端用 Netty Client 代替 Http Client,目的是支持后端异步。
  • 过滤器换了一下名字,用 Inbound Filters 代替 Pre-routing Filters,用 Endpoint Filter 代替Routing Filter,用 Outbound Filters 代替 Post-routing Filters

线上环境使用建议

  1. 同步异步各有利弊,同步多线程编程模型简单,但会有线程开销和阻塞问题,异步非阻塞模式线程少并发高,可是编程模型变得复杂。
  2. 架构师作技术选型须要严谨务实,具有批判性思惟 (Critical Thinking),即便是对于一线大公司推出的开源产品,也要批判性看待,不可盲目追新。
  3. 我的 建议生产环境继续使用 Zuul1.0,同步阻塞模式的一些不足,可使用熔断组件 Hystrix 和AsyncServlet 等技术进行优化。

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

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

相关文章

独立开发变现周刊(第85期):一个会员服务的SaaS,月收入2万美金

分享独立开发、产品变现相关内容&#xff0c;每周五发布。目录1、Obsidian Canvas&#xff1a;一个无限的空间来构建你的想法2、message-pusher: 搭建专属于你的消息推送服务3、Careerflow LinkedIn: 40倍提升你的工作机会4、vue-pure-admin: 一款开源后台管理系统5、一个提供会…

【HarmonyOS】调测助手安装失败10内部错误

关于鸿蒙开发通过应用调测助手向watch gt 3 手表安装hap时报错。 问题背景&#xff1a; 鸿蒙开发&#xff0c;使用新建工程的helloworld 没有其他修改&#xff0c;生成hap包。然后通过应用调测助手向watch gt 3 手表安装hap时提示 安装失败:10.内部错误。 Sdk&#xff1a; a…

基于VUE学生选课管理系统

开发工具(eclipse/idea/vscode等)&#xff1a;idea 数据库(sqlite/mysql/sqlserver等)&#xff1a;mysql 功能模块(请用文字描述&#xff0c;至少200字)&#xff1a; 一、登录注册模块: 1.学生&#xff0c;教师&#xff0c;管理员三个角色&#xff08;同一时刻&#xff0c;账户…

WSL2的安装、应用

WSL2的安装、应用WSL安装、升级常用命令WSL导入导出其他 - 图形界面、虚拟化WSL安装、升级 win10系统上开启WSL参考如下&#xff0c;我先是安装了WSL1&#xff0c;之后又升级到WSL2的。关键是一些Win10上电配置&#xff0c;之后在windows应用商店下载ubuntu即可。 win10上lin…

Python基础(十八):学员管理系统应用

文章目录 学员管理系统应用 一、系统简介 二、步骤分析 三、需求实现 1、显示功能界面 2、用户输入序号&#xff0c;选择功能 3、根据用户选择&#xff0c;执行不同的功能 4、定义不同功能的函数 学员管理系统应用 一、系统简介 需求&#xff1a;进入系统显示系统功能…

跨域问题以及解决跨域问题的vue-cli解决方案

跨域问题 写项目前要问后端,接口支持跨域吗? 支持就不会出现问题,不支持就需要解决跨域问题 1.如何判断一个浏览器的请求是否跨域&#xff1f; 在A地址&#xff08;发起请求的页面地址&#xff09;向B地址&#xff08;要请求的目标页面地址&#xff09;发起请求时&#xff…

Java环境配置——Linux 安装JDK

注意这是用普通用户登录后&#xff0c;单独设置用户的java环境变量&#xff0c;非root用户 root用户的编辑命令是 vi /etc/profile 下载安装包 创建java目录 mkdir java 进入目录 cd java 上传安装包 将jdk-8u161-linux-x64.tar.gz上传到java目录 配置环境变量 解压安…

leetcode——155. 最小栈

leetcode——155. 最小栈&#x1f50d;题目详情&#x1f914;解题思路&#x1f4bb;代码实现&#x1f4ac;总结&#x1f440;先看这里&#x1f448; &#x1f600;作者&#xff1a;江不平 &#x1f4d6;博客&#xff1a;江不平的博客 &#x1f4d5;学如逆水行舟&#xff0c;不进…

【信管5.2】估算活动资源与持续时间

估算活动资源与持续时间在经过上次课程的学习后&#xff0c;我们已经了解到了进度、活动的概念及定义&#xff0c;并且简单地学习了下活动顺序如何排列的一些工具技术。今天&#xff0c;我们学习的主要方向是估算活动资源与估算活动持续时间这两个过程&#xff0c;另外我们还会…

WMS类图分析-android12

为什么要分析类图&#xff1f; WMS是一个复杂的模块&#xff0c;就像一个很大的家族&#xff0c;里面有各种角色&#xff0c;认识类图就像是认识WMS模块中的各个角色&#xff0c;不先把人认清楚了&#xff0c;怎么更好的理解他们之间的交互&#xff1f; 我觉得&#xff0c;这…

达梦数据IPO过会:拟募资24亿 光谷“扫地僧”冯裕才将敲钟

雷递网 雷建平 12月23日武汉达梦数据库股份有限公司&#xff08;简称&#xff1a;“达梦数据”&#xff09;日前IPO过会&#xff0c;准备在科创板上市。达梦数据计划募资23.51亿元。其中&#xff0c;3.52亿元用于集群数据库管理系统升级项目&#xff0c;3.43亿元用于高性能分布…

pytorch 多卡运行详细教程

先说明一下背景&#xff0c;目前正在魔改以下这篇论文的代码&#xff1a; https://github.com/QipengGuo/GraphWriter-DGLgithub.com 由于每次完成实验需要5个小时&#xff08;baseline&#xff09;&#xff0c;自己的模型需要更久&#xff08;2倍&#xff09;&#xff0c;非…

2022星空创造营应用创新大赛圆满落幕,获奖名单出炉!

​12月22日&#xff0c;2022星空创造营应用创新大赛在2022手机创新周暨第十届手机设计大赛颁奖典礼上作为特别专场正式公布获奖名单。2022星空创造营应用创新大赛由联通在线、手机设计大赛天鹅奖组委会联合主办&#xff0c;联通在线音乐公司及工信部赛迪研究院共同承办&#xf…

小学生C++编程基础 课程10

938.最小公倍数的简单方法 &#xff08;课程A&#xff09; 难度&#xff1a;1 登录 939.最大公约数的简单方法 ( 课程A&#xff09; 难度&#xff1a;1 登录 940.韩信点兵 &#xff08;课程A&#xff09; 难度&#xff1a;1 登录 941.求123…N的和 &#xff08;课程A&#x…

Spring MVC【返回数据与请求转发和重定向】

Spring MVC【返回数据与请求转发和重定向】&#x1f34e;一. 返回数据&#x1f352;1.1 返回静态页面&#x1f352;1.2 返回一个非静态页面&#x1f352;1.3 返回text/html类型页面&#x1f352;1.4 返回JSON对象&#x1f352;1.5 实现计算器功能&#x1f352;1.6 使用ajax方式…

【算法】P1 算法简介

算法什么是算法正确与错误的算法算法可以解决什么问题本专栏有哪些算法什么是算法 算法 (Algorithm) 取某个值或集合作为 输入&#xff0c;并产生某个值或集合作为 输出。算法就是把输入转换为输出的计算&#xff0c;描述这个计算的过程来实现输入与输出的关系。 正确与错误的…

股票量化分析工具QTYX使用攻略——实盘交易信号监控(更新2.5.7)

搭建自己的量化系统如果要长期在市场中立于不败之地&#xff01;必须要形成一套自己的交易系统。如何学会搭建自己的量化交易系统&#xff1f;边学习边实战&#xff0c;在实战中学习才是最有效地方式。于是我们分享一个即可以用于学习&#xff0c;也可以用于实战炒股分析的量化…

插值算法基本原理

插值&#xff1a;数据处理的手段 将缺失数据补全处理 线性内插 拉格朗日插值法 牛顿插值 拟合&#xff1a;预测&#xff0c;寻找规律的手段 是插值的外延 插值算法&#xff1a;使用在现有的数据极少&#xff0c;不足以支撑分析的进行&#xff0c;这时就需要使用一些数学方法…

【Vue】Vue重写教室管理系统的前端网页V1(前后端分离)--20221222

项目说明 目的 练习并熟悉Vue2 的API&#xff0c;来为Vue项目做准备&#xff1a; 插值语法插槽props和data父子组件通信Ajax异步请求数据生命周期函数methods方法computed属性vue-router、路由守卫、query/params传参、编程函数式路由模拟后端服务器传送数据打包项目 需要加…

Python正在消亡?致命弱点是否会让Python被新语言取代?

被业界称为“瑞士军刀”的编程语言&#xff0c;可能会被更适合该任务的其他语言取代吗&#xff1f; 自从1990年代初Python发布以来&#xff0c;它引起了很多热议。当然&#xff0c;编程社区花了至少20年的时间才逐渐注意到它的存在&#xff0c;而当它一旦开始流行起来&#xf…