微服务技术栈

微服务导学

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

认识微服务

单体架构

单体架构:将业务的所有功能集中在一个项目中开发,打成-一个包部署。

在这里插入图片描述
分布式架构

分布式架构:根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,称为-一个服务。

在这里插入图片描述

分布式架构的要考虑的问题:

服务拆分粒度如何?
服务集群地址如何维护?
服务之间如何实现远程调用?
服务健康状态如何感知?

微服务是一种经过良好架构设计的分布式架构方案,微服务架构特征:

单一职责: 微服务拆分粒度更小,每一个服务都对应唯一 的业务能力,做到单一职责,避免重复业务开发

面向服务: 微服务对外暴露业务接口

自治: 团队独立、技术独立、数据独立、部署独立

隔离性强: 服务调用做好隔离、容错、降级,避免出现级联问题

在这里插入图片描述

微服务结构

微服务这种方案需要技术框架来落地,全球的互联网公司都在积极尝试自己的微服务落地技术。在国内最知名的就是SpringCloud和阿里巴巴的Dubbo。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

SpringCloud

SpringCloud是目前国内使用最广泛的微服务框架。

官网地址: https://spring.io/projects/spring-cloud

SpringCloud集成了各种微服务功能组件,并基于SpringBoot实现了这些组件的自动装配,从而提供了良好的开箱即用体验:

在这里插入图片描述

服务拆分及远程调用

服务拆分注意事项

不同微服务,不要重复开发相同业务
微服务数据独立,不要访问其它微服务的数据库
微服务可以将自己的业务暴露为接口,供其它微服务调用


1. 微服务需要根据业务模块拆分,做到单一职责,不要重复开发相同业务
2. 微服务可以将业务暴露为接口,供其它微服务使用
3. 不同微服务都应该有自己独立的数据库

案例:根据订单id查询订单功能

需求:根据订单id查询订单的同时,把订单所属的用户信息一起返回

在这里插入图片描述
在这里插入图片描述

注册RestTemplate

  1. 注册RestTemplate

     在order-service的OrderApplication中注册RestTemplate
    

    在这里插入图片描述

  2. 服务远程调用RestTemplate

     修改order-service中的OrderService的queryOrderByld方法:
    

在这里插入图片描述
微服务调用方式

基于RestTemplate发起的http请求实现远程调用
http请求做远程调用是与语言无关的调用,只要知道对方的ip、端口、接口路径、请求参数即可。

提供者与消费者

服务提供者:  一次业务中,被其它微服务调用的服务。( 提供接口给其它微服务)
服务消费者:  一次业务中,调用其它微服务的服务。(调 用其它微服务提供的接口)

问:

服务A调用服务B,服务B调用服务C,那么服务B是什么角色?

答:

服务调用关系
	服务提供者:暴露接口给其它微服务调用
	服务消费者:调用其它微服务提供的接口
	提供者与消费者角色其实是相对的

Eureka注册中心

服务调用出现的问题

服务消费者该如何获取服务提供者的地址信息?
如果有多个服务提供者,消费者该如何选择?
消费者如何得知服务提供者的健康状态?

在这里插入图片描述

Eureka作用
在这里插入图片描述

消费者该如何获取服务提供者具体信息?
	服务提供者启动时向eureka注册自己的信息
	eureka保存这些信息
	消费者根据服务名称向eureka拉取提供者信息
	
如果有多个服务提供者,消费者该如何选择?
	服务消费者利用负载均衡算法,从服务列表中挑选-个
	
消费者如何感知服务提供者健康状态?
	服务提供者会每隔30秒向EurekaServer发送心跳请求,报告健康状态
	eureka会更新记录服务列表信息,心跳不正常会被剔除
	消费者就可以拉取到最新的信息




在Eureka架构中,微服务角色有两类:
	● EurekaServer: 服务端,注册中心
		◆记录服务信息
		◆心跳监控
		
	● EurekaClient: 客户端
		◆Provider: 服务提供者,例如案例中的user-service
		◆注册自己的信息到EurekaServer
		◆每隔30秒向EurekaServer发 送心跳

	● consumer:服务消费者,例如案例中的order-service
		◆根据服务名称从EurekaServer拉取服务列表
		◆基于服务列表做负载均衡,选中一个微服务后发起远程调用

案例:动手实践
在这里插入图片描述

搭建EurekaServer

  1. 创建项目,引入spring-cloud-starter-netflix-eureka-server的依赖
    在这里插入图片描述
  2. 编写启动类,添加@EnableEurekaServer注解
    在这里插入图片描述

3.添加application.yml文件, 编写下面的配置:
在这里插入图片描述

注册user-service

将user-service服务注册到EurekaServer步骤如下:

  1. 在user-service项目引入spring-cloud-starter- netflix-eureka-client的依赖
    在这里插入图片描述

  2. 在application.yml文件, 编写下面的配置:
    在这里插入图片描述
    另外,我们可以将user-service多次启动,模拟多实例部署,但为了避免端口冲突,需要修改端口设置:
    在这里插入图片描述
    在order-service完成服务拉取

     服务拉取是基于服务名称获取服务列表,然后在对服务列表做负载均衡
    
    1. 修改OrderService的代码,修改访问的url路径,用服务名代替ip、端口:
      在这里插入图片描述
    2. 在order-service项 目的启动类OrderApplication中的RestTemplate添加负载均衡注解:
      在这里插入图片描述

Ribbon负载均衡

负载均衡原理
在这里插入图片描述
在这里插入图片描述

负载均衡策略

Ribbon的负载均衡规则是- -一个叫做lRule的接口来定义的,每一个子接口都是一种规则:

在这里插入图片描述
在这里插入图片描述

通过定义IRule实现可以修改负载均衡规则,有两种方式:

  1. 代码方式:在order-service中的OrderApplication类中, 定义一个新的IRule:
    在这里插入图片描述
  2. 配置文件方式: 在order-service的application.yml文件中,添加新的配置也可以修改规则:
    在这里插入图片描述

饥饿加载

Ribbon默认是采用懒加载,即第一次访问时才 会去创建LoadBalanceClient,请求时间会很长。
而饥饿加载则会在项目启动时创建,降低第一次访问的耗时, 通过下面配置开启饥饿加载:

在这里插入图片描述

Nacos注册中心

Nacos是阿里巴巴的产品,现在是SpringCloud中的一一个组件。相比Eureka功能更加丰富,在国内受欢迎程度较高。

服务注册到Nacos

  1. 在cloud-demo父工程中添加spring-cloud-alilbaba的管理依赖:
    在这里插入图片描述

  2. 注释掉order-service和user-service中 原有的eureka依赖。

  3. 添加nacos的客户 端依赖:
    在这里插入图片描述

  4. 修改user-service&order-service中的application.yml文件, 注释eureka地址,添加nacos地址:
    在这里插入图片描述

Nacos服务分级存储模型

在这里插入图片描述

服务跨集群调用问题

服务调用尽可能选择本地集群的服务,跨集群调用延迟较高
本地集群不可访问时,再去访问其它集群

在这里插入图片描述
服务集群属性

  1. 修改application.ymI, 添加如下内容:
    在这里插入图片描述

  2. 在Nacos控制台可以看到集群变化:
    我们修改user-service配置,达到下面的效果:
    在这里插入图片描述

  3. 修改order-service中的application.yml, 设置集群为HZ:
    在这里插入图片描述

  4. 然后在order-service中设置负载均衡的IRule为NacosRule,这个规则优先会寻找与自己同集群的服务:
    在这里插入图片描述

  5. 注意将user-service的权重都设置为1

NacosRule负载均衡策略

①优先选择同集群服务实例列表
②本地集群找不到提供者,才去其它集群寻找,并且会
报警告
③确定了可用实例列表后,再采用随机负载均衡挑选实
例

根据权重负载均衡

实际部署中会出现这样的场景:

服务器设备性能有差异, 部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求

  1. 在Nacos控制台可以设置 实例的权重值,首先选中实例后面的编辑按钮
    在这里插入图片描述

  2. 将权重设 置为0.1,测试可以发现8081被访问到的频率大大降低
    在这里插入图片描述

实例的权重控制

①Nacos控制台可以设置实例的权重值,0~1之间
②同集群内的多个实例,权重越高被访问的频率越高
③权重设置为0则完全不会被访问

环境隔离- namespace

Nacos中服务存储和数据存储的最外层都是一个名为namespace的东西, 用来做最外层隔离

在这里插入图片描述

  1. 在Nacos控制台可以创建namespace, 用来隔离不同环境
    在这里插入图片描述
  2. 然后填写一个新的命名空间信息:

在这里插入图片描述

  1. 保存后会在控制台看到这个命名空间的id:

在这里插入图片描述

  1. 修改order- service的application.yml,添加namespace:
    在这里插入图片描述
  2. 重启order-service后,再来查看控制台:
    在这里插入图片描述
  3. 此时访问order-service, 因为namespace不同,会导致找不到userservice,控制台会报错:
    在这里插入图片描述

Nacos环境隔离

①namespace用来做环境 隔离
②每个namespace都有唯一id
③不同namespace 下的服务不可见

Nacos注册中心原理

在这里插入图片描述

服务注册到Nacos时,可以选择注册为临时或非临时实例,通过下面的配置来设置:

在这里插入图片描述
Nacos和Eureka的区别

Nacos与Eureka的共同点
	①都支持服务注册和服务拉取
	②都支持服务提供者心跳方式做健康检测
	
Nacos与Eureka的区别
	①Nacos支 持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
	②临时实例心跳不正常会被剔除,非临时实例则不会被剔除
	③Nacos支 持服务列表变更的消息推送模式,服务列表更新更及时
	④Nacos集群 默认采用AP方式,当集群中存在非临时实例时,采用CP模式; Eureka采用AP方式

Nacos配置管理

统一配置管理

配置更改热更新
在这里插入图片描述

在Nacos中添加配置信息:
在这里插入图片描述

在弹出表单中填写配置信息:
在这里插入图片描述

配置获取的步骤如下:
在这里插入图片描述

  1. 引入Nacos的配置管理客户端依赖:
    在这里插入图片描述
  2. 在userservice中的resource目录添加一 个bootstrap.yml文件, 这个文件是引导文件,优先级高于application.yml
    在这里插入图片描述
    我们在user-service中将pattern.dateformat这个属性注入到UserController中做测试:
    在这里插入图片描述

配置热更新

Nacos中的配置文件变更后,微服务无需重启就可以感知。不过需要通过下面两种配置实现:

方式一: 在@Value注入的变量所在类上添加注解@RefreshScope

在这里插入图片描述

方式二: 使用@ConfiqurationProperties注解

在这里插入图片描述

多环境配置共享

微服务启动时会从nacos读取多个配置文件:
在这里插入图片描述
无论profile如何变化,[spring. application.name].yaml这个文件一定会加载,因此多环境共享配置可以写入这个文件
在这里插入图片描述
多种配置的优先级:

服务名-profile.yaml > 服务名称.yaml >本地配置

Nacos集群搭建

Nacos生产环境下一-定要部署为集群状态,部署方式参考课前资料中的文档:

http客户端Feign

引入

以前利用RestTemplate发起远程调用的代码:

在这里插入图片描述
存在下面的问题:

代码可读性差,编程体验不统一
参数复杂URL难以维护

Feign的介绍

Feign是一个声明式的http客户端,官方地址:https://github.com/OpenFeign/feign
公其作用就是帮助我们优雅的实现http请求的发送,解决上面提到的问题。

定义和使用Feign客户端

  1. 引入依赖:
    在这里插入图片描述
  2. 在order-service的启 动类添加注解开启Feign的功能:
    在这里插入图片描述
  3. 编写Feign客户端:
    在这里插入图片描述

主要是基于SpringMVC的注解来声明远程调用的信息,比如:

	服务名称: userservice
	请求方式: GET
	请求路径: /user/{id}
	请求参数: Long id
	返回值类型: User
  1. 用Feign客 户端代替RestTemplate
    在这里插入图片描述

自定义Feign的配置

Feign运行自定义配置来覆盖默认配置,可以修改的配置如下:
在这里插入图片描述
一般我们需要配置的就是日志级别。

配置Feign日志有两种方式:

方式一:配置文件方式

①全局生效:
在这里插入图片描述
②局部生效:
在这里插入图片描述

配置Feign日志的方式二: java代码方式,需要先声明一-个Bean:

在这里插入图片描述

①而后如果是全局配置,则把它放到@EnableFeignClients这个注解中:
在这里插入图片描述

②如果是局部配置,则把它放到@FeignClient这个注解中:
在这里插入图片描述

Feign的性能优化

Feign底层的客户端实现:

● URLConnection: 默认实现,不支持连接池
● Apache HttpClient :支持连接池
● OKHttp:支持连接池

因此优化Feign的性能主要包括:

①使用连接池代替默认的URLConnection
②日志级别, 最好用basic或none

Feign的性能优化-连接池配置

Feign添加HttpClient的支持:
引入依赖:
在这里插入图片描述

配置连接池:
在这里插入图片描述

Feign的最佳实践

方式一(继承) :给消费者的FeignClient和提供者的controller定义统一的父接口作为标准。

在这里插入图片描述

方式二(抽取) :将FeignClient抽取为独立模块, 并且把接口有关的POJO、默认的Feign配置都放到这个模块中,提供给所有消费者使用

在这里插入图片描述
抽取FeignClient

实现最佳实践方式二的步骤如下:
	1.首先创建个module, 命名为feign-api, 然后引入feign的starter依赖
	2.将order-service中 编写的UserClient、User、 DefaultFeignConfiguration都复制到feign-api项目中
	3.在order-service中 引入feign-api的依赖
	4.修改order-service中的所有与上述三个组件有关的import部分,改成导入feign-api中的包

当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。 有两种方式解决:

方式一: 指定FeignClient所在包
在这里插入图片描述

方式二:指定FeignClient字节码

统一网关Gateway

在这里插入图片描述

网关的技术实现

在SpringCloud中网关的实现包括两种:

● gateway
● zuul 
Zuul是基于Servlet的实现,属于阻塞式编程。而SpringCloudGateway则是基于Spring5中提供的WebFlux,属于响应式编程的实现,具备更好的性能。

搭建网关服务

搭建网关服务的步骤:

  1. 创建新的module, 引入SpringCloudGateway的依赖和nacos的服务发现依赖:
    在这里插入图片描述

  2. 编写路由配置及nacos地址
    在这里插入图片描述

     网关搭建步骤:
     	1. 创建项目,引入nacos服务发现和gateway依赖
     	2. 配 置application.yml,包括服务基本信息、nacos地址、路由
    
     路由配置包括:
     	1.路由id:路由的唯-标示
     	2.路由目标 (uri) :路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡
     	3.路由断言( predicates) :判断路由的规则
     	4.路由过滤器(filters) :对请求或响应做处理
    

路由断言工厂Route Predicate Factory

网关路由可以配置的内容包括:

路由id: 路由唯一标示
uri: 路由目的地,支持Ib和http两种
predicates: 路由断言,判断请求是否符合要求,符合则转发到路由目的地
filters: 路由过滤器,处理请求或响应

我们在配置文件中写的断言规则只是字符串,这些字符串会被Predicate Factory读取并处理,转变为路由判断的条件
例如Path=/user/**是按照路径匹配,这个规则是由orq.springframework .cloud.gateway.handler.predicate.PathRoutePredicateFactory类来处理的

像这样的断言工厂在SpringCloudGateway还有十几个

Spring提供了11种基本的Predicate工厂:
在这里插入图片描述

路由过滤器GatewayFilter

GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:

Spring提供了31种不同的路由过滤器工厂。例如:
在这里插入图片描述

案例:给所有进入userservice的请求添加一个请求头

给所有进入userservice的请求添加一一个请求头: Truth=itcast is freaking awesome!
实现方式:在gateway中修改application.yml文件,给userservice的路 由添加过滤器:
在这里插入图片描述

默认过滤器

如果要对所有的路由都生效,则可以将过滤器工厂写到default下。 格式如下:
在这里插入图片描述

全局过滤器GlobalFilter

全局过滤器的作用也是处理一切进入网关的请求和微服务响应, 与GatewayFilter的作用一 样。
区别在于GatewayFilter通过配置定义,处理逻辑是固定的。而GlobalFilter的逻辑需要 自己写代码实现。

定义方式是实现GlobalFilter接口。
在这里插入图片描述

案例: 定义全局过滤器,拦截并判断用户身份

需求:定义全局过滤器,拦截请求,判断请求的参数是否满足下面条件:
	● 参数中是否有authorization,
	● authorization参数值是否为admin
	如果同时满足则放行,否则拦截

自定义类,实现GlobalFilter接口, 添加@Order注解:
在这里插入图片描述

过滤器执行顺序

请求进入网关会碰到三类过滤器:当前路由的过滤器、DefaultFilter、 GlobalFilter
请求路由后,会将当前路由过滤器和DefaultFilter、 GlobalFilter, 合并到一个过滤器链(集合)中,排序后依次执行每个过滤器
在这里插入图片描述

每一个过滤器都必须指定一个int类型的order值, order值越小,优先级越高,执行顺序越靠前。

GlobalFilter通过实现Ordered接口,或者添加@Order注解来指定order值,由我们自己指定

路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增。

当过滤器的order值一样时,会按照defaultFilter >路由过滤器>、GlobalFilter的顺序执行。

可以参考下面几个类的源码来查看:
在这里插入图片描述

跨域问题处理

跨域:域名不一致就是跨域,主要包括:
域名不同:  www.taobao.com 和 www.taobao.org 和 wwwjd.com 和 miaoshajd.com
域名相同,端口不同:  localhost:8080和localhost8081

跨域问题:浏览器禁止请求的发起者与服务端发生跨域ajax请求,请求被浏览器拦截的问题

解决方案: CORS

网关处理跨域采用的同样是CORS方案,并且只需要简单配置即可实现:
在这里插入图片描述

Docker

初识Docker

项目部署的问题
大型项目组件较多,运行环境也较为复杂,部署时会碰到一些问题 :

  • 依赖关系复杂,容易出现兼容性问题
  • 开发、测试、生产环境有差异
    在这里插入图片描述

Docker如何解决依赖的兼容问题的?

●将应用的Libs (函数库)、Deps (依赖)、配置与应用一起打包
●将每个应用放到一个隔离容器去运行,避免互相干扰

在这里插入图片描述

不同环境的操作系统不同,Docker如何解决?需要了解下操作系统结构
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Docker如何解决大型项目依赖关系复杂,不同组件依赖的兼容性问题?
1. Docker允许开发中将应用、依赖、函数库、配置-起打包,形成可移植镜像
2. Docker应用运行在容器中,使用沙箱机制,相互隔离

Docker如何解决开发、测试、生产环境有差异的问题
1. Docker镜像中包含完整运行环境,包括系统函数库,仅依赖系统的Linux内核,因此可以在任意Linux操作系统上运行

Docker是一个快速交付应用、运行应用的技术:
	1.可以将程序及其依赖、运行环境一 起打包为一个镜像,可以迁移到任意Linux操作系统
	
	2.运行时利用沙箱机制形成隔离容器,各个应用互不干扰
	
	3.启动、移除都可以通过一行命令完成,方便快捷

Docker与虚拟机
在这里插入图片描述
在这里插入图片描述

Docker和虚拟机的差异:
	●docker是一 个系统进穆;虚拟机是在操作系统中的操作系统
	●docker体积小、启动速度快、性能好;虚拟机体积大、启动速度慢、性能一般

镜像和容器

镜像(Image) : Docker将 应用程序及其所需的依赖、函数库、环境、配置等文件打包在一起,称为镜像。
容器(Container) :镜像中的应用程序运行后形成的进程就是容器,只是Docker会给容 器做隔离,对外不可见。

Docker和DockerHub

DockerHub: DockerHub是一个Docker镜像的托管平台。这样的平台称为Docker Registry。
国内也有类似于DockerHub的公开服务,比如网易云镜像服务、阿里云镜像库等。

docker架构

Docker是一个CS架构的程序,由两部分组成:
◆ 服务端(server): Docker守护进程,负责处理Docker指令,管理镜像、容器等
◆ 客户端(client): 通过命令或RestAPI向Docker服务端发送指令。可以在本地或远程向服务端发送指令。
在这里插入图片描述

Docker基本操作

镜像相关命令

镜像名称一般分两 部分组成: [repository]:[tag]。
在没有指定tag时,默认是latest,代表最新版本的镜像

镜像操作命令
在这里插入图片描述
在这里插入图片描述
案例:利用docker save将nginx镜像导出磁盘,然后再通过load加载回来

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

容器相关命令

在这里插入图片描述
案例:创建运行一个Nginx容器

步骤一: 去docker hub查看Nginx的容器运行命令

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

案例:进入Nginx容器,修改HTML文件内容,添加“欢迎您”

步骤一: 进入容器。进入我们刚刚创建的nginx容器的命令为:
在这里插入图片描述

命令解读:

docker exec :进入容器内部,执行一个命令
-it :给当前进入的容器创建一个标准输入、输出终端,允许我们与容器交互
mn:要进入的容器的名称
bash:进入容器后执行的命令,bash是一个linux终端交互命令

步骤二:进入nginx的HTML所在目录/usr/share/ nginx/html
在这里插入图片描述

步骤三:修改index.html的内容
在这里插入图片描述

查看容器状态:
	●docker ps 
	●添加-a参数查看所有状态的容器
	删除容器:
	●docker rm
	不能删除运行中的容器,除非添加-f参数
	进入容器:
	命令是docker exec -it [容器名] [要执行的命令]
	exec命令可以进入容器修改文件,但是在容器内修改文件是不推荐的

数据卷

容器与数据耦合的问题
在这里插入图片描述

数据卷(volume)是一个虚拟目录,指向宿主机文件系统中的某个目录。

在这里插入图片描述

数据卷操作的基本语法如下:
在这里插入图片描述

案例:创建一个数据卷,并查看数据卷在宿主机的目录位置

创建
在这里插入图片描述
查看
在这里插入图片描述
删除
在这里插入图片描述

挂载数据卷
我们在创建容器时,可以通过-V参数来挂载一个数据卷到某个容器目录

在这里插入图片描述

案例:创建一个nginx容器, 修改容器内的html目录内的index.html内容

需求说明:上个案例中, 我们进入nginx容器内部,已经知道nginx的html目录所在位置/usr/share/nginx/html,我们需要把这个目录挂载到html这个数据卷上,方便操作其中的内容。

提示: 运行容器时使用-V参数挂载数据卷
步骤:
①创建容器并挂载数据卷到容器内的HTML目录
②进入html数据卷所在位置,并修改HTML内容

1

在这里插入图片描述
2.
在这里插入图片描述
数据卷挂载方式:

● -V volumeName: /targetContainerPath
● 如果容器运行时volume不存在,会自动被创建出来

案例:创建并运行一-个MySQL容器,将宿主机目录直接挂载到容器

提示:目录挂载与数据卷挂载的语法是类似的
●tV [宿主机目录]:[容器内目录]
●-V [宿主机文件]:[容器内文件]

实现思路如下:

  1. 在将课前资料中的mysql.tar文件上传到虚拟机,通过load命令加载为镜像
  2. 创建目录/tmp/myql/data
  3. 创建目录/tmp/myql/conf, 将课前资料提供的hmy.cnf文件.上传到/tmp/myql/conf
    4.去DockerHub查阅资料, 创建并运行MySQL容器,要求:
    ①挂载/tmp/myql/data到mysql容器内数据存储目录
    ②挂载/tmp/myql/conf/hmy.cnf到mysql容 器的配置文件
    ③设置MySQL密码

数据卷挂载的方式对比

在这里插入图片描述

docker run的命令中通过-V参数挂载文件或目录到容器中:
	①-V volume名称:容器内目录
	②-V 宿主机文件:容器内文件
	③-V宿主机目录:容器内目录
数据卷挂载与目录直接挂载的
	①数据卷挂载耦合度低,由docker来管理目录,但是目录较深,不好找
	②目录挂载耦合度高,需要我们自己管理目录,不过目录容易寻找查看

Dockerfile自定义镜像

镜像结构

镜像是将应用程序及其需要的系统函数库、环境、配置、依赖打包而成。

在这里插入图片描述

镜像是分层结构,每一层称为一-个Layer
	Baselmage层:包含基本的系统函数库、环境变量、文件系统
	Entrypoint:入口,是镜像中应用启动的命令
	其它:在Baselmage基础上添加依赖、安装程序、完成整个应用的安装和配置

什么是Dockerfile

Dockerfile就是一个文本文件, 其中包含一个个的指令(Instruction),用指令来说明要执行什么操作来构建镜像。每一个指令都会形成一层Layer。

在这里插入图片描述
更新详细语法说明,请参考官网文档: https://docs.docker.com/ engine/ reference/builder

案例:基于Ubuntu镜像构建一个新镜像, 运行一个java项目

在这里插入图片描述
在这里插入图片描述
案例:基于java:8-alpine镜像,将一个Java项目构建为镜像

实现思路如下:
①新建一个空的目录,然后在目录中新建一一个文件,命名为Dockerfile
②拷贝课前资料提供的docker-demo.jar到这个目录中
③编写Dockerfile文件:
	a)基于java:8-alpine作为基础镜像
	b)将app.jar拷 贝到镜像中
	c)暴露端口
	d)编写入口ENTRYPOINT
④使用docker build命令构建镜像
公
⑤使用docker run创建容器并运行

在这里插入图片描述

1. Dockerfile的本质是一一个文件,通过指令描述镜像的构建过程
3. Dockerfile的第 行必 须是FROM, 从一个基础镜像来构建
5. 基础镜像可以是基本操作系统,如Ubuntu。也可以是其他人制作好的镜像,例如: java:8-alpine

DockerCompose

什么是DockerCompose

Docker Compose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器!
Compose文件是个文本文件, 通过指令定义集群中的每个容器如何运行。

在这里插入图片描述

DockerCompose的详细语法参考官网: https://docs.docker.com/compose/compose-file/
在这里插入图片描述

Docker镜像仓库

镜像仓库( Docker Registry )有公共的和私有的两种形式:
● 公共仓库:例如Docker官方的DockerHub,国内也有–些云服务商提供类似于DockerHub的公开服务,比如网易云镜像服务、DaoCloud 镜像服务、阿里云镜像服务等。
● 除了使用公开仓库外,用户还可以在本地搭建私有Docker Registry。 企业自己的镜像最好是采用私有DockerRegistry来实现。

在私有镜像仓库推送或拉取镜像

在这里插入图片描述

1. 推送本地镜像到仓库前都必须重命名(docker tag)镜像,以镜像仓库地址为前缀

2. 镜像仓库推送前需要把仓库地址配置到docker服务的daemon.json文件中,被docker信任

3.推送使用docker push命令

4.拉取使用docker pull命令

RabbitMQ

初识MQ

同步通讯和异步通讯
在这里插入图片描述

微服务间基于Feign的调用就属于同步方式,存在一些问题。

在这里插入图片描述

同步调用存在的问题

在这里插入图片描述
异步调用方案

异步调用常见实现就是事件驱动模式

在这里插入图片描述

事件驱动优势

优势 一 : 服务解耦
在这里插入图片描述
优势二 : 性能提升,吞吐量提高
在这里插入图片描述
优势三 : 服务没有强依赖,不担心级联失败问题
在这里插入图片描述
优势四 : 流量削峰
在这里插入图片描述

异步通信的优点:
	● 耦合度低
	● 吞吐量提升
	● 故障隔离
	● 流量削峰
异步通信的缺点:
	● 依赖于Broker的可靠性、安全性、吞吐能力
	● 架构复杂了,业务没有明显的流程线,不好追踪管理

什么是MQ

MQ (MessageQueue) ,中文是消息队列,字面来看就是存放消息的队列。也就是事件驱动架构中的Broker。

在这里插入图片描述

RabbitMQ快速入门

RabbitMQ概述

RabbitMQ是基于Erlang语言开发的开源消息通信中间件,官网地址: https://www.rabbitmq.com/
安装RabbitMQ,参考课前资料: .

方式一:在线拉取
在这里插入图片描述

方式二: 从本地加载
在课前资料已经提供了镜像包:

在这里插入图片描述
上传到虚拟机中后,使用命令加载镜像即可:
在这里插入图片描述

安装MQ

执行下面的命令来运行MQ容器:
在这里插入图片描述
在这里插入图片描述

RabbitMQ的结构和概念
在这里插入图片描述

RabbitMQ中的几个概念:
	● channel:操作MQ的工具
	● exchange:路由消息到队列中
	● queue:缓存消息
	● virtual host:虚拟主机,是对queue、exchange等资源的逻辑分组

常见消息模型

MQ的官方文档中给出了5个MQ的Demo示例,对应了几种不同的用法:
基本消息队列(BasicQueue )
工作消息队列(WorkQueue )
在这里插入图片描述
在这里插入图片描述

HelloWorld案例
官方的HelloWorld是基于最基础的消息队列模型来实现的,只包括三个角色:
在这里插入图片描述

案例:完成官方Demo中的hello world案例
在这里插入图片描述

发送消息

package cn.itcast.mq.helloworld;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import org.junit.Test;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class PublisherTest {
    @Test
    public void testSendMessage() throws IOException, TimeoutException {
        // 1.建立连接
        ConnectionFactory factory = new ConnectionFactory();
        // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
        factory.setHost("192.168.60.130");
        factory.setPort(5672);
        factory.setVirtualHost("/");
        factory.setUsername("root");
        factory.setPassword("123456");
        // 1.2.建立连接
        Connection connection = factory.newConnection();

        // 2.创建通道Channel
        Channel channel = connection.createChannel();

        // 3.创建队列
        String queueName = "simple.queue";
        channel.queueDeclare(queueName, false, false, false, null);

        // 4.发送消息
        String message = "hello, rabbitmq!";
        channel.basicPublish("", queueName, null, message.getBytes());
        System.out.println("发送消息成功:【" + message + "】");

        // 5.关闭通道和连接
        channel.close();
        connection.close();

    }
}

接收消息

package helloworld;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

public class ConsumerTest {

    public static void main(String[] args) throws IOException, TimeoutException {
        // 1.建立连接
        ConnectionFactory factory = new ConnectionFactory();
        // 1.1.设置连接参数,分别是:主机名、端口号、vhost、用户名、密码
        factory.setHost("192.168.60.130");
        factory.setPort(5672);
        factory.setVirtualHost("/");
        factory.setUsername("root");
        factory.setPassword("123456");
        // 1.2.建立连接
        Connection connection = factory.newConnection();

        // 2.创建通道Channel
        Channel channel = connection.createChannel();

        // 3.创建队列
        String queueName = "simple.queue";
        channel.queueDeclare(queueName, false, false, false, null);

        // 4.订阅消息
        channel.basicConsume(queueName, true, new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                // 5.处理消息
                String message = new String(body);
                System.out.println("接收到消息:【" + message + "】");
            }
        });
        System.out.println("等待接收消息。。。。");
    }
}
基本消息队列的消息发送流程:
	1.建立connection
	2.创建channel
	3.利用channel声明队列
	4.利用channel向 队列发送消息
	
基本消息队列的消息接收流程:
	1.建立connection
	2.创建channel
	3.利用channel声明队列
	4.定义consumer的消 费行为handleDelivery()
	5.利用channel将 消费者与队列绑定

SpringAMQP

什么是SpringAMQP
SpringAmqp的官方地址: https://spring.io/projects/spring-amqp

在这里插入图片描述

案例:利用SpringAMQP实现HelloWorld中的基础消息队列功能

流程如下:
	1.在父工程中引入spring-amqp的依赖
	2.在publisher服务 中利用RabbitTemplate发送消息到simple.queue这个队列
	3. 在consumer服务中编写消费逻辑,绑定simple.queue这个队列

步骤1 : 引入AMQP依赖
因为publisher和consumer服务都需要amqp依赖,因此这里把依赖直接放到父工程mq-demo中:
在这里插入图片描述

步骤2:在publisher中编写测试方法,向simple.queue发送消息
1.在publisher服务中编写application.yml, 添加mq连接信息:
在这里插入图片描述
2.在publisher服务 中新建一一个测试类 ,编写测试方法:
在这里插入图片描述

步骤3:在consumer中编写消费逻辑,监听simple.queue
1.在consumer服务中编写application.yml, 添加mq连接信息:
在这里插入图片描述
2.在consumer服务中新建一 个类,编写消费逻辑:
在这里插入图片描述

SpringAMQP如何接收消息?
	引入amqp的starter依赖
	配置RabbitMQ地址
	定义类,添加@Component注解
	类中声明方法,添加@RabbitListener注解,方法参数就时消息
	注意:消息一旦消费就会从队列删除,RabbitMQ没有消息回溯功能

Work Queue工作队列

Work queue,工作队列,可以提高消息处理速度,避免队列消息堆积

在这里插入图片描述

案例:模拟WorkQueue,实现-个队列绑定多个消费者

基本思路如下:
1.在publisher服务中定义测试方法,每秒产生50条消息,发送到simple.queue
2.在consumer服务 中定义两个消息监听者,都监听simple.queue队列
3.消费者1每秒处理50条消息,消费者2每秒处理10条消息

@Test
public void testSendMessage2WorkQueue() throws InterruptedException {
        String queueName = "simple.queue";
        String message = "hello,spring amqp__";
        for (int i=0;i<=50;i++){
            rabbitTemplate.convertAndSend(queueName,message + i);
            Thread.sleep(20);
        }
}
@RabbitListener(queues = "simple.queue")
public void listenSimpleQueue1(String msg) throws InterruptedException {
    System.out.println("消费者1接收到simple.queue的消息:【" + msg + "】" + LocalTime.now());
    Thread.sleep(20);
}
@RabbitListener(queues = "simple.queue")
public void listenSimpleQueue2(String msg) throws InterruptedException {
    System.err.println("消费者2接收到simple.queue的消息:【" + msg + "】" + LocalTime.now());
    Thread.sleep(200);
}

消费预取限制

修改application.yml文件,设置preFetch这个值,可以控制预取消息的.上限:
在这里插入图片描述

Work模型的使用:
	多个消费者绑定到一个队列,同一条消息只会被一-个
消费者处理
	通过设置prefetch来控制消费者预取的消息数量

发布( Publish )、订阅( Subscribe )

发布订阅模式与之前案例的区别就是允许将同一消息发送给多个消费者。实现方式是加入了exchange (交换机)。
在这里插入图片描述

发布订阅-Fanout Exchange

Fanout Exchange会将接收到的消息路由到每一个跟其绑定的queue

案例:利用SpringAMQP演示FanoutExchange的使用

实现思路如下:

  1. 在consumer服务中,利用代码声明队列、交换机,并将两者绑定
  2. 在consumer服务中, 编写两个消费者方法,分别监听fanout.queue 1和fanout.queue2
  3. 在publisher中编 写测试方法,向itcast.fanout发送消息
    在这里插入图片描述

步骤1:在consumer服务声明Exchange、Queue、 Binding
SpringAMQP提供了声明交换机、队列、绑定关系的API,例如:
在这里插入图片描述
在consumer服务常见-个类,添加@Configuration注解, 并声明FanoutExchange、Queue和绑定关系对象Binding,代码如下:
在这里插入图片描述

接收消息

@RabbitListener(queues = "fanout.queue1")
public void listenFanoutQueue1(String msg) {
    System.err.println("消费者接收到fanout.queue1的消息:【" + msg + "】");
}
@RabbitListener(queues = "fanout.queue2")
public void listenFanoutQueue2(String msg) {
    System.err.println("消费者接收到fanout.queue2的消息:【" + msg + "】");
}

发送消息

@Test
public void testSendMessageFanoutExchange() {
    //交换机名称
    String exchangeName = "itcast.fanout";
    //消息
    String message = "hello every one!";
    //发送消息
    rabbitTemplate.convertAndSend(exchangeName,"",message);
}

结果两个都收到消息
在这里插入图片描述

交换机的作用是什么?
	● 接收publisher发送的消息
	● 将消息按照规则路由到与之绑定的队列
	● 不能缓存消息,路由失败,消息丢失
	● FanoutExchange的会将消息路由到每个绑定的队列
	
声明队列、交换机、绑定关系的Bean是什么?
	● Queue
	● FanoutExchange
	● Binding

发布订阅-DirectExchange

Direct Exchange会将接收到的消息根据规则路由到指定的Queue,因此称为路由模式(routes) 。
● 每一个Queue都与Exchange设 置一个BindingKey
● 发布者发送消 息时,指定消息的RoutingKey
● Exchange将消息路由到BindingKey与消息RoutingKey- 致的队列
在这里插入图片描述

案例:利用SpringAMQP演示DirectExchange的使用

实现思路如下:

  1. 利用@RabbitListener声明Exchange、 Queue、RoutingKey
  2. 在consumer服务中, 编写两个消费者方法,分别监听direct.queue1和direct.queue2
  3. 在publisher中编 写测试方法,向itcast. direct发送消息
    在这里插入图片描述
    接收消息
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(name = "direct.queue1"),
        exchange = @Exchange(name = "itcast.direct",type = ExchangeTypes.DIRECT),
        key = {"red","blue"}
))
public void listenDirectQueue1(String msg){
    System.out.println("消费者接收到direct.queue1的消息:【" + msg + "】");
}
@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("消费者接收到direct.queue2的消息:【" + msg + "】");
}

发送消息

@Test
public void testSendMessageDirectExchange() {
    //交换机名称
    String exchangeName = "itcast.direct";
    //消息
    String message = "hello blue!";
    //发送消息
    rabbitTemplate.convertAndSend(exchangeName,"red",message);
}

在这里插入图片描述

描述下Direct交换机与Fanout交换机的差异?
	● Fanout交换机将消息路由给每一个与之绑定的队列
	● Direct交换机根据RoutingKey判断路由给哪个队列
	● 如果多个队列具有相同的RoutingKey,则与Fanout功能类似
	
基于@RabbitListener注解声明队列和交换机有哪些常见注解?
	● @Queue
	● @Exchange

发布订阅-TopicExchange

TopicExchange与DirectExchange类似,区别在于routingKey必须是多个单词的列表,并且以 . 分割。
Queue与Exchange指定BindingKey时可以使用通配符
#: 代指0个或多个单词
*: 代指一个单词

在这里插入图片描述

案例:利用SpringAMQP演示TopicExchange的使用

实现思路如下:

  1. 并利用@RabbitListener声 明Exchange、Queue、RoutingKey
  2. 在consumer服务中, 编写两个消费者方法,分别监听topic.queue 1和topic.queue2
  3. 在publisher中 编写测试方法,向itcast. topic发送消息

在这里插入图片描述
接收信息

@RabbitListener(bindings = @QueueBinding(
         value = @Queue(name = "topic.queue1"),
         exchange = @Exchange(name = "itcast.topic",type = ExchangeTypes.TOPIC),
         key = "china.#"
 ))
 public void listenTopicQueue1(String msg){
     System.out.println("消费者接收到topic.queue1的消息:【" + msg + "】");
 }
 @RabbitListener(bindings = @QueueBinding(
         value = @Queue(name = "topic.queue2"),
         exchange = @Exchange(name = "itcast.topic",type = ExchangeTypes.TOPIC),
         key = "#.news"
 ))
 public void listenTopicQueue2(String msg){
     System.out.println("消费者接收到topic.queue2的消息:【" + msg + "】");
 }

发送消息

@Test
public void testSendMessageTopicExchange() {
    //交换机名称
    String exchangeName = "itcast.topic";
    //消息
    String message = "hello blue!";
    //发送消息
    rabbitTemplate.convertAndSend(exchangeName,"sdjfhldsjk.news",message);
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

SpringAMQP-消息转换器

案例:测试发送Object类型消息

说明:在SpringAMQP的发送方法中,接收消息的类型是Object,也就是说我们可以发送任意对象类型的消息,SpringAMQP会帮我们序列化为字节后发送。
在这里插入图片描述

声明一个队列

//声明一个队列
@Bean
pubLic Queue objectQueue(){
	return new Queue( name: "object. queue") ;
}

发送消息

@Test
public void testSendMessageObjectQueue() {

    Map<String,Object> msg = new HashMap<>();
    msg.put("name","流量");
    msg.put("age",21);
    rabbitTemplate.convertAndSend("object.queue",msg);
}

在这里插入图片描述

Spring的对消息对象的处理是由org.springframework.amqp.support.converter.MessageConverter来处理的。而默认实现是SimpleMessageConverter,基于JDK的ObjectOutputStream完成序列化。
如果要修改只需要定义一个MessageConverter类型的Bean即可。推荐用JSON方式序列化,步骤如下:

  • 我们在publisher服务引入依赖
    在这里插入图片描述
  • 我们在publisher服务声明MessageConverter:
    在这里插入图片描述
    在这里插入图片描述
    我们在consumer服务引入ackson依赖:
    在这里插入图片描述
    我们在consumer服务定义MessageConverter:
    在这里插入图片描述
    然后定义一个消费者,监听object.queue队列并消费消息:
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

取个好的

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值