微服务+Docker+RabbitMQ

SpringCloud微服务

1.认识微服务

文章目录

  • SpringCloud微服务
    • 1.认识微服务
    • @[toc]
      • 1)微服务架构
      • 2)单体架构与分布式架构
      • 3)微服务概述
      • 4)SpringCloud框架概述
      • 5)服务拆分和远程调用
      • 6)Eureka注册中心
        • 搭建EurakaServer
        • 注册user-server
        • 多次启动相同服务
        • 服务拉取
        • **Ribbon负载均衡**
      • 7)Nacos注册中心
        • 下载与安装
        • 服务注册
        • nacos服务的分级存储模型
        • NacosRule和权重负载均衡
        • Nacos环境隔离
        • eureka和nacos对比
        • 统一配置管理
        • Nacos集群的搭建
      • 8)Http客户端Feign
        • RestTemplate存在的问题
        • Feigh的概述
        • 自定义Feign的配置
        • Feign的性能优化
        • Feign的最佳实践
      • 9)统一网关Gateway
        • 网关的概述
        • 路由过滤器GatewayFilter
        • 跨域问题处理
    • 2.Docker
      • 1)认识docker
        • Docker与虚拟机
        • 镜像和容器
        • DockerHub
        • Docker架构
      • 2)使用Docker
        • 安装Docker
        • Docker镜像操作
        • Docker容器基本操作
        • 数据卷
      • 3)自定义镜像
        • 镜像结构
        • Dockerfile
        • DockerCompose
    • 3.RabbitMQ
      • 1)同步通信和异步通信
      • 2)MQ消息队列
        • RabbitMQ安装
        • HelloWorld消息模型
        • SpringAMQP
        • WorkQueue工作队列
        • 发布订阅模型
        • 消息转换器
  • 文章末尾传送点

1)微服务架构

微服务的架构图:

微服务+Docker+RabbitMQ_第1张图片

微服务技术完整:

微服务+Docker+RabbitMQ_第2张图片

2)单体架构与分布式架构

单体架构

概念

将业务的所有功能集中再一个项目中开发,打包成一个包进行部署。

优点

a.架构简单

b.部署成本低

缺点

c.耦合度高


分布式架构

概念:根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,成为一个服务。

优点

a.降低服务耦合

b.有利于服务升级拓展

缺点

c.架构非常复杂

d.运维,监控,部署难度提高

待解决的问题

c.服务拆分粒度如何处理?

d.服务集群地址如何维护?

e.服务之间如何实现远程调用?

f.服务健康状态如何感知?

3)微服务概述

概念

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

特征

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

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

c.自治:团队独立,技术独立,数据独立,部署独立。

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


4)SpringCloud框架概述

概念

SpringCloud是目前国内最广泛的服务框架,其官网地址: https://spring.io/projects/spring-cloud

springcloud与springboot版本兼容

微服务+Docker+RabbitMQ_第3张图片

5)服务拆分和远程调用


服务拆分

实际案例

订单信息用户信息 服务拆分。

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错误。

6)Eureka注册中心


提出问题

①服务消费者该如何获取服务提供者的地址信息?

:服务提供者启动时向eureka注册自己的信息。euraka保存这些信息。消费者根据服务名向eureka拉去提供者信息。

②如果有多个服务提供者,消费者如何选择?

:负载均衡算法选择其中一个。

③消费者如何得知服务提供者的健康状态?

:服务提供者每隔30s向EurakaServer发起心跳,报告健康状态。eureka会更新记录服务列表信息,心跳不正常会被剔除。消费者就可以拉渠道最新的信息。

总结

在Euraka架构中,微服务角色有两类:

1.EurakaServer:服务端,注册中心。其包含功能:

①记录服务信息。

②心跳监控(轮询机制)。

2.EurakaClient:客户端。其包含功能:

①Provider:服务提供者,其需要做两件事:

Ⅰ.注册自己信息到EurakaServer

Ⅱ.每隔30s向EurakaServer发送心跳。

consumer:服务消费者。其需要做两件事:

Ⅰ.根据服务名称从EurakaServer拉取服务列表。

Ⅱ.基于服务列表做负载均衡,选一个微服务后发起远程调用。

搭建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

注册user-server

操作步骤:

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();
}

※切记勿忘添加注解,第一次测试无法访问原因是未加注解,根据名称无法访问到。

Ribbon负载均衡

流程图

微服务+Docker+RabbitMQ_第4张图片

细则流程图

微服务+Docker+RabbitMQ_第5张图片

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

※ 通过实例测试,饥饿加载的网页运行效率是之前的十倍有余。

7)Nacos注册中心

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

nacos服务的分级存储模型

内容

①一级是服务,例如userservice

②二级是集群,例如杭州or上海集群

③三级是实例,例如杭州机房的某台部署了userservice的服务器

设置步骤

修改application.yml文件,做以下修改:

spring:
  cloud:
    nacos:
      discovery:
        cluster-name: com.alibaba.cloud.nacos.ribbon.NacosRule #集群名

NacosRule和权重负载均衡

NacosRule负载均衡规则

​ ①优先选择同集群服务实例列表

​ ②本地集群找不到提供者,才回去其他集群寻找,并且会报错警告

​ ③确定了可用实例列表后,再采用随机负载均衡挑选实例

根据权重负载均衡

内容:Nacos提供权重控制负载均衡规则。

步骤:再nacos界面中设置服务权重(0~1),权重大的服务实例更容易被访问到。比如,权重为0.1与1的服务实例,1的访问概率约为0.1的十倍。权重为0时服务不会被访问到。

Nacos环境隔离

内容

​ ①namespace来做环境隔离

​ ②每个namespace都要唯一id

​ ③不同namespace下的服务不可见。

使用步骤

再yml配置文件中做以下配置:

spring:
  cloud:
    nacos:
      discovery:
        namespace: xxx #此处填写命名空间的uid

eureka和nacos对比


共同点

①都支持服务注册和服务拉取

②都支持服务提供者心跳方式做健康检测

不同点

①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 > 本地配置。

Nacos集群的搭建

P29详细讲解

认识集群(结构图):

微服务+Docker+RabbitMQ_第6张图片

数据库集群的作用是同步Nacos注册中心的数据。

搭建步骤

1)搭建数据库集群,初始化数据库表结构

2)配置nacos

集群配置,数据库配置…

3)启动nacos集群(多个nacos节点)

4)使用nginx实现反向代理

8)Http客户端Feign


RestTemplate存在的问题

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

String url = "http://userservice/user/"+order.getUserId();
User user = restTemplate.getForObject(url,User.class);

分析问题:

1.代码可读性差。

2.参数复杂URL难以维护。

Feigh的概述

概念: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的配置

自定义设置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的性能优化

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 #单个路径的最大连接数

Feign的最佳实践

方式一(继承):

消费者的FeignClient提供者的controller 定义统一的父接口作为标准。

微服务+Docker+RabbitMQ_第7张图片

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})

9)统一网关Gateway


网关的概述

网关的功能

①身份认证和权限校验

②服务路由,负载均衡

③请求限流

网关的技术实现

步骤:

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开头就符合要求

路由断言工厂

读取断言规则,从而对用户发起的路由做判断。

微服务+Docker+RabbitMQ_第8张图片

路由过滤器GatewayFilter

作用:可以对网关的请求和微服务返回的响应做处理:

案例:给所有进入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 #跨域检测的有效期

2.Docker

1)认识docker


为什么要使用docker?

①依赖关系复杂,容易出现兼容性问题。

②开发测试生产环境有差异。

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

①将应用的函数库,依赖,配置与应用一起打包,形成 可移植镜像

②将每个应用放到一个隔离容器去运行(沙箱机制),避免互相干扰。

Docker如何解决不同系统环境的问题?

①Docker将用户程序与所需要调用的系统(UbuntuCentOS)函数库一起打包。

②Docker运行到不同操作系统时,直接基于打包的库函数,借助于操作系统的Linux内核来运行。

微服务+Docker+RabbitMQ_第9张图片

Docker与虚拟机

虚拟机(virtual machine)是在操作系统中模拟硬件设备,然后运行另一个操作系统,比如在Windows上运行Ubuntu系统。使用了 Hypervisor 技术

对比

性能:Docker更接近原生,而虚拟机性能较差。

硬盘占用:Docker一般为MB,虚拟机一般为GB。

启动速度:Docker以秒为单位,虚拟机以分钟级为单位。

本质:Docker是一个系统进程,虚拟机是在操作系统中的另一套操作系统。

镜像和容器

镜像(Image)

镜像是只读的!!!Docker将应用程序及其所需的依赖,函数库,环境,配置等文件打包在一起。

容器(Container)

镜像中的应用程序运行后形成的进程就是容器,只是Docker会给容器做隔离,对外部可见。

DockerHub

DockerHub:是一个Docker镜像的托管平台。这样的平台叫做Docker Registry。

栗子:网易云镜像服务,阿里云镜像服务…

Docker架构

Docker是一个 C/S架构 的程序,由两部分组成:

♦ 服务端(server):Docker守护进程,负责处理Docker指令,管理镜像,容器等

♦ 客户端(client):通过命令或RestAPI向Docker服务端发送指令。可在本地或远程向服务端发送指令。

微服务+Docker+RabbitMQ_第10张图片

2)使用Docker

安装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

Docker镜像操作

镜像的命名:

由两部分组成:[repository]:[tag]

栗子:mysql:5.7

tag的默认值是latest,代表最新版本。


微服务+Docker+RabbitMQ_第11张图片

样例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容器基本操作

微服务+Docker+RabbitMQ_第12张图片

额外命令:

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+RabbitMQ_第13张图片

数据卷操作语句

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

3)自定义镜像

镜像结构

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

微服务+Docker+RabbitMQ_第14张图片

镜像是一个分层结构,每一层是一个Layer。

BaseImage层:包含基本的系统函数库,环境变量。文件系统。

Entrypoint:入口,是镜像中应用启动的命令。

其他:再BaseImage基础上添加依赖,安装程序,完成整个应用的安装和配置。

Dockerfile

定义:是一个文本文件,包含一个个的指令,用指令来说明要执行什么操作来构建镜像。每个指令都会形成一层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

DockerCompose

定义:是一个基于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构建集群。

3.RabbitMQ


1)同步通信和异步通信

同步通信异步通信:与生活中的视频电话与聊天相似。

同步通信存在的问题

耦合度高。每次加入新的请求,都需要修改原代码。

性能下降。调用者需要等待服务提供者响应,调用链过长响应时间等于每次调用时间之和。

资源浪费。调用链中每个服务在等待响应的过程中,不能释放请求占用的资源,高并发下会浪费系统资源。

级联失败。如果服务提供者出现问题,所有调用方法都会出问题。

优点:时效性较强。

异步通信

特点:事件驱动优势:

①流量削峰:高并发流量发起请求时,Broker缓存请求信息。微服务依次去取请求。

②耦合度低。

③吞吐量提升。

④故障隔离。

缺点

①依赖于Broker的可靠性,安全性和吞吐能力。

②架构复杂了,业务没有明显的流程线,不好追踪管理。

2)MQ消息队列

定义:英文名MessageQueue,事件驱动架构中的Broker,表面上看就是存放消息的队列。

微服务+Docker+RabbitMQ_第15张图片

RabbitMQ安装

概念: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

概述:官方的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将消费者与队列绑定

SpringAMQP

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);
    }
}

WorkQueue工作队列

定义:一个队列绑定多个消费者。

作用:有效防止了消息队列的阻塞。

样例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.newschina.weatherjapan.newsjapan.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("...")
}

文章末尾传送点

你可能感兴趣的:(分布式,微服务,docker,rabbitmq)