本文将简要分析一下关于 Spring Eureka 相关的一些必要的源代码,对应的版本:Spring Cloud 2021.0.1

@EnableEurekaServer 注解

@EnableEurekaServer 注解标记当前的应用程序作为一个注册中心,查看 @EnableEurekaServer 的源代码,具体内容如下所示:

package org.springframework.cloud.netflix.eureka.server;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import org.springframework.context.annotation.Import; @Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(EurekaServerMarkerConfiguration.class)
public @interface EnableEurekaServer {
}

可以看到,该注解导入了 EurekaServerMarkerConfiguration 配置类,继续查看 EurekaServerMarkerConfiguration 对应的源代码,具体内容如下所示:

package org.springframework.cloud.netflix.eureka.server;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration(proxyBeanMethods = false)
public class EurekaServerMarkerConfiguration { @Bean
public Marker eurekaServerMarkerBean() {
return new Marker();
} class Marker { } }

可以看到,该配置类只是定义了一个 Mark 类型的 Bean。可以看到,Mark 就是在 EurekaServerMarkerConfiguration 中定义的一个类,该类并没有任何特殊的地方。

由于 Spring-Boot 的自动配置是通过加载 spring.factories 文件中的内容来完成自动配置的,因此,可以试着从当前 EurekaServerMarkerConfiguration 配置类所在的包的 spring.factories 文件入手来进一步的进行分析

查看 EurekaServerMarkerConfiguration 对应的包的 spring.factories 文件,具体内容如下所示:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.server.EurekaServerAutoConfiguration

可以看到,在 spring.factories 文件中只有一个自动配置类 .EurekaServerAutoConfiguratio,查看该类对应的源代码,具体的内容如下所示:

package org.springframework.cloud.netflix.eureka.server;

// 省略一些其它的包的导入

@Configuration(proxyBeanMethods = false)
@Import(EurekaServerInitializerConfiguration.class)
/*
可以看到,在这里定义了一个条件 Bean,只有当这个 Bean 出现在
Spring 上下文环境中时,才会将 EurekaServerAutoConfiguration Bean
加载到 Spring 应用的上下文中
*/
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class, InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
// 省略具体的源代码
}

也就是说,只有 Spring 中存在 EurekaServerMarkerConfiguration.Mark 类型的 Bean 时,才会将 EurekaServerAutoConfiguration 自动配置类加载到 Spring 上下文中,从而实现 Eureka 注册中心的功能

DashBoard Path

我们知道,当成功启动一个 Spring Eureka 注册中心时,访问对应的 IP 和端口即可看到对应的控制台界面,这里的访问路径是通过 EurekaDashboardProperties 来进行配置的。

查看 EurekaDashboardProperties 对应的源代码,如下所示:

package org.springframework.cloud.netflix.eureka.server;

import org.springframework.boot.context.properties.ConfigurationProperties;

/*
这里通过配置属性的方式来进行 dashboard 访问路径的配置
*/
@ConfigurationProperties("eureka.dashboard")
public class EurekaDashboardProperties {
// 默认的 dashboard 访问路径
private String path = "/"; // 省略一部分不是特别重要的代码
}

这就是为什么直接访问注册中心的 IP 加端口号就能访问到控制台的原因

EnableDiscoveryClient 注解

实际上,对于普通的应用服务来讲(为了和注册中心区分,将非注册中心的服务称为客户端),加上 @EnableDiscoveryClient 就能够实现服务的自动注册和发现,@EnableDiscoveryClient 注解对应的源代码如下所示:

package org.springframework.cloud.client.discovery;

// 省略一部分不太重要的代码

public @interface EnableDiscoveryClient {
/*
可以看到,这里的默认值为 true,因此默认情况下,
会将当前的服务注册到注册中心
*/
boolean autoRegister() default true;
}

这是加上 @EnableDiscoveryClient 注解后的默认属性,不是

即使在启动类上没有加上 @EnableDiscoveryClient 注解,该服务依旧能够被注册中心发现,这是由于在 Spring Eureka Client 的自动配置类中默认了这种行为。

查看 spring-cloud-starter-netflix-eureka-client 包中的 spring.factories 文件,具体的内容如下所示:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaClientConfigServerAutoConfiguration,\
org.springframework.cloud.netflix.eureka.config.DiscoveryClientOptionalArgsConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration,\
org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration,\
org.springframework.cloud.netflix.eureka.reactive.EurekaReactiveDiscoveryClientConfiguration,\
org.springframework.cloud.netflix.eureka.loadbalancer.LoadBalancerEurekaAutoConfiguration org.springframework.cloud.bootstrap.BootstrapConfiguration=\
org.springframework.cloud.netflix.eureka.config.EurekaConfigServerBootstrapConfiguration org.springframework.boot.BootstrapRegistryInitializer=\
org.springframework.cloud.netflix.eureka.config.EurekaConfigServerBootstrapper

其中,org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration 自动配置类定义了这一默认的行为,具体的源代码如下所示:

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@ConditionalOnDiscoveryEnabled
@AutoConfigureBefore({ CommonsClientAutoConfiguration.class, ServiceRegistryAutoConfiguration.class })
@AutoConfigureAfter(name = { "org.springframework.cloud.netflix.eureka.config.DiscoveryClientOptionalArgsConfiguration",
"org.springframework.cloud.autoconfigure.RefreshAutoConfiguration",
// 在这个自动配置类中定义了服务发现的默认行为
"org.springframework.cloud.netflix.eureka.EurekaDiscoveryClientConfiguration",
"org.springframework.cloud.client.serviceregistry.AutoServiceRegistrationAutoConfiguration" })
public class EurekaClientAutoConfiguration {
// 省略一部分不太重要的代码
}

继续查看 EurekaDiscoveryClientConfiguration 配置类的源代码,相关的内容如下所示:

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties
@ConditionalOnClass(EurekaClientConfig.class)
// 重点在这里,可以看到,这里的默认值为 true,即默认允许被自动发现和注册
@ConditionalOnProperty(value = "eureka.client.enabled", matchIfMissing = true)
@ConditionalOnDiscoveryEnabled
@ConditionalOnBlockingDiscoveryEnabled
public class EurekaDiscoveryClientConfiguration {
// 省略一部分不太重要的代码
}

EurekaServerAutoConfiguration 解析

前文提到,在 EurekaServerMarkerConfiguration.Marker 类型的 Bean 装载到 Spring 上下文环境中时,才会加载 EurekaServerAutoConfiguration,这个配置类定义了有关 Eureka 服务端的相关配置,在这里进行简要的解析

相关属性介绍

EurekaServerAutoConfiguration 定义的相关属性如下:

// 省略一部分包的导入以及注解的声明

public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
@Autowired
private ApplicationInfoManager applicationInfoManager; /*
Eureka 服务端相关的配置
*/
@Autowired
private EurekaServerConfig eurekaServerConfig; /*
Eureka 客户端的相关配置
*/
@Autowired
private EurekaClientConfig eurekaClientConfig; @Autowired
private EurekaClient eurekaClient; @Autowired
private InstanceRegistryProperties instanceRegistryProperties;
}

服务启动

有关集群和应用服务程序的注册对应的源代码:

// 省略一部分包的导入以及注解的声明

public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
// 省略部分源代码 /*
初始化集群注册表
*/
@Bean
public PeerAwareInstanceRegistry peerAwareInstanceRegistry(ServerCodecs serverCodecs) {
this.eurekaClient.getApplications(); // force initialization
return new InstanceRegistry(this.eurekaServerConfig, this.eurekaClientConfig, serverCodecs, this.eurekaClient,
this.instanceRegistryProperties.getExpectedNumberOfClientsSendingRenews(),
this.instanceRegistryProperties.getDefaultOpenForTrafficCount());
} /*
初始化集群节点集合
*/
@Bean
@ConditionalOnMissingBean
public PeerEurekaNodes peerEurekaNodes(PeerAwareInstanceRegistry registry, ServerCodecs serverCodecs,
ReplicationClientAdditionalFilters replicationClientAdditionalFilters) {
return new RefreshablePeerEurekaNodes(registry, this.eurekaServerConfig, this.eurekaClientConfig, serverCodecs,
this.applicationInfoManager, replicationClientAdditionalFilters);
} /*
Eureka 服务端上下文
*/
@Bean
@ConditionalOnMissingBean
public EurekaServerContext eurekaServerContext(ServerCodecs serverCodecs, PeerAwareInstanceRegistry registry,
PeerEurekaNodes peerEurekaNodes) {
return new DefaultEurekaServerContext(this.eurekaServerConfig, serverCodecs, registry, peerEurekaNodes,
this.applicationInfoManager);
} /*
Eureka 服务端启动类
*/
@Bean
public EurekaServerBootstrap eurekaServerBootstrap(PeerAwareInstanceRegistry registry,
EurekaServerContext serverContext) {
return new EurekaServerBootstrap(this.applicationInfoManager, this.eurekaClientConfig, this.eurekaServerConfig,
registry, serverContext);
}
}

具体的初始化类,可以看一下 EurekaServerAutoConfiguration 引入的注解:

@Configuration(proxyBeanMethods = false)
/*
可以看到,这里引入了 EurekaServerInitializerConfiguration 类型的 Bean,
该 Bean 的作用是用于初始化 Eureka Server
*/
@Import(EurekaServerInitializerConfiguration.class)
@ConditionalOnBean(EurekaServerMarkerConfiguration.Marker.class)
@EnableConfigurationProperties({ EurekaDashboardProperties.class, InstanceRegistryProperties.class })
@PropertySource("classpath:/eureka/server.properties")
public class EurekaServerAutoConfiguration implements WebMvcConfigurer {
// 省略一部分具体的代码
}

继续查看 EurekaServerInitializerConfiguration 对应的源代码,如下所示:

// 省略一部分包的导入

@Configuration(proxyBeanMethods = false)
public class EurekaServerInitializerConfiguration
/*
注意这里的 SmartLifecycle 接口,这个接口继承了 Lifecycle,
用于管理 Spring Context 的生命周期 具体到当前分析的环境,由于是初始化 Eureka Server,因此需要调用
start() 方法
*/
implements ServletContextAware, SmartLifecycle, Ordered { private static final Log log = LogFactory.getLog(EurekaServerInitializerConfiguration.class); @Autowired
private EurekaServerConfig eurekaServerConfig; private ServletContext servletContext; @Autowired
private ApplicationContext applicationContext; @Autowired
private EurekaServerBootstrap eurekaServerBootstrap; private boolean running; private int order = 1; @Override
public void setServletContext(ServletContext servletContext) {
this.servletContext = servletContext;
} @Override
public void start() {
new Thread(() -> {
try {
// TODO: is this class even needed now?
/*
Eureka Server 启动之前的初始化
*/
eurekaServerBootstrap.contextInitialized(EurekaServerInitializerConfiguration.this.servletContext);
log.info("Started Eureka Server"); /*
由于 Eureka Server 已经启动了,因此发布 Eureka Server 注册中心的启动事件
*/
publish(new EurekaRegistryAvailableEvent(getEurekaServerConfig())); // 设置 Eureka 的运行状态
EurekaServerInitializerConfiguration.this.running = true; // 现在 Eureka Server 已经完全启动了,此时再发布一个 Eureka Server 启动事件
publish(new EurekaServerStartedEvent(getEurekaServerConfig()));
} catch (Exception ex) {
// Help!
log.error("Could not initialize Eureka servlet context", ex);
}
}).start();
} // 省略一部分代码
}

由于 EurekaServerInitializerConfiguration 实现了 SmartLifecycle,因此会被 Spring 容器进行相对应的处理,这里不做过多的介绍,有关 Spring IOC 部分的源码解析,可以查看 Spring IOC 容器源码分析_Javadoop,这是一篇关于 Spring IOC 源码分析比较好的文章。

在这里只需要知道由于 EurekaServerInitializerConfiguration 实现了 SmartLifecycle 接口,因此在 Spring 应用上下文的初始化过程中会调用 start() 方法即可

接下来继续分析 contextInitialized(ServletContext) 方法,该方法用于初始化启动 Eureka Server 之前的必要准备。对应的源代码如下所示:

public void contextInitialized(ServletContext context) {
try {
/*
这里的具体实现只是单纯地打印一行日志
*/
initEurekaEnvironment(); /*
这里初始化 Eureka Server 的上下文
*/
initEurekaServerContext(); context.setAttribute(EurekaServerContext.class.getName(), this.serverContext);
}
catch (Throwable e) {
log.error("Cannot bootstrap eureka server :", e);
throw new RuntimeException("Cannot bootstrap eureka server :", e);
}
}

比较关系的是 Eureka Server 上下文的设置,继续进入该方法,对应的源代码如下所示:

protected void initEurekaServerContext() throws Exception {
// For backward compatibility
JsonXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH);
XmlXStream.getInstance().registerConverter(new V1AwareInstanceInfoConverter(), XStream.PRIORITY_VERY_HIGH); /*
针对 AWS 的特殊处理,一般情况下可能用不到,略过
*/
if (isAws(this.applicationInfoManager.getInfo())) {
this.awsBinder = new AwsBinderDelegate(this.eurekaServerConfig, this.eurekaClientConfig, this.registry,this.applicationInfoManager);
this.awsBinder.start();
} EurekaServerContextHolder.initialize(this.serverContext); log.info("Initialized server context"); // Copy registry from neighboring eureka node
/*
邻接点的数据同步
*/
int registryCount = this.registry.syncUp(); /*
服务剔除
*/
this.registry.openForTraffic(this.applicationInfoManager, registryCount); // Register all monitoring statistics.
EurekaMonitors.registerAllStats();
}

邻节点的数据同步

邻接点的同步 syncUp(),对应的源代码如下所示:

// 省略部分包的导入以及注解的声明

public class PeerAwareInstanceRegistryImpl
extends AbstractInstanceRegistry
implements PeerAwareInstanceRegistry {
// 省略部分其它代码 @Override
public int syncUp() {
// Copy entire entry from neighboring DS node
int count = 0; for (int i = 0; ((i < serverConfig.getRegistrySyncRetries()) && (count == 0)); i++) {
if (i > 0) {
try {
Thread.sleep(serverConfig.getRegistrySyncRetryWaitMs());
} catch (InterruptedException e) {
logger.warn("Interrupted during registry transfer..");
break;
}
}
/*
apps 表示获取到的所有的应用信息
*/
Applications apps = eurekaClient.getApplications();
/*
双层 Map 结构,第一层 Map 存储应用程序
*/
for (Application app : apps.getRegisteredApplications()) {
/*
第二层存储应用程序对应的实例信息
*/
for (InstanceInfo instance : app.getInstances()) {
try {
/*
服务的注册实际执行的地方
*/
if (isRegisterable(instance)) {
register(instance, instance.getLeaseInfo().getDurationInSecs(), true);
count++;
}
} catch (Throwable t) {
logger.error("During DS init copy", t);
}
}
}
}
return count;
}
}

服务剔除

服务剔除对应 openForTraffic(ApplicationInfoManager, int) 方法,实际对应的对象的源代码如下:

// 省略部分包导入代码

public class PeerAwareInstanceRegistryImpl
extends AbstractInstanceRegistry
implements PeerAwareInstanceRegistry {
// 省略部分关系不大的代码 public void openForTraffic(ApplicationInfoManager applicationInfoManager, int count) {
// Renewals happen every 30 seconds and for a minute it should be a factor of 2.
this.expectedNumberOfClientsSendingRenews = count;
updateRenewsPerMinThreshold();
logger.info("Got {} instances from neighboring DS node", count);
logger.info("Renew threshold is: {}", numberOfRenewsPerMinThreshold);
this.startupTime = System.currentTimeMillis();
if (count > 0) {
this.peerInstancesTransferEmptyOnStartup = false;
}
DataCenterInfo.Name selfName = applicationInfoManager.getInfo().getDataCenterInfo().getName(); /*
这里也是针对 AWS 的处理,一般用不到,略过
*/
boolean isAws = Name.Amazon == selfName;
if (isAws && serverConfig.shouldPrimeAwsReplicaConnections()) {
logger.info("Priming AWS connections for all replicas..");
primeAwsReplicas(applicationInfoManager);
}
// 针对 AWS 的处理结束 logger.info("Changing status to UP");
applicationInfoManager.setInstanceStatus(InstanceStatus.UP);
super.postInit();
}
}

继续查看父类的 postInit() 方法,对应的源代码如下所示:

public abstract class AbstractInstanceRegistry
implements InstanceRegistry {
// 省略部分不太重要的代码 protected void postInit() {
renewsLastMin.start();
if (evictionTaskRef.get() != null) {
evictionTaskRef.get().cancel();
}
// 这里添加一个剔除任务
evictionTaskRef.set(new EvictionTask()); // 设置这个剔除任务的执行周期
evictionTimer.schedule(evictionTaskRef.get(),
serverConfig.getEvictionIntervalTimerInMs(),
serverConfig.getEvictionIntervalTimerInMs());
}
}

这个剔除任务的定义对应的源代码如下所示:

class EvictionTask extends TimerTask {
@Override
public void run() {
try {
long compensationTimeMs = getCompensationTimeMs();
logger.info("Running the evict task with compensationTime {}ms", compensationTimeMs);
evict(compensationTimeMs);
} catch (Throwable e) {
logger.error("Could not run the evict task", e);
}
}
}

evict(int) 对应的源代码比较长,这里就不再贴出。剔除工作交给 internalCancel(String, String, boolean) 方法来完成。

更加具体一点,internalCancel(String, String, boolean) 是按照实例 id 来删除双层 Map 中对应的元素来实现的。

参考:

[1]

https://mp.weixin.qq.com/s/Xfq5YCaSSc7WgOH6Yqz4zQhttps://mp.weixin.qq.com/s/Xfq5YCaSSc7WgOH6Yqz4zQ

Spring Eureka 源码解析的更多相关文章

  1. Spring Cloud系列(四):Eureka源码解析之客户端

    一.自动装配 1.根据自动装配原理(详见:Spring Boot系列(二):Spring Boot自动装配原理解析),找到spring-cloud-netflix-eureka-client.jar的 ...

  2. Spring系列(三):Spring IoC源码解析

    一.Spring容器类继承图 二.容器前期准备 IoC源码解析入口: /** * @desc: ioc原理解析 启动 * @author: toby * @date: 2019/7/22 22:20 ...

  3. spring事务源码解析

    前言 在spring jdbcTemplate 事务,各种诡异,包你醍醐灌顶!最后遗留了一个问题:spring是怎么样保证事务一致性的? 当然,spring事务内容挺多的,如果都要讲的话要花很长时间, ...

  4. Spring IoC源码解析之invokeBeanFactoryPostProcessors

    一.Bean工厂的后置处理器 Bean工厂的后置处理器:BeanFactoryPostProcessor(触发时机:bean定义注册之后bean实例化之前)和BeanDefinitionRegistr ...

  5. Spring IoC源码解析之getBean

    一.实例化所有的非懒加载的单实例Bean 从org.springframework.context.support.AbstractApplicationContext#refresh方法开发,进入到 ...

  6. Spring系列(五):Spring AOP源码解析

    一.@EnableAspectJAutoProxy注解 在主配置类中添加@EnableAspectJAutoProxy注解,开启aop支持,那么@EnableAspectJAutoProxy到底做了什 ...

  7. Spring系列(六):Spring事务源码解析

    一.事务概述 1.1 什么是事务 事务是一组原子性的SQL查询,或者说是一个独立的工作单元.要么全部执行,要么全部不执行. 1.2 事务的特性(ACID) ①原子性(atomicity) 一个事务必须 ...

  8. Spring Boot系列(四):Spring Boot源码解析

    一.自动装配原理 之前博文已经讲过,@SpringBootApplication继承了@EnableAutoConfiguration,该注解导入了AutoConfigurationImport Se ...

  9. Spring Security源码解析一:UsernamePasswordAuthenticationFilter之登录流程

    一.前言 spring security安全框架作为spring系列组件中的一个,被广泛的运用在各项目中,那么spring security在程序中的工作流程是个什么样的呢,它是如何进行一系列的鉴权和 ...

  10. Spring Cloud系列(三):Eureka源码解析之服务端

    一.自动装配 1.根据自动装配原理(详见:Spring Boot系列(二):Spring Boot自动装配原理解析),找到spring-cloud-starter-netflix-eureka-ser ...

随机推荐

  1. 详解RecyclerView的预布局

    概述 RecyclerView 的预布局用于 Item 动画中,也叫做预测动画.其用于当 Item 项进行变化时执行的一次布局过程(如添加或删除 Item 项),使 ItemAnimator 体验更加 ...

  2. Python面向对象——1、什么是异常 2、为何处理异常 3、如何处理异常? 4、何时使用异常处理 网络编程的一些预备知识

    文章目录 异常 1.什么是异常 2.为何处理异常 3.如何处理异常? 4.何时使用异常处理 网络编程的一些预备知识 异常 1.什么是异常 异常是程序发生错误的信号.程序一旦出现错误,便会产生一个异常, ...

  3. Python面向对象——面向对象介绍、实现面向对象编程、定义类、再调用类产生对象、总结__init__方法、查找顺序

    文章目录 面向对象介绍 实现面向对象编程 一:先定义类 二:再调用类产生对象 总结__init__方法 查找顺序 面向对象介绍 ''' 面向过程: 核心是"过程"二字 过程的终极奥 ...

  4. the solution of Mining Your Own Business

    the description of problem (我看的是 PDF 里面的原题所以这里描述会和题目不一样,但是大意一致) 给定一个未必连通的无向图,问最少在几个点设置出口,可以保证任意一个点坍塌 ...

  5. Speex详解(2019年09月25日更新)

    Speex详解 整理者:赤勇玄心行天道 QQ号:280604597 微信号:qq280604597 QQ群:511046632 博客:www.cnblogs.com/gaoyaguo 大家有什么不明白 ...

  6. 飞码LowCode前端技术系列(一):数据结构设计

    简介 飞码是京东科技研发的低代码产品,可使营销运营域下web页面快速搭建.飞码是单web页面搭建工具,从创建页面到监测再到投产的一站式解决方案.会通过七篇文章介绍飞码,分别是:(1)背景与数据结构设计 ...

  7. Python 作用域:局部作用域、全局作用域和使用 global 关键字

    变量只在创建它的区域内可用.这被称为作用域. 局部作用域 在函数内部创建的变量属于该函数的局部作用域,并且只能在该函数内部使用. 示例:在函数内部创建的变量在该函数内部可用: def myfunc() ...

  8. Gitlab集成jenkins及docker自动化部署教程

    Gitlab集成jenkins及docker自动化部署教程 能实现提交代码到gitlab后,我们只需要合并代码到指定分支就可以上Jenkins自动拉取最新代码并重新构建部署 1.登录Jenkins点击 ...

  9. C#判断字符串的显示宽度

    C#判断字符串的显示宽度 起因: 公司有一个使用项目使用HTML转换为PDF,其中有一个表格,表格的最后一列中的单元格,其字符串超长后会被丢弃,而不是换行到下一行展示(HtmlToPdf渲染引擎导致的 ...

  10. 运维初级实践——Linux系统命令教程

    区块链运维工程师在Linux环境中常用的命令.快捷键,以及安装软件和文件管理的最佳实践. 1. 常用Linux命令 1.1 文件和目录操作 ls:列出目录内容 cd:更改目录 mkdir:创建新目录 ...