tomcat架构设计分析,核心组件详解

news2024/11/15 17:27:59

提示:tomcat整体架构分析,tomcat核心组件详解、tomcat请求全流程、tomcat设计模式分析。责任链模式设计、tomcat设计详解、tomcat调优的前置文档

文章目录

  • 前言
  • 一、相关概念
    • 1、tomcat的概念
    • 2、web应用部署的3种方式
  • 二、tomcat的整体架构
    • 1、tomcat架构图
    • 2、tomcat核心组件详解
      • 2.1、server组件
      • 2.2、service组件
      • 2.3、连接器Connector组件
      • 2.4、容器Container组件
    • 3、请求定位Servlet的过程
  • 三、Tomcat架构设计精髓分析
    • 1、Connector高内聚低耦合设计
      • 1.1、ProtocolHandler
      • 1.2、EndPoint
      • 1.3、Processpr
      • 1.4、Adapter
    • 2、设计复杂系统的基本思路
    • 3、父子同期组合模式设计
    • 4、PipeLine-Valve责任链模式设计
      • 4.1、PipeLine-Vavle责任链模式
      • 4.2、为什么要使用管道机制?
      • 4.3、Vavle接口设计
      • 4.4、Valve和Filter的区别:
    • 5、Tomcat生命周期设计
      • 5.1、一键式启停: LifetCycle接口
      • 5.2、可扩展性:LifeCycle事件
      • 5.3、观察这模式
      • 5.4、组件的生命周期状态变化
      • 5.5、重用性
      • 5.6、模版设计模式
  • 总结


前言

本来想搞个tomcat的调优,毕竟是高频面试题。不过要想调优,得先知道tomcat的底层架构,才能更方便的调优。本人水平有限,如有误导,欢迎斧正,一起学习,共同进步!


一、相关概念

1、tomcat的概念

tomcat是一个servlet容器,同时也是一个web服务器。

2、web应用部署的3种方式

  1. 拷贝到webapps目录下
// 指定appBase
<Host name="localhost" appBase="webapps"
      unpackWARs="true" autoDeploy="true">
      
/**
host: 虚拟主机的相关配置,
name: localhost是本身,你也可以改成具体的ip或者
appBase: 这个目录下的文件会帮你部署,可以是相对路径,也可以是绝对路径
unpackWARs: true代表自动把war包给解压。
autoDeploy: 热部署(自动刷新)
  1. server.xml 的Context标签下配置Context
<Context docBase="D:\mvc" path="/mvc" reloadable="true">

/**
context: 一个context标签对应一个web应用。
path:指定访问该web应用的url入口。(context-path)
docBase:指定web应用的文件路径,可以给定绝对路径,也可以给定相对于<Host>中appBase属性的相对路径。例如:
docBase="F:\Resource\mvc\target\mvc-1.0-SNAPSHOT"
reloadable:如果这个属性设为true,tomcat服务器在运行状态下会监听在WEB-INF/classes和WEB-INF/lib目录下class文件的改动。
如果监听到class文件被更新的。服务器会自动重新加载web应用。

如果server.xml是下面这样的:
则代表的是 http://localhost:8080/mvc/user.do
其中你的controller写的是: @GetMapping(value = “user.do”)

<Host name="localhost"  appBase="webapps"
unpackWARs="true" autoDeploy="true">

<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
           prefix="localhost_access_log." suffix=".txt"
           pattern="%h %l %u %t &quot;%r&quot; %s %b" />
           
<!--  http://localhost:8080/mvc/user.do   path:context-path   -->           
<Context docBase="F:\Resource\mvc\target\mvc-1.0-SNAPSHOT"
    path="/mvc" reloadable="true" />
  1. 在$CATALINA_BASE/conf/[enginename]/[hostname]/ 目录下,(默认是conf/Catalina/localhost)创建xml文件,文件名就是contextPath。比如创建mvc.xml,path就是/mvc
    注意:想要用根路径访问,文件名为ROOT.xml
<Context docBase="D:\mvc" reloadable="true">

二、tomcat的整体架构

tomcat要实现两个核心功能

  • 处理socket链接,负责网络字节流Request和Response对象转换
  • 加载和管理Servlet,以及具体处理Request请求
    因此Tomcat设计了两个核心组件连接器(Connector)和容器(Container)来分别做这两件事。连接器负责对外交流,容器负责内部处理。

1、tomcat架构图

在这里插入图片描述
从图中我们可以看到。

  1. Connector:Tomcat的连接器,用于接收请求并将其发送给容器。
  2. Container:Tomcat的容器,负责管理Servlet,Jsp和静态资源生命周期
  3. Engine:Tomcat的引擎,管理容器的生命周期和分配请求
  4. Host:Tomcat的主机,可以管理多个web应用程序的配置信息
  5. Context:Tomcat的上下文,用于管理多个web应用程序
  6. Servlet:Tomcat的Servlet,负责处理请求并生成响应
  7. JSP:Tomcat的JSP,用于动态生成web内容

一个tomcat中可以有多个service,每一个service可以分为容器Engine、链接Connector。
其中的容器Engine,每个容器有多个Host,每一个host都可以有多个context,然后每个context内又有多个servlet(tomcat将servlet封装成了Wrapper)
以 http://localhost:8080/mvc2 为例。
localhost,就会在多个host中找到是localhost的host,如果是指定的ip,则是具体的ip。8080,则会在这个host中找到8080的context。然后找到mvc2的servlet。然后去处理servlet中的逻辑,然后返回结果。

2、tomcat核心组件详解

2.1、server组件

指的就是整个Tomcat服务器,包含多组服务(services),负责管理和启动各个service,同时监听8005端口发送过来的shutdown命令,用于关闭整个容器

2.2、service组件

每个service组件都包含若干个用于接收客户端消息的Connector和处理请求的Engine组件。每个service组件还包含了若干个Executor组件,每个exevutor都是一个线程池,它可以为service内所有组件提供线程池执行任务。一个tomcat内可以有多个service,这样也是基于灵活性的考虑。通过一个tomcat内配置多个serivce,可以实现通过调用不同的端口去访问同一个机器上面的不同应用

2.3、连接器Connector组件

Tomcat与外部世界的连接器,监听固定端口接收外部请求,传递给Container,并将Container处理的结果返回给外部。连接器对servlet容器屏蔽了不同的应用层协议及I/O模型,无论是Http还是AJP,在容器中获取到的都是一个标准的ServletRequest对象

2.4、容器Container组件

容器,就是用来装载东西的器具,在tomcat中,容器就是用来装载Servlet的。Tomcat通过一种分层的架构,使得Servlet容器具有很好的灵活性。Tomcat设计了4种容器,分别是Engine、Host、Context和Wrapper。这4种容器不是平行关系,而是父子关系。

  • Engine : 引擎,Servlet顶层容器,用来管理多个虚拟站点,一个Service最多只有一个Engine;
  • Host : 虚拟主机,负责web应用的部署和Context的创建。可以给Tomcat配置多个虚拟主机地址,而一个虚拟主机下可以部署多个Web应用程序
  • Context: Web应用上下文,包含多个Wrapper,负责web配置的解析、管理所有的web资源,一个Context对应一个web应用程序
  • Wrapper:表示一个Servlet,最底层的容器,是对Servlet的封装,负责Servlet实例的创建、执行和销毁

在这里插入图片描述
可以对着Tomcat中的server.xml来理解:

<Server> //顶层组件,可以包括多个Service
	<Service> //顶层组件,可包含一个Engine,多个连接器
		<Connector/>//连接器组件,代表通信接口
		<Engine>//容器组件,一个Engine组件处理Service中的所有请求,包含多个Host
			<Host> //容器组件,处理特定的Host下客户请求,可包含多个Context
				<Context/> //容器组件,为特定的Web应用处理所有的客户请求
			</Host> 
		</Engine>
	</Service>
</Server>

tomcat启动期间会通过解析server.xml,利用反射创建相应的组件,所以xml中的标签和源码一一对应。

3、请求定位Servlet的过程

Tomcat是用Mapper组件来完成这个任务的。Mapper组件的功能就是将用户请求的url定位到一个Servlet,他的工作原理是:Mapper组件里保存了Web应用的配置信息,其实就是容器组件与访问路径的映射关系,比如Host容器里配置的域名、Context容器里的Web应用路径,以及Wrapper容器里Servlet映射的路径,你可以想象这些配置信息就是一个多层次的map,当一个请求到来时,Mapper组件通过解析请求url里面的域名和路径,再到自己保存的Map里面查找,就能定位到一个Servlet。一个请求url最只会定位到一个Wrapper容器,也就是一个Servlet

在这里插入图片描述

三、Tomcat架构设计精髓分析

1、Connector高内聚低耦合设计

优秀的设计应考虑高内聚、低耦合

  • 高内聚是指相关度比较高 功能要尽可能的集中,不要分散
  • 低耦合是指两个相关的模块要紧可鞥减少依赖的部分和降低依赖程度,不要让俩个模块产生强依赖

Tomcat连接器要实现的功能:

  • 监听网络端口
  • 接收网络连接请求
  • 读取请求网络字节流
  • 根据具体应用层协议(HTTP/AJP)解析字节流,生成统一的Tomcat Request对象
  • 将Tomcat Reuqest对象转为标准的ServletRequest
  • 调用Servlet容器,得到ServletResponse
  • 将ServletResponse转成TomcatResponse对象
  • 将Tomcat Response转为网络字节流
  • 将响应字节流写回给浏览器

分析连接器的详细功能,我们会发现连接器需要完成3个高内聚功能:

  • 网络通信
  • 应用层协议解析
  • Tomcat Request/Response与ServletRequest/ServletResponse的转换

因此,Tomcat的设计者设计了3个组件来分别实现这3个功能,分别是:EndPoint、Processor、Adapter

  • EndPoint 负责提供字节流给Processor
  • Processor负责提供Tomcat Request对象给Adapter
  • Adapter 负责提供ServletReqest对象给容器

除了这些变化点,系统也存在一些相对稳定的部分,因此 Tomcat 设计了一系列抽象基类来封装这些稳定的部分,抽象基类 AbstractProtocol 实现了 ProtocolHandler 接口。每一种应用层协议有自己的抽象基类,比如 AbstractAjpProtocol 和 AbstractHttp11Protocol,具体协议的实现类扩展了协议层抽象基类

在这里插入图片描述

1.1、ProtocolHandler

连接器用ProtocolHandler来处理网络连接和应用层协议,包含了2个重要组件:EndPoint和Processor

在这里插入图片描述
连接器用ProtocolHandler接口来封装通信协议和I/O模型的差异,ProtocolHandler内部又分为EndPoint和Processor模块,EndPoint负责底层Socket通讯,Processor负责应用层协议解析,连接器通过适配器Adapter调用容器。

1.2、EndPoint

endPoint是通讯端点。即通信监听的接口,是具体的Socket接收和发送处理器,是对传输层的抽象,因此,EndPoint是用来实现TCP/IP协议的。EndPoint是一个接口,对应的抽象实现类是AbstractEndpoint,而AbstractEndpoint的具体子类,比如在NoiEndpoint和Nio2Endpoint中,有两个重要的子组件:Acceptor和SocketProcessor、其中Acceptor用于监听Socket连接请求,SocketProcessor用于处理接收到的Sovket请求,他实现Runanble接口,在Run方法里调用协议处理组件Processor进行处理,为了提供处理能力,SovketProcessor被提交到线程池来执行,而这个线程池叫做执行器(Executor)。

1.3、Processpr

Processor用来实现HTTP/AJP协议,Processor接收来自EndPoint的Socket,读取字节流解析成Tomcat Request和Response对象,并通过Adapter将其提交到容器处理,Processor是对应用层协议的抽象。Processor是一个接口,定义了请求的处理等方法,他的抽象实现类AbstractProcessor对一些协议共有的属性进行封装,没有对方法进行实现,具体的视线有AJPProcessor、HTTP11Processor等,这些具体的实现类实现了特定协议的解析方法和请求处理方式。

在这里插入图片描述
EndPoint接收到Soket连接后,生成一个SocketProcessor任务提交到线程池去处理,SovketProcessor的Run方法会调用Processor组件去解析应用层协议,Processor通过解析生成的Request对象后,会调用Adapter的Service方法。

1.4、Adapter

由于协议不同,客户端发过来的请求信息也不尽相同,Tomcat定义了自己的Request类来“存放”这些请求信息。ProtocolHandler接口负责解析请求并生成Tomcat Reuquest类。但是这个Request对象不是标准的ServletRequest,也就一位这,不能用TomcatRequest作为参数来调用容器,Tomcat设计者的解决方案是引入CoyoteAdapter,这是适配器模型的经典运用,连接器调用CoyoteAdapter的Service方法,传入的是Tomcat Request对象,CoyoteAdapter负责将Tomcat Request转换为ServletRequest,在调用容器的Service方法。

2、设计复杂系统的基本思路

首先要分析需求,根据高内聚低耦合的原则确定子模块,然后找出子模块中的变化点和不变点,用接口和抽象基类去封装不变点,在抽象类中定义目标方法,让子类自行实现抽象方法,也就是具体子类去实现变化点

3、父子同期组合模式设计

(tomcat设计了4中容器:Engine、Host、Context、Wrapper,tomcat是怎么管理这些容器的?)
tomcat采用组合模式来管理这些容器,具体的实现方法是,所有容器组件都实现了Container接口,因此组合模式可以使用用户对单容器对象和组合同期对象的使用具有一致性。(我们从2.1的架构图可以知道每个services下都有一个engine,每个engine下都有多个Host,每个host下面都有多个context,每个context下都有多个wrapper,让这4个都继承一个公共的类Container,这样就能用一个Container来表示这4个容器了,不同的实现)
Container 接口定义如下:

public interface Container extends    Lifecycle{
    public void setName(String name);
    public Container getParent();
    public void setParent(Container container);
    public void addChild(Container child);
    public void removeChild(Container child);
    public Container findChild(String name);
}

在这里插入图片描述

4、PipeLine-Valve责任链模式设计

连接器中的Adapter会调用容器的Service方法来执行Servlet,最先拿到请求的是Engine容器,Engine容器对请求做一些处理后,会把请求传递给Host继续处理,以此类推,最后这个请求会传给Wrapper容器,Wrapper会调用最终的Servlet来处理。这个调用过程是怎么实现的呢?就是用Pipeline-Vavle管道。

4.1、PipeLine-Vavle责任链模式

PipeLine-Vavle是责任链模式,责任链模式是指一个请求处理的过程中,有很多的处理者依次对请求进行处理,每个处理者负责做自己对应的处理,处理完了疑惑,在调用下一个处理者继续处理

4.2、为什么要使用管道机制?

因为一个比较复杂的大型系统重,如果一个对象需要经过很复杂的逻辑处理,直接处理这些复杂的业务逻辑,扩展性,可重用性都是很差的。更好的解决方案是采用管道机制,用一条管道把多个对象(这些对象是指阀门部件,也就是这个很复杂的逻辑拆出来的一小块一小块的逻辑处理)连接起来,整体看起来就像若干个阀门嵌套在管道中一样,而处理逻辑放在阀门上。

PipeLine-Vavle责任链模式,从名字也能看出来,就俩组件,一个管道(pipeline)、一个阀门(vavle)。

4.3、Vavle接口设计

由于Pipeline是为容器设计的,所以他在设计时加入了一个Contained接口,就是为了制定当前Pipeline所属的容器
tomcat源码: org.apache.catalina.Pipeline

public interface Pipeline extends Contained {
    // 基础的处理阀
    public Valve getBasic();
    public void setBasic(Valve valve);

    // 对节点(阀门)增删查
    public void addValve(Valve valve);
    public Valve[] getValves();
    public void removeValve(Valve valve);

    // 获取第一个节点,遍历的起点,所以需要有这方法
    public Valve getFirst();

    // 是否对所有节点(阀门)都支持处理Servlet3异步处理
    public boolean isAsyncSupported();

    // 找到所有不支持Servlet3异步处理的阀门
    public void findNonAsyncValves(Set<String> result);
}

Pipeline维护了Valve链表,Valve可以插入到Pipeline中,对请求做某些处理,整个调用链的触发是Vavle来完成的,Valve完成自己的处理后,调用getNext.invoke()来触发下一个Vavle调用,每个容器都有一个Pipeline对象,只要触发这个Pipeline的第一个Valve,这个容器里Pipeline的Valve就会被调用到。Basic Valve处于Valve链表的末端,他是Pipeline中必不可少的一个Valve,负责调用下层容器的Pipeline里的第一个Valve。
在这里插入图片描述

  1. 当Adapter来请求了以后,先进入到Engine层,然后engine能够拿到(Pipeline的)getFirst,拿到第一个valve
  2. 然后从在engine中,就会一级一级的,从first拿到最后一个Basic
  3. 然后engine的basic就会getHost,拿到新的pipeline中的getFirst,然后再一级一级的拿到basic。。。直到拿到最下面的basic。(当然,每次都是先拿到管道pipeline,然后在用pipeline中的getFirst,然后invoke,然后nextInvoke。。。一级一级的往下走)

整个调用过程由连接器中的Adapter触发的,他会调用Engine的第一个Vavle:
真要通俗的讲,可以认为,先(每层都有pipeline)用管道pipiline中的getFirst拿到第一个,然后invoke,nextinvoke,然后直到basic,到basic了,在获取下一层(engine找host,host找context,context找wrapper)的pipeline,然后下一层的管道在getFirst,在invoke。。。

connector.getService().getContainer().getPipeline().getFirst().invoke(
        request, response);

Wrapper容器的最后一个Valve会创建一个Filter链,并调用doFilter(),最终会调到Servlet的service方法

filterChain.doFilter(request.getRequest(), response.getResponse());

4.4、Valve和Filter的区别:

  • Valve是Tomcat的私有机制,与Tomcat的基础架构/API是紧耦合的,Servlet API是共有的标准,所有的web容器包裹jetty都支持Filter机制。
  • Valve工作在web容器级别,拦截所有应用的请求;而Servlet Filter工作在应用级别,只能拦截某个web应用的所有请求
管道/阀门过滤器链/过滤器
管道(Pipeline)过滤器链(FilterChain)
阀门(Valve)过滤器(Filter)
底层实现为具有头、尾指针的单向链表底层实现为数组
Valve的核心方法invoke(request,response)Filter核心方法doFilter(request,response,chain)
pipeline.getFilter().invoke(request,respnse)filterchain.doFilter(request,response)

链表嘛,有单向链表、双向链表的,只是因为我们的请求是单向的,因此他是设计的单向的。
请求是单向的,因此我们链表单向的就行,就是值有next,没有pre。

5、Tomcat生命周期设计

tomcat里面有很多的组件,那这么大的系统,怎么管理这些组件呢?而且tomcat是怎么管理这些组件的生命周期呢,而且他还要支持热加载、热部署、后续可能还有针对于组件的扩展啊之类的,所以他的设计一定要扩展性比较高,要符合开闭原则(就是说你可以新增类,但是不能改旧类的逻辑)

5.1、一键式启停: LifetCycle接口

系统设计就是要找到系统的变化点和不变点。这里的不变点就是每个组件都要经历创建、初始化、启动这几个过程。而变化点是每个组件的具体的初始话方法是不同的。因此我们把不变点抽象出来为一个接口,这个接口和生命周期有关,叫做LifeCycle。这个接口有这些方法:init()、start()、stop()、destory(),每个具体的组件去实现这些方法。

public interface Lifecycle {
  /** 第1类:针对监听器 **/
  // 添加监听器
  public void addLifecycleListener(LifecycleListener listener);
  // 获取所有监听器
  public LifecycleListener[] findLifecycleListeners();
  // 移除某个监听器
  public void removeLifecycleListener(LifecycleListener listener);
 
  /** 第2类:针对控制流程 **/
  // 初始化方法
  public void init() throws LifecycleException;
  // 启动方法
  public void start() throws LifecycleException;
  // 停止方法,和start对应
  public void stop() throws LifecycleException;
  // 销毁方法,和init对应
  public void destroy() throws LifecycleException;
  
  /** 第3类:针对状态 **/
  // 获取生命周期状态
  public LifecycleState getState();
  // 获取字符串类型的生命周期状态
  public String getStateName();
}

在父组件的init()方法里需要创建子组件并调用子组件的init()方法,同样,在父组件的start()方法中也需要调用子组件的start()方法,因此调用者可以无差别调用各组件的init()方法和start()方法,这就是组合模式的使用。只需要调用最顶层组件,也就是server组件的init()和start()方法,整个tomcat就被启动起来了。

5.2、可扩展性:LifeCycle事件

各个组件的实现都是复杂多变的,如果将来需要增加新的逻辑,直接修改start()方法吗?这样会违法开闭原则。组件的init()和start()调用是由他的父组件的状态变化触发的,上层组件的初始化会触发子组件的初始化,上层组件的启动会触发子组件的启动,因此我们把组件的生命周期定义为一个个的状态,把状态的转变看做是一个事件,而事件是有监听器的,在监听器里可以实现一些逻辑,并且监听器也可以方便的添加和删除,这就是典型的观察者模式
其实就是在LifeCycle接口里加入俩方法:添加监听器和删除监听器。

在这里插入图片描述

5.3、观察这模式

监听器,观察者模式,每个组件都可以实现自己的监听,只要让组件去实现这个生命周期的接口就行(他里面有监听器的接口,只需要去实现就行)
我们还需要定义一个Enum来表示组件有哪些状态,以及处在什么状态会触发什么样的事件
在这里插入图片描述

5.4、组件的生命周期状态变化

组件的生命周期状态变化如下:

在这里插入图片描述

5.5、重用性

LifeCycleBase抽象基类
一般来说,实现类不止一个,不同的类在实现接口时往往会有一些相同的逻辑,如果让各个子类都去实现一遍,就会有重复的代码。那子类如果重用这部分逻辑呢?就是定义一个基类来实现共同的逻辑,然后让各个子类去继承他,就达到了重用的目的。而基类中往往定义一些抽象方法,所谓的抽象方法就是说基类不会去实现这些方法,而是调用这些方法来实现骨架逻辑。

Tomcat定义一个基类LifeCycleBase来实现LifeCycle接口,把一些公共的逻辑放到基类中去,比如生命状态的转变与维护、生命周期时间的触发以及监听器的添加和删除等,而子类就负责实现自己的初始化、启动和停止等方法。为了避免跟基类中的方法同名,我们把具体子类的实现方法改个名字,在后面加上Internal,叫initlenternal()、startInternal()等。

5.6、模版设计模式

模版设计模式(骨架抽象类和模版方法):LifeCycleBase实现了LifeCycle接口中的所有方法,还定义了相应的抽象方法交给具体子类去实现。


总结

tomcat的整体架构就分析完毕了,我们下一步就是,tomcat调优应该怎么搞,哈哈,是不是还蛮期待的。毕竟各种调优,各种原理,是面试官最喜欢问的问题。当然,了解底层原理,也有助于我们后续解决问题。

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

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

相关文章

三星与海力士发力决战HBM4

在8月中旬&#xff0c;三星宣布正在加速推进下一代HBM&#xff08;High Bandwidth Memory&#xff09;的研发&#xff0c;目标是在今年年底前完成HBM4的设计定稿&#xff08;tape-out&#xff09;。而SK海力士则继续保持其竞争力&#xff0c;计划在10月份完成HBM4的设计定稿&am…

华为云征文|部署私有云和文档管理系统 Kodcloud

华为云征文&#xff5c;部署私有云和文档管理系统 Kodcloud 一、Flexus云服务器X实例介绍1.1 云服务器介绍1.2 应用场景1.3 对比普通ECS 二、Flexus云服务器X实例配置2.1 重置密码2.2 服务器连接2.3 安全组配置 三、部署 Kodcloud3.1 Jellyfin 介绍3.2 Docker 环境搭建3.3 Jell…

Linux内核6.12新特性:panic之后扫码显示故障信息

Linux 内核 6.12 版本即将引入一项有趣的功能——在内核Panic时显示一个可选的二维码。这一功能将允许用户通过扫描二维码直接访问内核Panic信息的日志&#xff0c;从而更容易地诊断问题所在。 这不是 Linux 第一次尝试使用二维码。早在2014年&#xff0c;就有过关于在内核Pani…

docker-compose容器及Dockerfile的使用

软件资源&#xff1a; 链接: https://pan.baidu.com/s/1iYMP7p7v_HUMc9vakIQfbQ?pwd6fes 提取码: 6fes docker-compose容器 部署nmt---nginxcomposetomcat项目 使用基础的docker指令来创建镜像&#xff0c;实现项目的发布。 Dockerfile--->docker-compose 背景&#x…

[知识技巧] iPhone 15 卡在恢复模式修复方法

如果您发现您的iPhone 15 卡在恢复模式下&#xff0c;这可能是一次相当紧张的经历&#xff0c;尤其是当它阻止您使用手机时。请放心&#xff0c;这是许多 iPhone 用户面临的共同挑战。幸运的是&#xff0c;有一些行之有效的方法可以解决此问题并将您的 iPhone 恢复到正常功能。…

启动盘如何复原

条件&#xff1a; 本教程适合Windows系统 通过Diskpart CMD启动U盘还原U盘在Windows中还有CMD命令可以使用 先“WindowsR”呼出运行窗口&#xff0c;在命令行中搜索"Diskpart"。点击回车输入“list disk”并且回车输入“select disk X”(X为替换可启动u盘的磁盘号)…

Vite项目启动服务器时报错 Error: Cannot find module @rollup/rollup-win32-x64-msvc.

前言&#xff1a; 网上找了很多方法&#xff0c;尝试后都不行。在某篇文章评论区种找到了一种方法解决了&#xff0c;谨以此篇文章记录解决问题的方案&#xff0c;方便日后使用。 解决方法&#xff1a;github链接 Cannot find module rollup/rollup-win32-x64-msvc (rollup 4.…

idea配置连接数据库的操作方法(适配不同版本)

文章目录 一、IDEA找不到database图标的解决方法二、链接数据库 一、IDEA找不到database图标的解决方法 首先很多小伙伴说我左右侧边栏都找了找不到数据库图标 解决办法&#xff1a; 第一种&#xff1a;选择 View --> Tool Windows --> Database 如果你的idea版本在这里…

昂瑞微IPO:华为、小米看重的国产射频芯片,“腾飞”了吗?

从一些科技大厂的投资动向中&#xff0c;往往能够发现一些新的技术力量正在酝酿。 据悉&#xff0c;近日&#xff0c;小米、华为投资的射频芯片供应商——北京昂瑞微电子技术股份有限公司&#xff08;以下简称“昂瑞微”&#xff09;在北京证监局办理辅导备案登记&#xff0c;…

docker简单私有仓库的搭建

示例&#xff1a; 【搭建简单的Registry仓库】 1. 下载 Registry 镜像 [rootdocker ~]# docker pull registry #可以查看开放的端口&#xff0c;需要把端口暴露出来 [rootdocker ~]# docker history registry:latest [rootdocker ~]# docker run -d -p 5000:5000 --restartal…

若依微服务ruoyi-auth在knife4j中不显示问题解决

关于若依微服务ruoyi-auth在knife4j中不显示问题解决 解决办法 一、添加swagger依赖文件 在ruoyi-auth模块下的pom.xml文件中添加ruoyi-common-swagger依赖 <!-- RuoYi Common Swagger --><dependency><groupId>com.ruoy

DS1202ZE - 零碎使用记录

文章目录 DS1202ZE - 零碎使用记录概述笔记测量通道频率是使用频率测量还是内建的频率计测量&#xff1f;如何让Ultra Sigma检测到DS1202ZE?如何消除过多的测量结果?通过UltraScope对示波器屏幕进行截图END DS1202ZE - 零碎使用记录 概述 手头在使用DS1202ZE, 将零碎的操作…

javascript网页设计案例详细讲解

​ 大家好&#xff0c;我是程序员小羊&#xff01; 前言&#xff1a; 我们将以深入讲解一个基于JavaScript的网页设计案例。这个案例不仅涵盖了基础的DOM操作、事件处理和动画效果&#xff0c;还将涉及更高级的主题&#xff0c;如异步数据加载、表单验证、模块化设计和响应式布…

【动态规划】两个数组 / 字符串的dp问题(子序列、子数组问题、匹配问题、字符串问题)

文章目录 前言算法题1.最长公共子序列2.不相交的线3.不同的子序列4.通配符匹配5.正则表达式匹配6.交错字符串7.两个字符串的最小ASCII删除和8.最长重复子数组 前言 两个数组或字符串的动态规划问题通常涉及到比较和匹配元素。以下是两个常见的例子&#xff1a; 最长公共子序列…

「OC」CAlayer——巧用动画实现一个丝滑的折叠cell

「OC」CAlayer——巧用动画实现一个丝滑的折叠cell 前言 在这个暑假集训后的时间&#xff0c;都在家里做着学习笔记的整理&#xff0c;深入学习了CALayer的相关知识&#xff0c;掌握了第三方库Masonry自动布局的用法&#xff0c;以及学习了MVC的相关内容&#xff0c;正好组内…

在Postgresql中计算工单的对应的GPS轨迹距离

一、概述 在某个App开发中&#xff0c;要求记录用户的日常轨迹&#xff0c;在用户巡逻设备的时&#xff0c;将记录的轨迹点当做该设备巡逻时候的轨迹。 由于业务逻辑上没有明确的指示人员巡逻工单-GPS位置之间的关系&#xff0c;所以通过时间关系进行轨迹划定。 二、创建测试表…

Core ML

本文翻译整理自&#xff1a;Core ML : https://developer.apple.com/cn/documentation/coreml/ 文章目录 一、概览二、获取 Core ML 模型三、将 Core ML 模型集成到你的 App 中1、将模型添加到您的Xcode项目2、在代码中创建模型3、获取要传递给模型的输入值4、使用模型进行预测…

vue2踩坑记录:el-select如何绑定对象

页面上的下拉框&#xff1a; 选中人员之后&#xff0c;需要使用人员的其它信息做后续操作。所以不能只绑定用户Id&#xff0c;需要绑定整个item(用户对象)&#xff0c;这样每次change事件所获取到的newValue是整个对象&#xff0c;而且v-model中的变量指向的也是整个对象&#…

Windows bat脚本学习五(函数)

一、简介 使用bat脚本时&#xff0c;经常要使用函数&#xff0c;而函数的传参和返回值也是非常重要的&#xff0c;今天就总结一下bat中函数相关的使用方法。 二、函数 1.函数调用 :函数名 在Bat中&#xff0c;使用“:函数名”来定义一个函数。 见如下代码&#xff1a; echo o…

安装KataGo+Sabaki围棋引擎

1.由于我的用户名是中文名,修改TMP和TEMP为SystemRoot(后续修改回来) 2.加入xx.bin文件后,cmd输入katago.exe genconfig -model model.bin -output gtp_custom.cfg 3.KataGo下载完成 4.配置GUI界面