微服务(Java分布式)详解
1. 概念
微服务是一种软件架构模式,它将应用程序分解为一组小型、自治的服务单元。
个人理解上:微服务就是将服务拆分,让一种服务在一台或者多台电脑上运行,如下图微服务技术栈所示:

注册中心可以配置在一台或者多台电脑上,将功能拆分,n台电脑共同实现一个软件
单体架构:将业务的所有功能集中在一个项目中开发,打成一个包部署
优点:
- 架构简单
- 部署成本低
缺点:耦合度高
微服务:一种良好的分布式架构方案
优点:拆分粒度更小、服务更独立、耦合度更低
缺点:架构非常复杂,运维、监控、部署难度提高
微服务技术对比:

2. 服务的远程调用
第一步:向spring容器中注入RestTemplate对象
在启动类(Application)中加入如下代码:
package cn.itcast.order;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
// 注入
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
第二步:利用RestTemplate中getForObject方法或者postForObject方法发送请求
在业务逻辑层(一般是Service)中调用
package cn.itcast.order.service;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.pojo.Order;
import cn.itcast.order.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
// 注入
@Autowired
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId) {
// 1.查询订单
Order order = orderMapper.findById(orderId);
String url = "http://userservice/user/"+order.getUserId();
// 4.调用,利用反序列化获取对象
User user = restTemplate.getForObject(url, User.class);
order.setUser(user);
// 4.返回
return order;
}
}
3. 注册中心
Eureka
搭建Eureka服务器
1.创建Eureka服务器项目,引入spring-cloud-starter-netflix-eureka-server的依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>
注:这里不用加版本号,因为在父工程的依赖中已经指定springboot、springcloud和springcloud依赖库(spring-cloud-dependencies)
2.编写启动类,添加@EnableEurekaServer注解
3.添加application.yml文件,编写下面的配置:
server:
port:10086
spring:
application:
name: eurekaserver
eureka:
client:
service-url:
defaultZone:http://127.0.0.1:10086/eureka/
Eureka自己也是服务,此处为注册自己
向Eureka服务器注册服务
在客户端项目中引入eureka-client依赖:
<!--eureka多户端放赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
在客户端项目的application.yml中配置eureka地址
spring:
datasource:
url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
username: root
password: 123
driver-class-name: com.mysql.jdbc.Driver
application:
name: orderservice
eureka:
client:
service-url:#eureka的地址信息
defaultZone: http://127.0.0.1:10086/eureka
消费者拉取所有生产者实现负载均衡(服务发现)
1.修改业务逻辑层(Service)的代码,修改访问的url路径,用服务名代替ip、端口:
String url ="http://userservice/user/"+ order.getUserId();
2.在消费者项目的启动类Application中的RestTemplate添加负载均衡注解:
package cn.itcast.order;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
@Bean
@LoadBalanced //负载均衡注解
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
Ribbon负载均衡
原理:

策略:

默认为ZoneAvoidanceRule
通过定义IRule实现可以修改负载均衡规则,有两种方式:
1、代码方式:在order-service中的OrderApplication类中,定义一个新的IRule:
@Bean
public IRule randomRule(){
return new RandomRule();
}
2、配置文件方式:在消费者的application.yml文件中,添加新的配置也可以修改规则:
userservice:#生产者服务名
ribbon:
NFLoadBalancerRuleclassName: com.alibaba.cloud.nacos.ribbon.NacosRule #负载均衡规则
特点:懒加载
Ribbon默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:
ribbon :
eager-load:
enabled:true#开启饥饿加载
clients:userservice#指定对userservice这个服务饥饿加载
Nacos
Nacos是阿里巴巴的产品,现在是Springcloud中的一个组件。相比Eureka功能更加丰富,在国内受欢迎程度较高。
安装
Windows安装:
开发阶段采用单机安装即可。
1.下载安装包
在Nacos的GitHub页面,提供有下载链接,可以下载编译好的Nacos服务端或者源代码:
GitHub主页:https://github.com/alibaba/nacos
GitHub的Release下载页:https://github.com/alibaba/nacos/releases
2.解压
将这个包解压到任意非中文目录下
目录说明:
- bin:启动脚本
- conf:配置文件
3.端口配置
Nacos的默认端口是8848,如果你电脑上的其它进程占用了8848端口,请先尝试关闭该进程。
如果无法关闭占用8848端口的进程,也可以进入nacos的conf目录,修改配置文件中的端口:

4.启动
启动非常简单,进入bin目录,结构如下:
然后执行命令即可:
- windows命令:
startup.cmd -m standalone
5.访问
在浏览器输入地址:http://127.0.0.1:8848/nacos即可:
默认的账号和密码都是nacos,进入后:
Linux安装:
Linux或者Mac安装方式与Windows类似。
1.安装JDK
Nacos依赖于JDK运行,索引Linux上也需要安装JDK才行。
上传jdk安装包
上传到某个目录,例如:/usr/local/
然后解压缩:
tar -xvf jdk-8u144-linux-x64.tar.gz
然后重命名为java
配置环境变量:
export JAVA_HOME=/usr/local/java
export PATH=$PATH:$JAVA_HOME/bin
设置环境变量:
source /etc/profile
2.上传安装包
上传到Linux服务器的某个目录,例如/usr/local/src目录下:
3.解压
命令解压缩安装包:
tar -xvf nacos-server-1.4.1.tar.gz
然后删除安装包:
rm -rf nacos-server-1.4.1.tar.gz
4.端口配置
与windows中类似
5.启动
在nacos/bin目录中,输入命令启动Nacos:
sh startup.sh -m standalone
服务注册到nacos
1.在cloud-demo父工程中添加spring-cloud-alilbaba的管理依赖:
父工程:
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.5.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
如果存在Eureka依赖,注释掉原有的eureka依赖
2.添加nacos的客户端依赖:
客户端:
<!-- nacos客户端依赖包 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
Nacos服务分级存储模型

userservice功能——》区域集群(上海、杭州)——》每个区域下多个的实例(userservice代码)
配置文件中加入集群设置:
spring:
datasource:
url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false
username: root
password: 123
driver-class-name: com.mysql.jdbc.Driver
application:
name: userservice
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: SH
cluster-name为集群名
NacosRule负载均衡(消费者调用生产者会优先调用同集群的生产者)
1、设置集群名称:
spring:
cloud:
nacos:
server-addr:localhost:8848#nacos服务端地址
discovery:
cluster-name:Hz#配置集群名称,也就是机房位置
2、然后在消费者的yml文件中中设置负载均衡的IRule为NacosRule,这个规则优先会寻找与自己同集群的服务
userservice:
ribbon:
NFLoadBalancerRuleclassName: com.alibaba.cloud.nacos.ribbon.NacosRule# 负载均衡规则
3、注意将user-service的权重都设置为1,权重0-1,越大被访问的概率越大,权重设置为0则完全不会被访问(可以用来升级更新服务)
Nacos环境隔离

命名空间->分组->服务
- namespace用来做环境隔离
- 每个namespace都有唯-id
- 不同namespace下的服务不可见
实现:
spring:
datasource:
url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
username: root
password: 123
driver-class-name: com.mysql.jdbc.Driver
application:
name: orderservice
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ
namespace: b5cf9721-b0f7-437c-90b6-8dff03e16254 #命名空间id
Nacos与Eureka区别
1.Nacos与eureka的共同点
都支持服务注册和服务拉取
都支持服务提供者心跳方式做健康检测
2.Nacos与Eureka的区别
Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式临时实例心跳不正常会被剔除,非临时实例则不会被剔除
Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式
Nacos配置管理
Nacos统一配置管理(先在Nacos中配置好配置文件)


微服务拉取上述Nacos配置
原理:

项目启动时会比读取application.yml更早读取bootstrap.yml文件,故而nacos配置文件的地址可以写在bootstrap.yml文件中
实现:
- 引入Nacos的配置管理客户端依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
因为在子项目中不用写版本号,在父项目的pom文件的依赖库中已经定义
- 在需要配置服务的项目中的resource目录添加一个bootstrap.yml文件,这个文件是引导文件,优先级高于application.yml:
spring:
application:
name: userservice
profiles:
active: dev
cloud:
nacos:
server-addr: localhost:8848
config:
server-addr: localhost:8848 #配置文件的地址不写会找不到,报空指针错误
file-extension: yaml
校验代码(Controller中获取nacos中的配置文件):
package cn.itcast.user.web;
import cn.itcast.user.pojo.User;
import cn.itcast.user.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@Value("${pattern.dateformat}")
private String dateformat;
@GetMapping("/now")
public String now(){
System.out.println("================================="+dateformat);
return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
}
/**
* 路径: /user/110
*
* @param id 用户id
* @return 用户
*/
@GetMapping("/{id}")
public User queryById(@PathVariable("id") Long id) {
return userService.queryById(id);
}
}
效果:
输出nacos中配置的字符串,成功获取到
配置优先级:

Feign
远程调用
- 导包
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
-
启动类加注解@EnableFeignClients
-
写Client接口
import cn.itcast.order.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient("userservice")
public interface UserClient {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
- Service中自动注入,调用
自定义配置
Feign的日志配置:
1.方式一:配置文件,feign.client.config.xxx.loggerLevel
①如果xxx是default则代表全局
②如果xxx是服务名称,例如userservice则代表某服务
2.方式二:java代码配置Logger.Level这个Bean
①如果在@EnableFeignClients注解声明则代表全局
②如果在@FeignClient注解中声明则代表某服务
性能优化
1.日志级别尽量用basic
2.使用HttpClient或OKHttp代替URLConnection
①引入feign-httpclient依赖
②配置文件开启httpClient功能,设置连接池参数
最佳实践
feign的最佳实践:
方式一:让controller和FeignClient继承同一接口

方式二:将FeignClient、POj0、Feign的默认配置都定义到一个项目中,供所有消费者使用

实现最佳实践:
抽取FeignClient:
实现最佳实践方式二的步骤如下:
1.首先创建一个module,命名为feign-api,然后引入feign的starter依赖
2.将order-service中编写的UserClient、User、DefaultFeignConfiquration都复制到feign-api项目中
3.在order-service中引入feiqn-api的依赖
4.修改order-service中的所有与上述三个组件有关的import部分,改成导入feiqn-api中的包
5.重启测试
出现问题:找不到生产者客户端,原因:生产者客户端已经被注入到feign服务中,解决方案如下:
SpringBootApplication的扫描包范围时,这些FeignClient无法使用。有两种方式解决:
方式一:指定Feignclient所在包
@EnableFeignClients(basePackages = "cn.itcast.feign.clients")
方式二:指定FeignClient字节码
@EnableFeignclients(clients = {Userclient.class})
gateway网关
作用介绍
- 对用户请求做身份认证、权限校验
- 将用户请求路由到微服务,并实现负载均衡
- 对用户请求做限流
网关搭建步骤
1.创建项目,引入nacos服务发现和gateway依赖
<!--nacos服务注册发现依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--网关gateway依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
2.配置application.yml,包括服务基本信息、nacos地址、路由
其中路由配置包括:
1.路由id:路由的唯一标示
2.路由目标(uri):路由的目标地址,http代表固定地址,Ib代表根据服务名负载均衡
3.路由断言(predicates):判断路由的规则,路由过滤器(filters):对请求或响应做处理
4.路由过滤器(filters):对请求或响应做处理
内容如下:
gateway:
routes:
id:user-service # 路由标示,必须唯一
uri:Lb://userservice # 路由的目标地址
predicates:# 路由断言,判断请求是否符合规则
-Path=/user/** # 路径断言,判断路径是否是以/user
id: order-service
uri: Lb://orderservice
predicates:
- Path=/order/**
路由断言工厂
总结:
-
PredicateFactory(路由工厂)的作用是什么?
读取用户定义的断言条件,对请求做出判断 -
Path=/user/*是什么含义?
路径是以/user开头的就认为是符合的
路由的过滤器配置
gateway:
routes:
id:user-service #路由标示,必须唯一uri:Lb://userservice# 路由的目标地址
predicates:# 路由断言,判断请求是否符合规则
Path=/user/** # 路径断言,判断路径是否是以/user开头,如果是则符合
filters:
— AddRequestHeader=Truth,Itcast is freaking awesome!
id: order-service
uri: Lb://orderservice
predicates:
-Path=/order/**
default-filters:
- AddRequestHeader=Truth,Itcast is freaking awesome!
其中filters为过滤器,default-filters为默认过滤器
总结:
过滤器的作用是什么?
- 对路由的请求或响应做加工处理,比如添加请求头
- 配置在路由下的过滤器只对当前路由的请求生效
defaultFilters的作用是什么?
- 对所有路由都生效的过滤器
全局过滤器
自定义类,实现GlobalFilter接口,添加@Order注解:
@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter {
@0verride
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//1.获取请求参数
MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
//2.获取authorization参数
String auth = params.getFirst("authorization");
//3.校验
if ("admin".equals(auth)) {
/放行
return chain.filter(exchange);
}
//4.拦截
//4.1.禁止访问
exchange.getResponse ().setStatusCode (HttpStatus.FORBIDDEN) ;
//4.2.结束处理
return exchange.getResponse().setComplete();
}
}
Order中的数字越大,优先级越低,-1优先级最高,也可通过实现Ordered实现Order注解功能
总结:
-
全局过滤器的作用是什么?
对所有路由都生效的过滤器,并且可以自定义处理逻辑 -
实现全局过滤器的步骤?
- 实现GlobalFilter接口
- 添加@Order注解或实现Ordered接口
- 编写处理逻辑
过滤器链执行顺序
过滤器执行顺序
- 每一个过滤器都必须指定一个int类型的order值, order值越小,优先级越高,执行顺序越靠前。
- GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定
路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。 - 当过滤器的order值一样时,会按照 defaultFilter> 路由过滤器 > GlobalFilter的顺序执行。
可以参考下面几个类的源码来查看:
org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#getFilters()
方法是先加载defaultFilters,然后再加载某个route的filters,然后合并。
org.springframework.cloud.gateway.handler.FilteringWebHandler#handle()
方法会加载全局过滤器,与前面的过滤器合并后根据order排序,组织过滤器链
总结:
路由过滤器、defaultFilter、全局过滤器的执行顺序?
① order值越小,优先级越高
②当order值一样时,顺序是defaultFilter最先,然后是局部的路由过滤器,最后是全局过滤器
网关的CORS跨域配置
网关处理跨域采用的同样是CORS方案,并且只需要简单配置即可实现:
spring:
cloud:
gateway:
globalcors:#全局的域处理
add-to-simple-url-handler-mapping: true#解决options请求被拦截问题
corsconfigurations:
'[/**]':
allowed0rigins:#允许哪些网站的请求
- "http: //localhost: 8090
- "http: //ww.Leyou.com"
allowedMethods:#允许的跨域ojox的请求方式
- "GET"
- "POST
- "DELETE"
-"PUT"
- "OPTIONS"
allowedHeaders:"*"#允许在请求中携带的头信息
ollowCredentials: true#否cookie
maxAge:360000#这次检测的有效期
Docker
定义
Docker 是一个开源的应用容器引擎,基于操作系统级虚拟化技术(Linux Namespace 和 Cgroups),将应用程序及其所有依赖项(库、环境变量、配置文件)打包成标准化的单元(镜像),实现“一次构建,到处运行“(跳过系统应用,直接调用Linux系统内核)
架构
Docker是一个CS架构的程序,由两部分组成:
- 服务端(server):Docker守护进程,负责处理Docker指令,管理镜像、容器等
- 客户端(client):通过命令或RestAPI向Docker服务端发送指令。可以在本地或远程向服务端发送指令。

其中docker build用来自己构建镜像,docker pull用来拉取镜像,docker run用来运行容器
docker包含以下部分:
- 镜像:将应用程序及其依赖、环境、配置打包在一起
- 容器:镜像运行起来就是容器,一个镜像可以运行多个容器
- Docker结构:服务端:接收命令或远程请求,操作镜像或容器;客户端:发送命令或者请求到Docker服务端
- DockerHub:一个镜像托管的服务器,类似的还有阿里云镜像服务,统称为DockerRegistry
安装
-
安装Docker
Docker分为CE和EE两大版本。CE即社区版(免费,支持周期7个月),EE即企业版,强调安全,付费使用,支持周期24个月。
Docker CE分为stabletest和nightly三个更新频道。
官方网站上有各种环境下的安装指南,这里主要介绍Docker CE在CentOS上的安装。 -
CentS安装Docker
Docker CE支持64位版本CentOS7,并且要求内核版本不低于3.10,CentOS7满足最
低内核的要求,所以我们在CentOS7安装Docker。
- 卸载(可选)
如果之前安装过旧版本的Docker,可以使用下面命令卸载:
yum remove docker \
docker-client \
docker-client-latest \
docker-commondocker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-selinux \
docker-engine-selinux \
docker-engine \
docker-ce
- 安装docker
首先需要大家虚拟机联网,安装yum工具
yum install -y yum-utils \
deyice-mapper-persistent-data \
lvm2 --skip-broken
然后更新本地镜像源:
# 设置docker镜像源
yum-config-manager \
--add-repo \
https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
sed -i 's/download.docker.com/mirrors.aliyun.com\/docker-ce/g'
/etc/yum.repos.d/docker-ce.repo
yum makecache fast
然后输入命令:
yum install -y docker-ce
docker-ce为社区免费版本。稍等片刻,docker即可安装成功。
- 启动docker
Docker应用需要用到各种端口,逐一去修改防火墙设置。非常麻烦,因此建议大家直接关闭防火墙!
#关闭
systemctl stop firewalld
#禁止开机启动防火墙
systemctl disable firewalld
通过命令启动docker:
systemctl start docker #启动docker服务
systemctl stop docker #停止docker服务
systemctl restart docker #重启docker服务
然后输入命令,可以查看docker版本:
docker -v
- 配置镜像
docker官方镜像仓库网速较差,我们需要设置国内镜像:
参考阿里云的镜像加速文档:https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors
基本命令
镜像命令

容器命令

- docker run命令的常见参数有哪些?
- -name:指定容器名称
- -p:指定端口映射
- -d:让容器后台运行
- 查看容器日志的命令:
- docker logs
- 添加-f参数可以持续查看日志
- 查看容器状态:
- docker ps
- 添加-a参数查看所有状态的容器(包含未启动)
- 进入容器命令:
docker exec -it mn bash
- docker exec : 进入容器内部,执行一个命令
- -it : 给当前进入的容器创建一个标准输入、输出终端,允许我们与容器交互
- mn : 要进入的容器的名称
- bash: 进入容器后执行的命令,bash是一个linux终端交互命令
镜像仓库官网:https://hub.docker.com/
容器内部只有基本Linux命令,没有vi命令,可以用sed代替
exec命令可以进入容器修改文件,但是在容器内修改文件是不推荐的
- 删除容器:
- docker rm
- 不能删除运行中的容器,除非添加 -f 参数
数据卷命令
数据卷(volume)是一个虚拟目录,指向宿主机文件系统中的某个目录。

我理解上本质上是将容器内部文件建立关联,使改外部文件容器内部也会跟着改
基本语法:
docker volume [COMMAND]
docker volume命令是数据卷操作,根据命令后跟随的command来确定下一步的操作:
- create - 创建一个volume
- inspect - 显示一个或多个volume的信息
- ls - 列出所有的volume
- prune - 删除未使用的volume
- rm - 删除一个或多个指定的volume
解决容器与数据耦合问题,即不便修改容器内部数据问题
总结
Docker是一个快速交付应用、运行应用的技术:
- 可以将程序及其依赖、运行环境一起打包为一个镜像,可以迁移到任意Linux操作系统
- 运行时利用沙箱机制形成隔离容器,各个应用互不干扰
- 启动、移除都可以通过一行命令完成,方便快捷
Docker和虚拟机的差异:
- docker是一个系统进程;虚拟机是在操作系统中的操作系统度慢、性能一般
- docker体积小、启动速度快、性能好;虚拟机体积大、启动速

浙公网安备 33010602011771号