Tomcat系统架构浅析

news2025/1/13 7:34:55

大家好,我是易安!

今天咱们就来一步一步分析Tomcat的设计思路,看看Tomcat的设计者们是如何设计一个复杂系统,怎么设计顶层模块,以及模块之间的关系。

Tomcat总体架构

我们知道如果要设计一个系统,首先是要了解需求。Tomcat有2个核心功能:

  • 处理Socket连接,负责网络字节流与Request和Response对象的转化。

  • 加载和管理Servlet,以及具体处理Request请求。

因此Tomcat设计了两个核心组件连接器(Connector)和容器(Container)来分别做这两件事情。连接器负责对外交流,容器负责内部处理。

所以连接器和容器可以说是Tomcat架构里最重要的两部分,需要花费大量精力理解清楚。

在开始讲连接器前,我先讲讲Tomcat支持的多种I/O模型和应用层协议。

Tomcat支持的I/O模型有:

  • NIO:非阻塞I/O,采用Java NIO类库实现。

  • NIO.2:异步I/O,采用JDK 7最新的NIO.2类库实现。

  • APR:采用Apache可移植运行库实现,是C/C++编写的本地库。

Tomcat支持的应用层协议有:

  • HTTP/1.1:这是大部分Web应用采用的访问协议。

  • AJP:用于和Web服务器集成(如Apache)。

  • HTTP/2:HTTP 2.0大幅度的提升了Web性能。

Tomcat为了实现支持多种I/O模型和应用层协议,一个容器可能对接多个连接器,就好比一个房间有多个门。但是单独的连接器或者容器都不能对外提供服务,需要把它们组装起来才能工作,组装后这个整体叫作Service组件。这里请你注意,Service本身没有做什么重要的事情,只是在连接器和容器外面多包了一层,把它们组装在一起。Tomcat内可能有多个Service,这样的设计也是出于灵活性的考虑。通过在Tomcat中配置多个Service,可以实现通过不同的端口号来访问同一台机器上部署的不同应用。

到此我们得到这样一张关系图:

alt

从图上你可以看到,最顶层是Server,这里的Server指的就是一个Tomcat实例。一个Server中有一个或者多个Service,一个Service中有多个连接器和一个容器。连接器与容器之间通过标准的ServletRequest和ServletResponse通信。

连接器

连接器对Servlet容器屏蔽了协议及I/O模型等的区别,无论是HTTP还是AJP,在容器中获取到的都是一个标准的ServletRequest对象。

我们可以把连接器的功能需求进一步细化,比如:

  • 监听网络端口。

  • 接受网络连接请求。

  • 读取网络请求字节流。

  • 根据具体应用层协议(HTTP/AJP)解析字节流,生成统一的Tomcat Request对象。

  • 将Tomcat Request对象转成标准的ServletRequest。

  • 调用Servlet容器,得到ServletResponse。

  • 将ServletResponse转成Tomcat Response对象。

  • 将Tomcat Response转成网络字节流。

  • 将响应字节流写回给浏览器。

需求列清楚后,我们要考虑的下一个问题是,连接器应该有哪些子模块?优秀的模块化设计应该考虑 高内聚、低耦合

  • 高内聚 是指相关度比较高的功能要尽可能集中,不要分散。

  • 低耦合 是指两个相关的模块要尽可能减少依赖的部分和降低依赖的程度,不要让两个模块产生强依赖。

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

  • 网络通信。

  • 应用层协议解析。

  • Tomcat Request/Response与ServletRequest/ServletResponse的转化。

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

组件之间通过抽象接口交互。这样做还有一个好处是 封装变化。 这是面向对象设计的精髓,将系统中经常变化的部分和稳定的部分隔离,有助于增加复用性,并降低系统耦合度。

网络通信的I/O模型是变化的,可能是非阻塞I/O、异步I/O或者APR。应用层协议也是变化的,可能是HTTP、HTTPS、AJP。浏览器端发送的请求信息也是变化的。

但是整体的处理逻辑是不变的,Endpoint负责提供字节流给Processor,Processor负责提供Tomcat Request对象给Adapter,Adapter负责提供ServletRequest对象给容器。

如果要支持新的I/O方案、新的应用层协议,只需要实现相关的具体子类,上层通用的处理逻辑是不变的。

由于I/O模型和应用层协议可以自由组合,比如NIO + HTTP或者NIO.2 + AJP。Tomcat的设计者将网络通信和应用层协议解析放在一起考虑,设计了一个叫ProtocolHandler的接口来封装这两种变化点。各种协议和通信模型的组合有相应的具体实现类。比如:Http11NioProtocol和AjpNioProtocol。

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

alt

通过上面的图,你可以清晰地看到它们的继承和层次关系,这样设计的目的是尽量将稳定的部分放到抽象基类,同时每一种I/O模型和协议的组合都有相应的具体实现类,我们在使用时可以自由选择。

小结一下,连接器模块用三个核心组件:Endpoint、Processor和Adapter来分别做三件事情,其中Endpoint和Processor放在一起抽象成了ProtocolHandler组件,它们的关系如下图所示。

alt

下面我来详细介绍这两个顶层组件ProtocolHandler和Adapter。

ProtocolHandler组件

由上文我们知道,连接器用ProtocolHandler来处理网络连接和应用层协议,包含了2个重要部件:Endpoint和Processor,下面我来详细介绍它们的工作原理。

  • Endpoint

Endpoint是通信端点,即通信监听的接口,是具体的Socket接收和发送处理器,是对传输层的抽象,因此Endpoint是用来实现TCP/IP协议的。

Endpoint是一个接口,对应的抽象实现类是AbstractEndpoint,而AbstractEndpoint的具体子类,比如在NioEndpoint和Nio2Endpoint中,有两个重要的子组件:Acceptor和SocketProcessor。

其中Acceptor用于监听Socket连接请求。SocketProcessor用于处理接收到的Socket请求,它实现Runnable接口,在run方法里调用协议处理组件Processor进行处理。为了提高处理能力,SocketProcessor被提交到线程池来执行。而这个线程池叫作执行器(Executor)

  • Processor

如果说Endpoint是用来实现TCP/IP协议的,那么Processor用来实现HTTP协议,Processor接收来自Endpoint的Socket,读取字节流解析成Tomcat Request和Response对象,并通过Adapter将其提交到容器处理,Processor是对应用层协议的抽象。

Processor是一个接口,定义了请求的处理等方法。它的抽象实现类AbstractProcessor对一些协议共有的属性进行封装,没有对方法进行实现。具体的实现有AjpProcessor、Http11Processor等,这些具体实现类实现了特定协议的解析方法和请求处理方式。

我们再来看看连接器的组件图:

alt

从图中我们看到,Endpoint接收到Socket连接后,生成一个SocketProcessor任务提交到线程池去处理,SocketProcessor的run方法会调用Processor组件去解析应用层协议,Processor通过解析生成Request对象后,会调用Adapter的Service方法。到这里我们学习了ProtocolHandler的总体架构和工作原理

Adapter组件

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

讲完了Tomcat的核心组件:连接器,下面我们紧接着来看下另外一个核心组件和容器

容器,顾名思义就是用来装载东西的器具,在Tomcat里,容器就是用来装载Servlet的。那Tomcat的Servlet容器是如何设计的呢?

容器

Tomcat设计了4种容器,分别是Engine、Host、Context和Wrapper。这4种容器不是平行关系,而是父子关系。比如下图,你就能看出他们的关系:

alt

你可能会问,为什么要设计成这么多层次的容器,这不是增加了复杂度吗?其实这背后的考虑是, Tomcat通过一种分层的架构,使得Servlet容器具有很好的灵活性。

Context表示一个Web应用程序;Wrapper表示一个Servlet,一个Web应用程序中可能会有多个Servlet;Host代表的是一个虚拟主机,或者说一个站点,可以给Tomcat配置多个虚拟主机地址,而一个虚拟主机下可以部署多个Web应用程序;Engine表示引擎,用来管理多个虚拟站点,一个Service最多只能有一个Engine。

你可以再通过Tomcat的 server.xml 配置文件来加深对Tomcat容器的理解。Tomcat采用了组件化的设计,它的构成组件都是可配置的,其中最外层的是Server,其他组件按照一定的格式要求配置在这个顶层容器中。

alt

那么,Tomcat是怎么管理这些容器的呢?你会发现这些容器具有父子关系,形成一个树形结构,你可能马上就想到了设计模式中的组合模式。没错,Tomcat就是用组合模式来管理这些容器的。具体实现方法是,所有容器组件都实现了Container接口,因此组合模式可以使得用户对单容器对象和组合容器对象的使用具有一致性。这里单容器对象指的是最底层的Wrapper,组合容器对象指的是上面的Context、Host或者Engine。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);
}

正如我们期望的那样,我们在上面的接口看到了getParent、setParent、addChild和removeChild等方法。你可能还注意到Container接口扩展了Lifecycle接口,Lifecycle接口用来统一管理各组件的生命周期。

请求定位Servlet的过程

你可能好奇,设计了这么多层次的容器,Tomcat是怎么确定请求是由哪个Wrapper容器里的Servlet来处理的呢?答案是,Tomcat是用Mapper组件来完成这个任务的。

Mapper组件的功能就是将用户请求的URL定位到一个Servlet,它的工作原理是:Mapper组件里保存了Web应用的配置信息,其实就是 容器组件与访问路径的映射关系,比如Host容器里配置的域名、Context容器里的Web应用路径,以及Wrapper容器里Servlet映射的路径,你可以想象这些配置信息就是一个多层次的Map。

当一个请求到来时,Mapper组件通过解析请求URL里的域名和路径,再到自己保存的Map里去查找,就能定位到一个Servlet。请你注意,一个请求URL最后只会定位到一个Wrapper容器,也就是一个Servlet。

读到这里你可能感到有些抽象,接下来我通过一个例子来解释这个定位的过程。

假如有一个网购系统,有面向网站管理人员的后台管理系统,还有面向终端客户的在线购物系统。这两个系统跑在同一个Tomcat上,为了隔离它们的访问域名,配置了两个虚拟域名: manage.shopping.comuser.shopping.com,网站管理人员通过 manage.shopping.com 域名访问Tomcat去管理用户和商品,而用户管理和商品管理是两个单独的Web应用。终端客户通过 user.shopping.com 域名去搜索商品和下订单,搜索功能和订单管理也是两个独立的Web应用。

针对这样的部署,Tomcat会创建一个Service组件和一个Engine容器组件,在Engine容器下创建两个Host子容器,在每个Host容器下创建两个Context子容器。由于一个Web应用通常有多个Servlet,Tomcat还会在每个Context容器里创建多个Wrapper子容器。每个容器都有对应的访问路径,可以通过下面这张图来帮助理解。

alt

假如有用户访问一个URL,比如图中的 http://user.shopping.com:8080/order/buy,Tomcat如何将这个URL定位到一个Servlet呢?

1根据协议和端口号选定Service和Engine。

我们知道Tomcat的每个连接器都监听不同的端口,比如Tomcat默认的HTTP连接器监听8080端口、默认的AJP连接器监听8009端口。上面例子中的URL访问的是8080端口,因此这个请求会被HTTP连接器接收,而一个连接器是属于一个Service组件的,这样Service组件就确定了。我们还知道一个Service组件里除了有多个连接器,还有一个容器组件,具体来说就是一个Engine容器,因此Service确定了也就意味着Engine也确定了。

2根据域名选定Host。

Service和Engine确定后,Mapper组件通过URL中的域名去查找相应的Host容器,比如例子中的URL访问的域名是 user.shopping.com,因此Mapper会找到Host2这个容器。

3根据URL路径找到Context组件。

Host确定以后,Mapper根据URL的路径来匹配相应的Web应用的路径,比如例子中访问的是 /order,因此找到了Context4这个Context容器。

4根据URL路径找到Wrapper(Servlet)。

Context确定后,Mapper再根据 web.xml 中配置的Servlet映射路径来找到具体的Wrapper和Servlet。

看到这里,我想你应该已经了解了什么是容器,以及Tomcat如何通过一层一层的父子容器找到某个Servlet来处理请求。需要注意的是,并不是说只有Servlet才会去处理请求,实际上这个查找路径上的父子容器都会对请求做一些处理。连接器中的Adapter会调用容器的Service方法来执行Servlet,最先拿到请求的是Engine容器,Engine容器对请求做一些处理后,会把请求传给自己子容器Host继续处理,依次类推,最后这个请求会传给Wrapper容器,Wrapper会调用最终的Servlet来处理。那么这个调用过程具体是怎么实现的呢?答案是使用Pipeline-Valve管道。

Pipeline-Valve是责任链模式,责任链模式是指在一个请求处理的过程中有很多处理者依次对请求进行处理,每个处理者负责做自己相应的处理,处理完之后将再调用下一个处理者继续处理。

Valve表示一个处理点,比如权限认证和记录日志。如果你还不太理解的话,可以来看看Valve和Pipeline接口中的关键方法。

public interface Valve {
  public Valve getNext();
  public void setNext(Valve valve);
  public void invoke(Request request, Response response)
}

由于Valve是一个处理点,因此invoke方法就是来处理请求的。注意到Valve中有getNext和setNext方法,因此我们大概可以猜到有一个链表将Valve链起来了。请你继续看Pipeline接口:

public interface Pipeline extends Contained {
  public void addValve(Valve valve);
  public Valve getBasic();
  public void setBasic(Valve valve);
  public Valve getFirst();
}

没错,Pipeline中有addValve方法。Pipeline中维护了Valve链表,Valve可以插入到Pipeline中,对请求做某些处理。我们还发现Pipeline中没有invoke方法,因为整个调用链的触发是Valve来完成的,Valve完成自己的处理后,调用 getNext.invoke 来触发下一个Valve调用。

每一个容器都有一个Pipeline对象,只要触发这个Pipeline的第一个Valve,这个容器里Pipeline中的Valve就都会被调用到。但是,不同容器的Pipeline是怎么链式触发的呢,比如Engine中Pipeline需要调用下层容器Host中的Pipeline。

这是因为Pipeline中还有个getBasic方法。这个BasicValve处于Valve链表的末端,它是Pipeline中必不可少的一个Valve,负责调用下层容器的Pipeline里的第一个Valve。我还是通过一张图来解释。

alt

整个调用过程由连接器中的Adapter触发的,它会调用Engine的第一个Valve:

// Calling the container
connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);

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

你可能会问,Filter似乎也有相似的功能,那Valve和Filter有什么区别吗?它们的区别是:

  • Valve是Tomcat的私有机制,与Tomcat的基础架构/API是紧耦合的。Servlet API是公有的标准,所有的Web容器包括Jetty都支持Filter机制。

  • 另一个重要的区别是Valve工作在Web容器级别,拦截所有应用的请求;而Servlet Filter工作在应用级别,只能拦截某个Web应用的所有请求。如果想做整个Web容器的拦截器,必须通过Valve来实现。

总结

  • Tomcat的整体架构包含了两个核心组件连接器和容器。连接器负责对外交流,容器负责内部处理。连接器用ProtocolHandler接口来封装通信协议和I/O模型的差异,ProtocolHandler内部又分为Endpoint和Processor模块,Endpoint负责底层Socket通信,Processor负责应用层协议解析。连接器通过适配器Adapter调用容器。

  • Tomcat设计了多层容器是为了灵活性的考虑,灵活性具体体现在一个Tomcat实例(Server)可以有多个Service,每个Service通过多个连接器监听不同的端口,而一个Service又可以支持多个虚拟主机。一个URL网址可以用不同的主机名、不同的端口和不同的路径来访问特定的Servlet实例。

  • 请求的链式调用是基于Pipeline-Valve责任链来完成的,这样的设计使得系统具有良好的可扩展性,如果需要扩展容器本身的功能,只需要增加相应的Valve即可

  • 通过对Tomcat整体架构的学习,我们可以得到一些设计复杂系统的基本思路。首先要分析需求,根据高内聚低耦合的原则确定子模块,然后找出子模块中的变化点和不变点,用接口和抽象基类去封装不变点,在抽象基类中定义模板方法,让子类自行实现抽象方法,也就是具体子类去实现变化点。

本文由 mdnice 多平台发布

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

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

相关文章

特征缩放(Scale Features)、特征缩放预测​CO2 值、df列索引扩展

目录 1、特征缩放 2、预测CO2 值 3、df列索引扩展 1、特征缩放 特征缩放可以用于不同的度量单位。度量单位不同的情况下,特征的数值大小也会有所不同,这可能会影响到某些机器学习算法的表现。例如,如果一个特征的单位是英寸,而另…

DAB-DETR代码学习笔记

先上一张整体架构图 : 代码地址:GitHub - IDEA-Research/DAB-DETR: [ICLR 2022] DAB-DETR: Dynamic Anchor Boxes are Better Queries for DETR 论文地址: https://arxiv.org/pdf/2201.12329.pdf 文章全名《DYNAMIC ANCHOR BOXES ARE BETTER …

建模杂谈系列223 Q-Learning示例的代码拆解分析

说明 找到了一个合适的例子,然后我对其中的内容进行了拆解分析。我觉得代码表达的内容比伪代码清晰多了。 这次算是补砖了(监督无监督强化),过去实际上接触过很多强化体系内的基本工具,但一直没有开始做,部分原因是没时间&#…

Java 与排序算法(5):归并排序

一、归并排序 归并排序(Merge Sort)是一种基于分治思想的排序算法。它将待排序的数组分成两个长度相等的子数组,然后对这两个子数组分别进行归并排序,最后将两个排好序的子数组合并成一个有序的数组。 具体实现过程如下&#xf…

【国内chatgpt使用方法合集】(5月22日已更新)

写在前面 Hello大家好, 我是【麟-小白】,一位软件工程专业的学生,喜好计算机知识。希望大家能够一起学习进步呀!本人是一名在读大学生,专业水平有限,如发现错误或不足之处,请多多指正&#xff0…

Elasticsearch文档操作:初学者指南(2023年最新版包含DSL语句的使用和RestHighLevelClient在Java中的使用)

2023年还没有学习Elasticsearch?,那么您将错过最强大、最通用的编程语言之一。 本文将介绍在Elasticsearch对文档分别使用DSL语句和Java High Level REST ClientAPI来对文档进行操作。获取更多信息查看官网帮助文档 运行环境: Linux,docke…

驱动开发DAY6

非阻塞IO 在应用程序中读取硬件数据时,无论硬件数据是否准备完毕,read()函数不会阻塞,继续向下执行 阻塞IO 当应用程序中读取硬件数据时,在硬件数据没有准备好时,进程会阻塞在read(&…

C语言——如何写出好的代码?

哈喽,大家好,今天我们来学习如何才能写出优秀的代码,主要讲的是assert和const的用法。 首先,什么样的代码才算的上是优秀的代码呢?应该符合下面的要求: 1. 代码运行正常 2. bug很少 3. 效率高 4. 可读性高 …

自抗扰PID(梯形图源代码)

有关ADRC的详细算法和源代码,请参看专栏的系列文章,这里不再赘述,常用链接如下: ADRC自抗扰控制算法(含梯形图完整源代码和算法公式)_adrc算法_RXXW_Dor的博客-CSDN博客PLC的自抗扰控制(ADRC)算法_RXXW_Dor的博客-CSDN博客_adrc算法1、自抗扰控制算法,网上很多文章有所…

x210---根文件系统制作

一、busybox的移植 1.1、busybox源码下载 (1)busybox是一个开源项目,所以源代码可以直接从网上下载。 (2)busybox的版本差异不大,版本新旧无所谓。 (3)下载busybox可以去linuxidc等镜像网站,也可以去www.busybox.net官方网站下载。 1.2、修…

技术人如何写简历?(文末有福利)

前言 笔者在滴滴、阿里和字节时候也面试了不少人,看过形形色色的简历没有上百也有大几十份了。校招季也快到了,这里总结自身经验聊一下 技术人的简历如何去写面试官是怎么样从一份简历去开展后续的面试 简历的作用 简历是你向一家公司求职的“敲门砖…

数据结构学习之路-集合

集合Set 集合的特点集合的内部实现(使用链表)集合的内部实现(使用红黑树)复杂度分析使用红黑树实现集合的限制 集合的特点 不存放重复的元素常用于去重 例如:存放新增的IP地址,统计新增IP量;存…

torch中的model.eval()、model.train()详解

👨‍💻个人简介: 深度学习图像领域工作者 🎉工作总结链接:https://blog.csdn.net/qq_28949847/article/details/128552785 链接中主要是个人工作的总结,每个链接都是一些常用demo&#xff0c…

Laravel框架05:模型和自动验证

Laravel框架05:模型和自动验证 一、模型(AR模式)概述二、定义模型三、调用模型四、基本操作1. 添加数据① AR模式② Request 2. 查询数据3. 修改操作① AR模式② update 4. 删除操作 五、控制器验证1. 基本语法2. 输出错误信息 一、模型&…

今麦郎跻身“我最喜欢中国品牌”榜,致力领航中国品牌发展新范式

在中国经济探寻高质量发展的当下,中国民营企业肩负着推动经济发展的重任。在当前中国经济向上向前的大背景下,展示中国特色、传播中国文化、践行社会责任多位一体的高质量品牌越来越受到重视。但冰冻三尺非一日之功,唯有经历时间考验&#xf…

Spring:Spring 整合 MyBatis 的具体过程

文章目录 Spring:Day 04整合 MyBatis一、配置环境1. 导入依赖2. 准备一个数据库 二、用 Spring 整合 MyBatis1. 编写通用配置文件2. 编写实现类3. 编写 Spring 配置文件4. 测试5. 分析总结 三、拓展1. 实现2. 总结 四、事务1. 概述2. 没有事务时3. 声明式事务4. 总结…

“Shell“SNAT,DNAT

文章目录 一.SNAT1.1 SNAT原理1.2 SNAT的应用环境1.3 SNAT工作原理1.4 进行SNAT转换后1.5 配置SNAT策略1.6SNAT实验 二.DNAT2.1 DNAT工作原理2.2 配置DNAT策略2.3 DNAT实验 一.SNAT 1.1 SNAT原理 SNAT原理:修改数据包的源地址。SNAT 应用环境:局域网主…

基础学习——关于卷积层的记录

文章目录 前言一、功能层1、池化层2、nn.BatchNorm2d()3、全连接层4、softmax层 二、卷积层1、普通卷积2、空洞卷积3、多尺度卷积4、分组卷积5、深度可分离卷积6、形变卷积 前言 老是忘有些模块的具体作用,记录一下。 一、功能层 1、池化层 池化层夹在连续的卷积…

总结最全面的TCP、UDP、Socket、HTTP网络编程面试题

先看一天面试的经验: 第一场: 面试官:你说一下TCP的三次握手 我:第一次Client将SYN置1......、第二次Server收........、 第三次........ 面试官:很难背吧? 我:......是啊,很难&…

harbor安装

文章目录 先决条件硬件软件网络端口 安装docker签发证书生成证书颁发机构证书 生成服务器证书向 Harbor 和 Docker 提供证书 下载harbor安装包containerd 配置私有仓库(二选一)分发证书(如上文只是路径变了)配置登录加密登录打标签并推送与拉取 docker 配…