微服务的架构图:
微服务技术完整:
单体架构:
概念:
将业务的所有功能集中再一个项目中开发,打包成一个包进行部署。
优点:
a.架构简单
b.部署成本低
缺点:
c.耦合度高
分布式架构 :
概念:根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,成为一个服务。
优点:
a.降低服务耦合
b.有利于服务升级拓展
缺点:
c.架构非常复杂
d.运维,监控,部署难度提高
待解决的问题:
c.服务拆分粒度如何处理?
d.服务集群地址如何维护?
e.服务之间如何实现远程调用?
f.服务健康状态如何感知?
概念:
微服务是一种经过良好架构设计的分布式架构方案。
特征:
a.单一职责:微服务拆分粒度更小,每一个服务都对应唯一的的业务能力,做到单一职责,避免重复开发。
b.面向服务:微服务对外暴露业务接口。
c.自治:团队独立,技术独立,数据独立,部署独立。
d.隔离性强:服务调用做好隔离,容错,降级,避免出现级联问题。
概念:
SpringCloud是目前国内最广泛的服务框架,其官网地址: https://spring.io/projects/spring-cloud
springcloud与springboot版本兼容:
服务拆分:
实际案例:
订单信息和用户信息 服务拆分。
a.不同微服务,不要重复开发相同业务。
b.微服务数据独立,不要访问其他微服务的数据库。
c.微服务可以将自己的业务暴露成接口,供其他微服务调用。
拆分实例demo问题解决:
Q:导入项目时遇到 java发行版本错误5
?
A:解决方案1:添加以下内容
<properties>
<java.version>1.8java.version>
properties>
解决方案2:添加以下内容:
<properties>
<maven.compiler.source>1.8maven.compiler.source>
<maven.compiler.target>1.8maven.compiler>
properties>
远程调控
实际案例:根据订单id查询订单的同时,吧订单所属用户信息一起返回。
RestTemplate的使用
使用条件:
①基于RestTemplate发起的http请求实现远程调用。
②http请求做远程调用是与语言无关的调用,只有知道对方的ip,端口,接口路径,请求参数即可。
使用示例:
1.在 主启动类(配置类) 下注入RestTemplate对象:
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
//将RestTemplate注入容器
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
2.在orderService中发起http请求:
@Service
public class OrderService{
@Autowired
private OrderMapper orderMapper;
//自动注入restTemplate对象
private RestTemplate restTemplate;
public Order queryOrderById(Long orderId){
//查询订单
Order order = orderMapper.findById(orderId);
//利用RestTemplate发起http请求,查询用户
String url = "http://localhost:8081/user/"+order.getUserId();
//GET方式访问
User user = restTemplate.getForObject(url,User.class);
order.setUser(user);
}
}
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args) {
SpringApplication.run(OrderApplication.class, args);
}
//将RestTemplate注入容器
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
测试问题:
跨服务器的远程调用需要双开服务器,否则会报 Connection refused:connect
错误。
提出问题:
①服务消费者该如何获取服务提供者的地址信息?
答:服务提供者启动时向eureka注册自己的信息。euraka保存这些信息。消费者根据服务名向eureka拉去提供者信息。
②如果有多个服务提供者,消费者如何选择?
答:负载均衡算法选择其中一个。
③消费者如何得知服务提供者的健康状态?
答:服务提供者每隔30s向EurakaServer发起心跳,报告健康状态。eureka会更新记录服务列表信息,心跳不正常会被剔除。消费者就可以拉渠道最新的信息。
总结:
在Euraka架构中,微服务角色有两类:
1.EurakaServer:服务端,注册中心。其包含功能:
①记录服务信息。
②心跳监控(轮询机制)。
2.EurakaClient:客户端。其包含功能:
①Provider:服务提供者,其需要做两件事:
Ⅰ.注册自己信息到EurakaServer
Ⅱ.每隔30s向EurakaServer发送心跳。
② consumer:服务消费者。其需要做两件事:
Ⅰ.根据服务名称从EurakaServer拉取服务列表。
Ⅱ.基于服务列表做负载均衡,选一个微服务后发起远程调用。
1.引入euraka-server依赖
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-serverartifactId>
<version>2.2.7.RELEASEversion>
dependency>
2.添加@EnableEurakaServer注解
@SpringBootApplication
@EnableEurekaServer
public class EurakaApplication {
public static void main(String[] args) {
SpringApplication.run(EurakaApplication.class,args);
}
}
3.在application.yml中配置euraka地址信息。
server:
port: 10086
spring:
application:
name: eurekaserver
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka
操作步骤:
1.引入eureka-client依赖。
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-netflix-eureka-clientartifactId>
<version>2.2.7.RELEASEversion>
dependency>
2.在application.yml配置以下内容。
spring:
application:
name: xxxserver
eureka:
client:
service-url:
defaultZone: http://localhost:10086/eureka
采用改端口拷贝方式解决端口冲突问题。
选中 服务栏的要双开的服务 Ctrl+D 复制项目,修改端口(在environment中的VM options输入-Dserver.port=8082
)。
概念:基于服务名称获取服务列表,然后再对服务列表做负载均衡。
步骤:
1.修改OrderService的代码,修改访问的url路径,用服务名称代替IP,端口:
String url = "http://userservice/user/"+order.getUserId();
2.再order-service项目的启动类OrderApplication中的RestTemplate添加 负载均衡 注解:
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
※切记勿忘添加注解,第一次测试无法访问原因是未加注解,根据名称无法访问到。
流程图:
细则流程图:
IRure中的策略:
默认策略:(ZoneAvoidanceRule
):在一个区域Zone的服务进行轮询。
主观修改轮询规则的方法:
1.再服务启动类中定义一个新的IRule:(作用于全局,所有服务均为随机)
@Bean
public IRule randomRule(){
//返回值可以是IRule中的任意一种Rule
return new RandomRule();
//样例写法即为随机选择服务
}
2.在yml文件中配置轮询规则:(作用于局部,仅作用域服务名称对应服务)
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #负载均衡规则
饥饿加载
概念:默认是采用懒加载,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:
ribbon:
eager-load:
enabled: true #开启饥饿加载
clients: #指定饥饿加载的名称
-orderservice
-xxx
※ 通过实例测试,饥饿加载的网页运行效率是之前的十倍有余。
Nacos简介:阿里巴巴的产品,是SpringCloud中一个组件,相比eureka功能更加丰富,在国内受欢迎程度更高。
主页地址:https://github.com/Netflix/eureka
下载页:https://github.com/alibaba/nacos/releases
执行命令:
startup.cmd -m standalone
# 单机模式启动
进入弹出网址,登录。(初始用户名和密码均为nacos
)
步骤:
1.添加依赖
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-alibaba-dependenciesartifactId>
<version>2.2.5.RELEASEversion>
<type>pomtype>
<scope>importscope>
dependency>
2.去掉eureka的依赖
3.去掉yml文件中eureka的配置。
4.添加nacos的配置信息:
cloud:
nacos:
server-addr: localhost:8848
内容:
①一级是服务,例如userservice
②二级是集群,例如杭州or上海集群
③三级是实例,例如杭州机房的某台部署了userservice的服务器
设置步骤:
修改application.yml文件,做以下修改:
spring:
cloud:
nacos:
discovery:
cluster-name: com.alibaba.cloud.nacos.ribbon.NacosRule #集群名
NacosRule负载均衡规则:
①优先选择同集群服务实例列表
②本地集群找不到提供者,才回去其他集群寻找,并且会报错警告
③确定了可用实例列表后,再采用随机负载均衡挑选实例
根据权重负载均衡:
内容:Nacos提供权重控制负载均衡规则。
步骤:再nacos界面中设置服务权重(0~1),权重大的服务实例更容易被访问到。比如,权重为0.1与1的服务实例,1的访问概率约为0.1的十倍。权重为0时服务不会被访问到。
内容:
①namespace来做环境隔离
②每个namespace都要唯一id
③不同namespace下的服务不可见。
使用步骤:
再yml配置文件中做以下配置:
spring:
cloud:
nacos:
discovery:
namespace: xxx #此处填写命名空间的uid
共同点:
①都支持服务注册和服务拉取
②都支持服务提供者心跳方式做健康检测
不同点:
①Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时模式采用主动检测模式。
设置非临时实例的方法,再yml配置文件中修改以下内容:
spring:
cloud:
nacos:
discovery:
ephemeral: false #是否为临时文件
②临时实例心跳不正常会被剔除,非临时实例不会被剔除。
③Nacos支持服务列表变更的推送模式,服务列表更新及时。
④Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式,Eureka采用AP模式。
进入 nacos->配置中心->新建配置->命名配置名为 【服务名-开发模式.yaml】->补充配置内容
步骤即可。
命名模式样例:userservice-dev.yaml -> 用户服务的开发者模式
配置内容样例:
patterns:
dateformat: yyyy-MM-dd HH-mm-ss
由于项目启动时会优先访问 nacos 的共有配置文件,故需提前得知nacos的内存地址,其存储在服务的bootstrap.yaml(该文件优先级远高于服务的配置文件)中。
步骤:
①引入Nacos的配置管理客户端依赖:
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-configartifactId>
dependency>
②在userservice中的resource目录添加一个bootstrap.yml
文件,这个文件是引导文件,优先级高于application.yml:
spring:
application:
name: userservice #服务名称
profile:
active: dev #开发环境
cloud:
nacos:
server-addr: localhost:8848 #Nacos地址
config:
file-extension: yaml #文件名后缀
样例测试展示:
在nacos中新建一个统一配置管理。
按照以上配置依次配。
最后在userservice服务中进行测试:
@RestController
@RequestMapping("/user")
public class UserController {
//注入nacos的配置属性
//此处@Value可换为nacosValue
//@Value(${key})注解可以获取到 配置文件中的属性值
@NacosValue("${pattern.dateformat}")
private String dateformat;
//编写controller,通过日期格式化器格式化现在时间并返回
@GetMapping("now")
public String now(){
return LocalDate.now().format(
DateTimeFormatterl.ofPattern(dateformat,Locale.China))
}
}
配置热更新:
方法一:在服务的Controller类上加 @RefreshScope
注解实现热刷新。
@RefreshScope 注解需配合 @Value注解一同使用。
方法二:创建一个专门的配置类实现热刷新:(加入@ConfigurationProperties注解)
@Component
@Data
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
private String dateformat;
}
多环境共享配置
优先级排序:服务名-profile.yaml > 服务名.yaml > 本地配置。
P29详细讲解
认识集群(结构图):
数据库集群的作用是同步Nacos注册中心的数据。
搭建步骤:
1)搭建数据库集群,初始化数据库表结构
2)配置nacos
集群配置,数据库配置…
3)启动nacos集群(多个nacos节点)
4)使用nginx实现反向代理
之前利用RestTemplate发起远程调用的代码:
String url = "http://userservice/user/"+order.getUserId();
User user = restTemplate.getForObject(url,User.class);
分析问题:
1.代码可读性差。
2.参数复杂URL难以维护。
概念:Feigh是一个声明式的http客户端。
地址:https://github.com/OpenFeigh/feigh
使用步骤:
①在使用者上引入依赖:
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
②加Feigh的注解:
@EnableFeighClients
@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {
public static void main(String[] args){
SpringApplication.run(OrderApplication.class,args);
}
}
③编写Feigh客户端:
首先明确发起请求的五个必要信息:
服务名称:userservice
请求方式:GET
请求路径:/user/{id}
请求参数:Long id
返回值类型:User
使用Feigh仅需要将以上信息交给Feigh即可:
@FeighClient("userservice")
public interface UserClient {
@GetMapping
User findById(@PathVariable("id") Long id);
}
※测试时注意去掉有关于RestTemplate的配置:包括yml中的namespace和
在Order的service层进行测试:
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private UserClient userClient;
public Order queryOrderById (Long orderId){
Order order = orderMapper.findById(orderId);
User user = userClient.findById(order.getUserId());
order.setUser(user);
return order;
}
}
自定义设置Feign的日志:
方式一(配置文件yml):
① 全局生效
feign:
client:
config:
default: #全局配置
loggerLevel: FULL #日志级别
② 局部生效:
feign:
client:
config:
userservice: #局部配置
loggerLevel: FULL
方式二(java代码):
声明一个配置类:
public class FeignClientConfiguration {
@Bean
public Logger.Level feignLogLevel(){
return Logger.Level.BASIC;
}
}
全局配置:将以下注解放在启动类上:
@EnableFeignClients(defaultConfiguration = FeignClientConfiguration.class)
局部配置,将以下注解放在配置类上:
@FeignClient(value = "userservice",configuration = FeignClientConfiguration.class)
Feign的底层客户端实现:
1.URLConnection:默认实现,不支持连接池。
2.Apache HttpClient:支持连接池。
3.OKHttp:支持连接池。
优化方式:
①使用连接池代替默认的URLConnection。
②日志级别,最好使用basic或者none。
优化步骤:
①引入依赖:
<dependency>
<groupId>io.github.openfeigngroupId>
<artifactId>feign-httpclientartifactId>
dependency>
②修改配置文件
feign:
httpclient:
enabled: true #开启HttpClient开关
max-connection: 200 #最大连接数
max-connections-per-route: 50 #单个路径的最大连接数
方式一(继承):
给消费者的FeignClient和 提供者的controller 定义统一的父接口作为标准。
public interface UserAPI {
@GetMapping("/user/{id}")
User findById(@PathVariable("id") Long id);
}
但该方案也有一定缺点:
①由于二者的API完全吻合,从而造成了服务紧耦合,不宜维护和修改。
②父接口参数列表中的映射(即@PathVariable)不会被继承。
方式二(抽取):
将FeignClient抽取为独立模块,并且吧接口有关的POJO,默认的Feign配置都放在该模块中,提供所有消费者使用。
实现步骤:
①创建一个module,命名为feign-api,然后引入feign的starter依赖。
②将order-service中编写的UserClient,User,DefaultFeignConfiguration都复制到feign-api项目中。
③在order-service中引入feign-api的依赖。
④修改order-service中的所有与上述三个组件有关的import导包部分,改成导入feign-api中的包。
⑤重启测试即可。
缺点:
该方案客户在引入依赖时,会引入许多多余的方法,造成内存的膨胀。
值得注意的一点:
当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用(即无法自动注入,spring容器中没有对应类创建号的对象),采用以下方法解决:
方式一:指定FeignClient所在的包
@EnableFeignClients(basePackages = "cn.itcast.feign.clients")
方式二:指定FeignClient字节码
@EnableFeignClients(clients = {UserClient.class})
网关的功能:
①身份认证和权限校验
②服务路由,负载均衡
③请求限流
网关的技术实现:
步骤:
1.创建新的module,引入SpringCloudGateway的依赖和nacos的服务发现依赖:
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
2.编写路由配置及nacos地址
server:
port: 10010 # 网关端口
spring:
application:
name: gateway #服务名称
cloud:
nacos:
server-addr: localhost:8848 # nacos地址
gateway:
routes: #网关路由配置
- id: user-service #路由id,只要唯一即可
uri: lb://userservice #路由的目标地址,lb是负载均衡,后面是服务名称
predicates: #路由断言,就是判断请求是否符合路由规则的条件
- Path=/user/** # 只要以/user开头就符合要求
路由断言工厂:
读取断言规则,从而对用户发起的路由做判断。
作用:可以对网关的请求和微服务返回的响应做处理:
案例:给所有进入userservice的请求添加一个请求头:Truth=NB
实现方式:在gateway中修改application.yml文件,给userservice的路由添加过滤器:
spring:
cloud:
gateway:
routes: #网关路由配置
- id: user-service
uri: lb://userservice
predicates:
- Path:/user/**
filters: #过滤器
- AddRequestHeader=Truth,NB # 添加请求头
default-filters: #给所有服务增加请求头
- AddRequestHeader=Truth,NB
全局过滤器GlobalFilter
概述:其处理一切进入网关的请求和微服务响应,与GatewayFilter作用一样。区别在于前者通过配置定义,处理逻辑固定,而后者逻辑需要自己写代码实现。定义方式是 实现GlobalFilter接口。
接口源码:
public interface GlobalFilter {
/**
* @param exchange 请求上下文,可获取到Request,Response等信息
* @param chain 用来吧请求委托给下一个过滤器
* @return 返回标识当前业务结束
*/
Mono<void> filter(ServerWebExchange,Gateway);
}
案例:定义全局过滤器,拦截请求,判断参数是否满足:
①参数中是否含有 authorization
②authorization参数值是否为admin
如果同时满足①②,则放行。
自定义过滤器:
@Order(-1) //排序,该组件的优先级(-1较高)
@Component //组件,将过滤器注入spring容器
public class AuthorizeFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange,GatewayFilterChain chain){
//获取请求参数
MultiValueMap<String,String> params = exchange.getRequest().getQueryParams();
//获取参数
String auth = params.getFirst("authorization");
//校验
if("admin".equals(auth)){
//放行
return chain.filter(exchange);
}
//拦截
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return exchange.getResponse().setComplete();
}
}
过滤器执行顺序法则:
①order值越小,优先级越高。
②当order值一样时,顺序是defaultFilter优先,然后是局部的路由过滤器,最后是全局过滤器。
跨域的概念:
①域名不同:www.taobao.com & www.taobao.org
②域名相同,端口不同。
跨域问题:浏览器 禁止请求发起者和服务端发生跨域 ajax 请求,请求被浏览器拦截的问题。
配置信息展示:
spring:
cloud:
gateway:
globalcors:
add-to-simple-url-handler-mapping: true #解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: #允许哪些网站的跨域请求
- "http://localhost:8090"
- "http://www.leyou.com"
allowedMethods: #允许跨越ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" #允许在请求中携带的头信息
allowCredentials: true #是否允许携带Cookie
maxAge: 360000 #跨域检测的有效期
为什么要使用docker?
①依赖关系复杂,容易出现兼容性问题。
②开发测试生产环境有差异。
docker如何解决依赖的兼容问题?
①将应用的函数库,依赖,配置与应用一起打包,形成 可移植镜像。
②将每个应用放到一个隔离容器去运行(沙箱机制),避免互相干扰。
Docker如何解决不同系统环境的问题?
①Docker将用户程序与所需要调用的系统(Ubuntu,CentOS)函数库一起打包。
②Docker运行到不同操作系统时,直接基于打包的库函数,借助于操作系统的Linux内核来运行。
虚拟机(virtual machine)是在操作系统中模拟硬件设备,然后运行另一个操作系统,比如在Windows上运行Ubuntu系统。使用了 Hypervisor 技术
对比:
①性能:Docker更接近原生,而虚拟机性能较差。
②硬盘占用:Docker一般为MB,虚拟机一般为GB。
③启动速度:Docker以秒为单位,虚拟机以分钟级为单位。
④本质:Docker是一个系统进程,虚拟机是在操作系统中的另一套操作系统。
镜像(Image):
镜像是只读的!!!Docker将应用程序及其所需的依赖,函数库,环境,配置等文件打包在一起。
容器(Container):
镜像中的应用程序运行后形成的进程就是容器,只是Docker会给容器做隔离,对外部可见。
DockerHub:是一个Docker镜像的托管平台。这样的平台叫做Docker Registry。
栗子:网易云镜像服务,阿里云镜像服务…
Docker是一个 C/S架构 的程序,由两部分组成:
♦ 服务端(server):Docker守护进程,负责处理Docker指令,管理镜像,容器等
♦ 客户端(client):通过命令或RestAPI向Docker服务端发送指令。可在本地或远程向服务端发送指令。
必须是在64位的CentOS7系统下,内核不能低于3.10,CentOS 7 满足最低内核要求。
卸载Docker:执行以下命令
yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-selinux \
docker-engine-selinux \
docker-engine \
docker-ce
安装yum工具:
yum install -y yum-utils \
device-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
安装Docker:(ce为社区版)
yum install -y docker-ce
启动Docker前一定要关闭防火墙!!!
启动Docker前一定要关闭防火墙!!!
启动Docker前一定要关闭防火墙!!!
关闭命令:
# 关闭防火墙
systemctl stop firewalld
# 防止开机自启
启动docker:
sudo systemctl start docker
查看启动是否成功:
docker info
镜像的命名:
由两部分组成:[repository]:[tag]
栗子:mysql:5.7
tag的默认值是latest,代表最新版本。
样例1:从DockerHub上拉取一个nginx镜像并查看。
1.访问hub.docker.com页面,搜索nginx关键字。
2.拉取自己需要的镜像即可,通过命令docker pull nginx)(默认最新版)(sudo?)
3.通过命令 docker images 查看镜像。
样例2:
1.通过命令 将nginx:lastest镜像导出到nginx.tar包中。
docker save -o nginx.tar nginx:lastest
2.删除本地的nginx镜像 命令 。
docker rmi nginx:lastest
3.重新将nginx镜像加载到本地镜像 命令 。
docker load -i nginx.tar
不要死记命令,学习查询帮助文档!!!
docker xxx --help。
额外命令:
docker exec
:进入容器执行命令
docker logs
:查看容器运行日志
docker ps
:查看所有运行的容器及状态
docker rm
:删除指定容器
样例1:创建运行一个nginx容器
1.使用Docker Hub查询Nginx容器的运行命令 。
docker run --name containrName -p 80:80 -d nginx
docker run
:创建并运行一个容器
参数解读:
–name:给容器起一个名字,比如叫做mn
-p:将宿主机端口与容器端口映射,冒号左侧是宿主机端口。
-d:后台运行容器。
样例2:进入Nginx容器,修改HTML文件内容,添加“hello world”。
1.进入容器,命令
docker exec -it mn bash
参数解读:
docker exec:进入容器内部,执行一个命令
-it:给当前进入的容器创建一个标准输入输出终端,允许我们与容器交互。
mn:要进入容器的名称。
bash:进入容器后执行的命令,bash是一个linux终端交互命令。
2.进入nginx的HTML所在目录 /usr/share/nginx/html。(可在DockerHub上查看位置)
cd /usr/share/nginx/html
3.修改index.html的内容(无vim命令)
不推荐在容器内修改文件
4.停止容器运行
docker stop mn # 停止容器
docker ps #默认查看运行时容器
docker ps -a #查看所有的容器
5.重启容器
docker start mn # 重新开始容器运行
6.删除容器
# 方案1
docker stop nm # 先暂停容器
docker rm mn # 删除容器
# 方案2
docker rm -f mn # 强制删除mn容器
数据卷操作语句
docker volume [COMMAND]:
create
:创建一个volume。
inspect
:显示一个或多个volume的信息。
ls
:列出所有的volume。
prune
:删除未使用的volume。
rm
:删除一个或多个指定的volume。
数据卷的挂载
案例:
docker run \
--name mn \
-v html:/root/html \ # 把html数据卷挂载在容器内对应目录中。
-p 8080:80
nginx \
样例:创建一个nginx容器,修改容器内的html目录内的index.html内容
在上个案例中,我们进入nginx内部,以及知道nginx的html目录所在位置/usr/share/nginx/html,我们需要把这个目录挂载到html这个数据卷上,方便操作其中的内容。
1.创建容器并挂在数据卷到容器内的HTML目录。
docker run --name mn -p 80:80 -v html:/usr/share/nginx/html -d nginx
2.查看当前容器
docker ps
3.查看数据卷html信息
docker inspect html
4.找到对应路径下的index.html文件直接进入修改即可。
该种方式进入修改无需进入容器,直接数据卷同步修改。
样例2:创建并运行一个MYSQL容器,将宿主机目录直接挂载到容器。
-v [宿主机目录]:[容器内目录]
-v [宿主机文件]:[容器内文件]
实现思路:
1.将mysql.tar文件上传到虚拟机,通过load命令加载为镜像
docker load -i mysql.tar # 加载镜像
docker images # 查看镜像是否导入成功
2.创建目录/tmp/mysql/data
mkdir -p mysql/data
mkdir -p mysql/conf
cd mysql/conf
3.创建目录/tmp/mysql/conf,将课前资料提供的hmy.cnf文件上传到/tmp/mysql/conf
4.去DockerHub查阅资料,创建并运行MYSQL容器,要求:
①挂载/tmp/mysql/data到mysql容器内数据存储目录
②挂载/tmp/mysql/conf/hmy.cnf到mysql容器的配置文件
③设置MYSQL密码
难点※:
docker run \
--name mysql \
-e MYSQL_ROOT_PASSWORD=123 \
-p 3306:3306\
-v /tmp/mysql/conf/hmy.cnf:/etc/mysql/conf.d/hmy.cnf \
-v /tmp/mysql/data:/var/lib/mysql \
-d \
mysql:5.7.25
定义复习:镜像是将应用程序及其需要的 系统函数库,环境,配置,依赖打包而成。
镜像是一个分层结构,每一层是一个Layer。
BaseImage层:包含基本的系统函数库,环境变量。文件系统。
Entrypoint:入口,是镜像中应用启动的命令。
其他:再BaseImage基础上添加依赖,安装程序,完成整个应用的安装和配置。
定义:是一个文本文件,包含一个个的指令,用指令来说明要执行什么操作来构建镜像。每个指令都会形成一层Layer。
※不会多查文档!!!
官方文档:https://docs.docker.com/engine/reference/builder
样例1:基于Ubuntu镜像构建一个新镜像,运行一个java项目。
①准备一个空文件夹docker-demo
# 切换目录,新建文件夹
cd /tmp/
mkdir docker-demo
ll
②将事先准备好的docker-demo.jar,jdk8.tar.gz,Dockerfile拷贝到docker-demo中。
③进入docker-demo,运行命令:
# -t == tag 版本
# 空格之后加“.”代表当前目录下
docker build -t javaweb:1.0 .
④检查是否操作成功
docker images # 查看所有镜像
docker run --name web -p 8090:8090 -d javaweb:1.0
# 让名为javaweb 1.0版本的镜像以8090端口在容器8090下并且可以后台运行
Dockerfile的写法案例:
# 指定基础镜像(基于ubuntu)
FROM ubuntu:16.04
# 配置环境变量,JDK的安装目录
ENV JAVA_DIR=/usr/local
# 拷贝jdk和java项目的包
COPY ./jdk8.tar.gz $JAVA_DIR/
COPY ./docker-demo.jar /tmp/app.jar
# 安装JDK
RUN cd $JAVA_DIR \
&& tar -xf ./jdk8.tar.gz \
&& mv ./jdk1.8.0_144 ./java8
# 配置环境变量
ENV JAVA_HOME=$JAVA_DIR/java8
ENV PATH=$PATH:$JAVA_HOME/bin
# 暴露端口(如果需要改端口需要在boot中去修改port)
EXPOSE 8090
# 入口 java的启动命令
ENTRYPOINT java -jar /tmp/app.jar
样例2:基于java:8-alpine镜像,将java项目部署到容器
Dockerfile:
#指定基础镜像(基于java:8-alpine)
FROM java:8-alpine
COPY ./docker-demo.jar /tmp/app.jar
# 暴露端口(如果需要改端口需要在boot中去修改port)
EXPOSE 8090
# 入口 java的启动命令
ENTRYPOINT java -jar /tmp/app.jar
定义:是一个基于Compose文件帮我们快速部署分布式应用,无需手动创建和运行容器!其是一个文本文件,通过定义集群中的每个容器如何运行。
安装:
curl -L http://github.com/docker/compose/releases/download/1.29.1/docker-compose-'uname -s'-'uname-m' > /usr/local/bin/docker-compose
上传到/usr/local/bin/
目录也可以。
修改文件权限:
chmod +x /usr/local/bin/docker-compose
自动补全命令:
curl -L https://raw.githubusercontent.com/docker/compose/1.29.1/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose
# 如果出错,执行以下命令
echo ""
样例:将之前学习的cloud-demo微服务集群利用docker-compose构建集群。
同步通信和异步通信:与生活中的视频电话与聊天相似。
同步通信存在的问题:
①耦合度高。每次加入新的请求,都需要修改原代码。
②性能下降。调用者需要等待服务提供者响应,调用链过长响应时间等于每次调用时间之和。
③资源浪费。调用链中每个服务在等待响应的过程中,不能释放请求占用的资源,高并发下会浪费系统资源。
④级联失败。如果服务提供者出现问题,所有调用方法都会出问题。
优点:时效性较强。
异步通信:
特点:事件驱动优势:
①流量削峰:高并发流量发起请求时,Broker缓存请求信息。微服务依次去取请求。
②耦合度低。
③吞吐量提升。
④故障隔离。
缺点:
①依赖于Broker的可靠性,安全性和吞吐能力。
②架构复杂了,业务没有明显的流程线,不好追踪管理。
定义:英文名MessageQueue,事件驱动架构中的Broker,表面上看就是存放消息的队列。
概念:RabbitMQ是基于Erlang语言开发的开源消息通信中间件,官网地址为:https://www.rabbitmq.com/
安装:
环境:在CentOS7的虚拟机下用Docker来安装。
下载镜像:
方式一:在线拉取
# 从镜像服务器中拉取rabbitmq镜像
docker pull rabbitmq:3-management
# 将其导入到系统本地
docker save -o rabbitmq.tar rabbitmq:3-management
方式二:从本地拉取
上传到虚拟机后,使用命令加载镜像即可:
docker load -i mq.tar
安装MQ:
# 创建MQ容器
docker run \
-e RABBITMQ_DEFAULT_USER=itcast \
-e RABBITMQ_DEFAULT_PASS=123321 \
--name mq \
--hostname mql \
-p 15672:15672 \
-p 5672:5672 \
-d \
rabbitmq:3-management
# 查看是否创建成功
docker ps
RabbitMQ中的几个概念:
①channel:操作MQ的工具(通道)
②exchange:路由消息到队列中
③queue:缓存消息
④virtual host:虚拟主机,是对queue,exchange等的资源逻辑分组。
HelloWorld:
概述:官方的HelloWorld是基于最基础的消息队列模型来实现的,其中包括三个角色:
① publisher:消息发布者,将消息发送到队列queue
② queue:消息队列,负责接受并缓存消息
③ consumer:订阅队列,处理队列中的消息
publisher----->queue----->consumer
基本消息队列的消息发送流程:
1.建立connection
2.创建channel
3.利用channel声明队列queue
4.利用channel向队列发送消息
基本消息队列的消息接收流程:
1.建立connection
2.创建channel
3.利用channel声明队列queue
4.定义consumer的消费行为handleDelivery()
5.利用channel将消费者与队列绑定
AMQP(Advanced Message Queuing Protocal):用于在应用程序或之间传递业务消息的开放标准,该协议与语言和平台无关,更符合微服务中独立性的要求。
Spring AMQP:基于AMQP协议定义的一套API规范,提供模板来发送和接收消息,包含两部分,其中spring-amqp是基础抽象,spring-rabbit是底层的默认实现。
案例1: 利用Spring AMQP实现HelloWorld中的基础消息队列功能。
流程如下:
1.在父工程中引入spring-amqp的依赖。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-amqpartifactId>
dependency>
2.在publisher服务中利用RabbitTemplate发送消息到simple.queue这个队列。
配置mq的连接信息:
spring:
rabbitmq:
host: (主机名)
port: 5672
virtual-host: / # 虚拟主机
username: itcast # 用户名
password: 123321 #密码
在publisher服务中新建一个测试类,编写测试方法:
@Runwith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
public void testSimpleQueue(){
String queueName = "simple.queue";
String message = "hello,spring amqp!";
rabbitTemplate.convertAndSend(queueName,message);
}
}
3.在consumer服务中编写消费逻辑,绑定simple.queue这个队列。
配置consumer信息,添加mq连接信息:
spring:
rabbitmq:
host: (主机名)
port: 5672
virtual-host: /
username: itcast
password: 123321
在consumer服务中新建一个类,编写消费逻辑:
@Component
public class SpringRabbitListener {
@RabbitListener(queues="simple.queue")
public void ListenSimpleQueueMessage(String msg) throws InterruptedException {
System.out.println("spring消费者接收到消息:"+msg);
}
}
定义:一个队列绑定多个消费者。
作用:有效防止了消息队列的阻塞。
样例1:模拟WorkQueue,实现一个队列绑定多个消费者。
基本思路:
1.在publisher中服务定义测试方法,每秒产生50条消息,发送到simple.queue。
2.在consumer服务中定义两个消息监听者,都监听simple.queue队列。
3.消费者1每秒处理50条信息,消费者2每秒处理10条信息。
消费预取机制:
修改application.yml文件,设置preFetch这个值,可以控制预取消息的上限:
spring:
rabbitmq:
host: (主机名)
port: 5672 # 端口
virtual-host: /
username: itcast
password: 123321
listener:
simple:
prefetch: 1 # 每次只能获取一条消息,处理完了才能接取下一个消息
定义:与之前案例的区别是允许将同意消息发送给多个消费者。实现方式是加入了exchange(交换机)。常见的exchange包括:Fanout 广播, Direct 路由, Topic 话题。exhcange负责消息路由,路由失效则消息丢失。
FanoutExchange
定义:会接收到消息路由到每一个跟其绑定的queue。
案例1:实现FanoutExchange。
在配置类中声明FanoutExchange,Queue和绑定关系对象Binding,代码如下:
@Configuration
public class FanoutConfig {
//声明交换机
@Bean
public FanoutExchange fanoutExchange(){
return new FanoutExchange("itcast.fanout");
}
//声明队列
//注意Queue的包:org.springframework.amqp.core.Queue;
@Bean
public Queue fanoutQueue1(){
return new Queue("fanout.queue1");
}
//绑定队列1和交换机
@Bean
public Binding bindingQueue1(Queue q,FanoutExchange fe){
return BindingBuilder.bind(q).to(fe);
}
}
使用RabbitMQ的方式:
@Component
public class SpringRabbitListener {
@RabbitListener(queues = "simple.queue")
public void listenWorkQueue1(String msg) throws InterruptedException {
,,,
}
}
DirectExchange
定义:会将接收到的消息规则路由到指定的Queue,因此成为路由模式(routes)。
①每一个Queue都与Exchange设置一个BindingKey。
②发布者发送消息时,指定消息的RoutingKey。
③Exchange将消息路由到BindingKey与消息RoutingKey一致的队列。
案例:利用SpringAMQP演示DirectExchange。
实现思路如下:
1.在consumer服务中,编写两个消费者方法,分别监听direct.queue1和direct.queue2。
2.利用@RabbitListen声明Exchange,Queue,RoutingKey。
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "direct.queue2"),
exchange = @Exchange(name = "itcast.direct" , type = ExchangeTypes.DIRECT),
key = {"red","yellow"}
))
public void listenDirectQueue2(String msg){
System.out.println("消费者2接收到消息:"+msg);
}
TopicExchange
定义:与上者类似,区别在于routingKey必须是多个单词的列表,并且以.分割。
Queue与Exchange指定key时可以使用通配符:
#:代指0或多个单词
*:代指一个单词
key的命名案例:china.news
,china.weather
,japan.news
,japan.weather
。
样例:利用SpringAMQP演示TopicExchange使用。
声明思路:
①利用@RabbitListener声明交换机,队列和RoutingKey
②在消费者服务中,编写两个消费者方法,分别监听topic.queue1和topic.queue2。
@RabbitListener(bindings = @QueueBinding(
value = @Queue(name = "topic.queue1"),
exchange = @Exchange(name = "itcast.topic",type = ExchangeTypes.TOPIC),
key = "china.#"
))
③在publisher中编写测试方法,向itcast.topic发送消息。
定义:Spring的对消息对象处理是由org.springframework.amqp.support.converter.MessageConverter
来处理的。默认实现是SimpleMessageConverter
,基于JDK的ObjectOutputStream完成序列化。推荐使用JSON方式序列化。
实现步骤:
①引入依赖
<dependency>
<groupId>com.fasterxml.jackson.dataformatgroupId>
<artifactId>jackson-dataformat-xmlartifactId>
<version>2.9.10version>
dependency>
②在publisher服务声明MessageConverter:
@Bean
public MessageConverter jsonMessageConverter(){
return new Jackson2JsonMessageConverter();
}
③然后定义一个消费者,监听object.queue队列并消费消息:
@RabbitListener(queues = "object.queue")
public void ListenObjectQueue(Map<String,Object> msg){
sout("...")
}