SpringCloud

介绍

前提

⚫ 需要对分布式架构有一定等了解
⚫ 需要对Spring Boot有一定的了解

简介

Spring Cloud是一系列框架的有序集合。 它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发,如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用Spring Boot的开发风格做到一键启动和部署。
Spring Cloud并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来,通过Spring Boot风格进行再封装 , 屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包。

学习导图

在这里插入图片描述

第一章 微服务架构介绍

一. 单体架构

单体架构也称之为单体系统或者是单体应用。就是一种把系统中所有的功能、模块耦合在一个应用中的架构方式。
特点 : 打包成一个独立的单元 jar包或者 war包 ;会以进程的方式运行
优点:项目易于管理、部署简单。
缺点:测试成本高、可伸缩性差、可靠性差、迭代困难、跨语言程度差、团队协作难

二. 微服务架构

常见项目架构风格

架构风格: 项目的一种设计模式

架构风格举例
客户端与服务端(C/S)QQ, 微信等
基于组件模型的架构( 基于组件模型的架构 EJB )https://blog.csdn.net/lovemenghaibin/article/details/50666964
分层架构 (MVC)OA系统,CMS内容管理系统等等
面向服务架构 (SOA)分布式项目-Ego电商项目等

三. MVC、RPC、SOA、微服务架构的介绍

MVC 架构
MVC(Model-View-Controller) : MVC架构就是一个单体架构。
代表技术: Struts2 、SpringMVC 、Spring 、Mybatis 等。

RPC 架构
RPC(Remote Procedure Call) :远程过调用。通过网络从计算机序上请求服务,而不需要了解底层网络技术的协议的架构方式。
代表技术:Thrift 、Hessian等

SOA(Service oriented Architecture): 面向服务架构
ESB(Enterparise Servce Bus): 企业服务总线,中介。主要是提供了一个于企业服务总线,
ESB 包含的功能如:负载均衡,流量控制, 加密处理, 服务的监控, 异常处理, 监控告急等。
代表技术: Mule 、WSO2

微服务架构
微服务就是一个轻量级的治理方案。
代表技术: SpringCloud、dubbo 等

在这里插入图片描述

四. 微服务的设计原则

AKF拆分原则

AKF可扩展立方

诞生背景
业界对于可扩展的系统架构设计有一个朴素的理念,就是: 通过加机器就可以解决容量和可用性问题。(如果一台不行那就两台)。 这一理念在“云计算”概念疯狂流行的今天,得到了广泛的认可!对于一个规模迅速增长的系统而言,容量和性能问题当然是首当其冲的。但是随着时间的向前,系统规模的增长,除了面对性能与容量的问题外,还需要面对功能与模块数量上的增长带来的系统复杂性问题以及业务的变化带来的提供差异化服务问题。而许多系统,在架构设计时并未充分考虑到这些问题,导致系统的重构成为常态,从而影响业务交付能力,还浪费人力财力!对此,《可扩展的艺术》一书提出了一个更加系统的可扩展模型—— AKF可扩展立方 (Scalability Cube)。这个立方体中沿着三个坐标轴设置分别为:X、Y、Z。
在这里插入图片描述

坐标介绍
Y轴(功能 ) —— 关注应用中功能划分, 基于不同的业务拆分
X轴(水平扩展) —— 关注水平扩展,也就是”加机器解决问题”
Z轴(数据分区) —— 关注服务和数据的优先级划分,如按地域划分

Y轴(功能 )

Y 轴扩展会将庞大的整体应用拆分为多个服务。每个服务实现一组相关的功能,如订单管理、客户管理等。在工程上常见的方案是面向服务架构(SOA) 。比如对于一个电子商务平台,我们可以拆分成不同的服务,组成下面这样的架构:
在这里插入图片描述
但通过观察上图容易发现,当服务数量增多时,服务调用关系变得复杂。为系统添加一个新功能,要调用的服务数也变得不可控,由此引发了服务管理上的混乱。所以,一般情况下,需要采用服务注册的机制形成服务网关来进行服务治理。系统的架构将变成下图所示:
在这里插入图片描述

X 轴(水平扩展)

为了提升单个服务的可用性和容量, 对每一个服务进行X 轴扩展划分。
X 轴扩展与我们前面朴素理念是一致的,通过绝对平等地复制服务与数据,以解决容量和可用性的问题。其实就是将微服务运行多个实例,做集群加负载均衡的模式。
在这里插入图片描述

Z 轴(数据分区)

Z 轴扩展通常是指基于请求者或用户独特的需求,进行系统划分,并使得划分出来的子系统是相互隔离但又是完整的。以生产汽车的工厂来举例:福特公司为了发展在中国的业务,或者利用中国的廉价劳动力,在中国建立一个完整的子工厂,与美国工厂一样,负责完整的汽车生产。这就是一种Z 轴扩展。

工程领域常见的Z 轴扩展有以下两种方案

  • 单元化架构
    在分布式服务设计领域,一个单元(Cell)就是满足某个分区所有业务操作的自包含闭环。如上面我们说到的Y 轴扩展的SOA 架构,客户端对服务端节点的选择一般是随机的,但是,如果在此加上Z 轴扩展,那服务节点的选择将不再是随机的了,而是每个单元自成一体。如下图:

在这里插入图片描述

  • 数据分区
    为了性能数据安全上的考虑,我们将一个完整的数据集按一定的维度划分出不同的子集。一个分区(Shard),就是是整体数据集的一个子集。比如用尾号来划分用户,那同样尾号的那部分用户就可以认为是一个分区。数据分区为一般包括以下几种数据划分的方式:
    数据类型(如:业务类型)
    数据范围(如:时间段,用户ID)
    数据热度(如:用户活跃度,商品热度)
    按读写分(如:商品描述,商品库存)

前后端分离原则

前后端分离原则,简单来讲就是前端和后端的代码分离,我们推荐的模式是最好采用物理分离的方式部署,进一步促使更彻底的分离。如果继续直接使用服务端模板技术,如:jsp,把java、js、html、css 都堆到一个页面里,稍微复杂一点的页面就无法维护了。
在这里插入图片描述
何为前后端分离?前后端本来不就分离么?这要从尴尬的jsp 讲起。分工精细化从来都是蛋糕做大的原则,多个领域工程师最好在不需要接触其他领域知识的情况下合作,才可能使效率越来越高,维护也会变得简单。jsp 的模板技术融合了html 和java 代码,使得传统MVC 开发中的前后端在这里如胶似漆,前端做好页面,后端转成模板,发现问题再找前端,前端又看不懂java 代码…前后端分离的目的就是将这尴尬局面打破。

这种分离方式有几个好处

  • 前后端技术分离,可以由各自的专家来对各自的领域进行优化,这样前段的用户体验优化效果更好。
  • 分离模式下,前后端交互界面更清晰,就剩下了接口模型,后端的接口简洁明了,更容易维护。
  • 前端多渠道集成场景更容易实现,后端服务无需变更,采用统一的数据和模型,可以支持多个前端:例如:微信h5 前端、PC 前端、安卓前端、IOS 前端。

无状态服务

在这里插入图片描述

对于无状态服务,首先说一下什么是状态:如果一个数据需要被多个服务共享,才能完成一笔交易,那么这个数据被称为状态。进而依赖这个“状态”数据的服务被称为有状态服务,反之称为无状态服务。 那么这个无状态服务原则并不是说在微服务架构里就不允许存在状态,表达的真实意思是要把有状态的业务服务改变为无状态的计算类服务,那么状态数据也就相应的迁移到对应的“有状态数据服务”中。
场景说明:例如我们以前在本地内存中建立的数据缓存、Session缓存,到现在的微服务架构中就应该把这些数据迁移到分布式缓存中存储,让业务服务变成一个无状态的计算节点。迁移后,就可以做到按需动态伸缩,微服务应用在运行时动态增删节点,就不再需要考虑缓存数据如何同步的问题。

Restful通信风格

在这里插入图片描述

作为一个原则来讲本来应该是个“无状态通信原则”,在这里我们直接推荐一个实践优选的Restful 通信风格 ,因为他有很多好处:

  1. 无状态协议HTTP,具备先天优势,扩展能力很强。例如需要安全加密,有现成的成熟方案HTTPS即可。
  2. JSON 报文序列化,轻量简单,人与机器均可读,学习成本低,搜索引擎友好。
  3. 语言无关,各大热门语言都提供成熟的Restful API框架,相对其他的一些RPC框架生态更完善。

第二章 SpringCloud入门

一. SpringCloud简介

SpringCloud概念

是一个服务治理平台,是一系列框架的有序集合。包含了:服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等

与Dubbo的区别

Spring Cloud 是一个微服务框架,相比Dubbo 等RPC 框架, Spring Cloud 提供的全套的分布式系统解决方案。
Spring Cloud 对微服务基础框架Netflix 的多个开源组件进行了封装,同时又实现了和云端平台以及和Spring Boot 开发框架的集成。
Spring Cloud 为微服务架构开发涉及的配置管理,服务治理,熔断机制,智能路由,微代理,控制总线,一次性token,全局一致性锁,leader 选举,分布式session,集群状态管理等操作提供了一种简单的开发方式。
Spring Cloud 为开发者提供了快速构建分布式系统的工具,开发者可以快速的启动服务或构建应用、同时能够快速和云平台资源进行对接。

SpringCloud的项目的位置

Sping Cloud 是Spring 的一个顶级项目与Spring Boot、Spring Data 位于同一位置。

Spring Cloud 包含了很多子项目,如:

  1. Spring Cloud Config:配置管理工具,支持使用Git 存储配置内容,支持应用配置的外部化存储,支持客户端配置信息刷新、加解密配置内容等
  2. Spring Cloud Bus:事件、消息总线,用于在集群(例如,配置变化事件)中传播状态变化,可与Spring Cloud Config 联合实现热部署。
  3. Spring Cloud Netflix:针对多种Netflix 组件提供的开发工具包,其中包括Eureka、Hystrix、Zuul、Archaius 等。
    • Netflix Eureka:一个基于rest 服务的服务治理组件,包括服务注册中心、服务注册与服务发现机制的实现,实现了云端负载均衡和中间层服务器的故障转移。
    • Netflix Hystrix:容错管理工具,实现断路器模式,通过控制服务的节点,
      从而对延迟和故障提供更强大的容错能力。
    • Netflix Ribbon:客户端负载均衡的服务调用组件。
    • Netflix Feign:基于Ribbon 和Hystrix 的声明式服务调用组件。
    • Netflix Zuul:微服务网关,提供动态路由,访问过滤等服务。
    • Netflix Archaius:配置管理API,包含一系列配置管理API,提供动态类型化属性、线程安全配置操作、轮询框架、回调机制等功能。
  4. Spring Cloud for Cloud Foundry : 通过Oauth2 协议绑定服务到CloudFoundry,CloudFoundry 是VMware 推出的开源PaaS 云平台。
  5. Spring Cloud Sleuth:日志收集工具包,封装了Dapper,Zipkin 和HTrace操作。
  6. Spring Cloud Data Flow:大数据操作工具,通过命令行方式操作数据流。
  7. Spring Cloud Security:安全工具包,为你的应用程序添加安全控制,主要是指OAuth2。
  8. Spring Cloud Consul:封装了Consul 操作,consul 是一个服务发现与配置工具,与Docker 容器可以无缝集成。
  9. Spring Cloud Zookeeper : 操作Zookeeper 的工具包, 用于使用zookeeper 方式的服务注册和发现。
  10. Spring Cloud Stream:数据流操作开发包,封装了与Redis,Rabbit、Kafka 等发送接收消息。
  11. Spring Cloud CLI:基于Spring Boot CLI,可以让你以命令行方式快速建立云组件。

在这里插入图片描述

二. SpringCloud与Dubbo的区别

在这里插入图片描述

虽然在对项目的技术选型时 ,性能是个比较好的评价指标 ,但不是唯一的评价的指标. 就拿上图来说,虽然Dobbo与 SpringCloud的性能比大约是3 :1 ,在性能上虽然优于SpringCloud , 但是在看具体使用时 ,还需要根据项目的功能进行综合的选型.

三. SpringCloud版本说明

常见版本号说明

软件版本号:2.0.2.RELEASE
2:主版本号。当功能模块有较大更新或者整体架构发生变化时,主版本号会更新
0:次版本号。次版本表示只是局部的一些变动。
2:修改版本号。一般是bug 的修复或者是小的变动
RELEASE:希腊字母版本号。次版本号用户标注当前版本的软件处于哪个开发阶段
希腊字母版本号作用
Base设计阶段。只有相应的设计没有具体的功能实现。
Alpha软件的初级版本。存在较多的bug
Bate表示相对alpha 有了很大的进步,消除了严重的bug,还存在一些潜在的bug。
Release该版本表示最终版。

为什么Spring Cloud 版本用的是单词而不是数字

设计的目的是为了更好的管理每个Spring Cloud 的子项目的清单。避免子的版本号与子项目的版本号混淆。

版本号单词的定义规则

采用伦敦的地铁站名称来作为版本号的命名,根据首字母排序,字母顺序靠后的版本号越大。

版本发布计划说明

在这里插入图片描述

注意 : 在使用SpringCloud时 ,建议使用SR 版本(如下图),因为这样的版本是正式发布版 ,稳定且无重大bug

在这里插入图片描述

第三章 SpringBoot实战

一. SpringBoot复习

简介

Spring Boot 是在Spring 的基础之上产生的(确切的说是在Spring4.0 的版本的基础之上),
其中“Boot”的意思就是“引导”,意在简化开发模式,是开发者能够快速的开发出基于Spring 的应用。Spring Boot 含有一个内嵌的web 容器。我们开发的web 应用不需要作为war包部署到web 容器中,而是作为一个jar 包,在启动时根据web 服务器的配置进行加载。

出现背景

在项目中存在大量的xml 文件,配置相当繁琐
整合第三方框架时的配置问题
低效的开发效率与部署效率问题

作用

Spring Boot 使配置简单
Spring Boot 使编码加单
Spring Boot 使部署简单
Spring Boot 使监控简单

二. 快速创建SpringBoot项目(通过官网)

  1. 打开快速创建SpringBoot的官网 点击这里快速创建SpringBoot项目
  2. 根据下图提示创建项目 ,导入工作空间即可
    在这里插入图片描述

项目结构
注意 :
1.可能是Maven版本的问题导致pom.xml出现异常,但是运行不报错就可忽略
2.第一次导入SpringBoot项目(Maven)时,需要下载相关的jar 等待下载后即可使用!!!

三. SpringBoot配置文件详解

全局配置文件 application.properties

  1. 修改内嵌容器的端口号

    server.port=8888
    
  2. 自定义属性配置

    msg=Hello World
    
    @Value("${msg}")
    private String msg;
    
  3. 配置变量引用

    hello=szxy
    msg=Hello World ${hello}
    
    @Value("${msg}")
    private String msg;
    
  4. 随机值配置

    配置随机值

    num=${random.int}
    msg=Hello World ${num}
    
    @Value("${msg}")
    private String msg;
    

    用处:配置随机值,在程序中如果有一些运算需要一个随机值,那么可以使用该方式来生成。
    注意: 启动一次SpringBoot项目只生成一次。

    配置随机启动端口

    server.port=${random.int[1024,9999]}
    

    用处:在SpringCloud 的微服务中,我们是不需要记录IP 与端口号的。让其随机生成就可以了。

yml配置文件

yml 配置文件的语法

  1. 在properties 文件中是以“.”进行分割,在yml 中使用“:”
    进行分割
  2. yml 的数据格式和json 的格式很像,都是K-V 结构的。并且
    是通过“:”赋值
  3. 在yml 中缩进一定不能使用TAB 键,否则会报错。
  4. 每个Key 的冒号后面一定要加一个空格
    示例:
#配置SpringBoot的启动端口号
server:
      port: 9999
    
#配置可以被引用的常量   
msg: 
      ymlmsg: hello
      othermsg: world

引用yml中的常量

@Value("${msg.ymlmsg}")
	private String msg;
@Value("${msg.othermsg}")
	private String othermsg;

yml 配置文件与properties 文件的区别

  1. 配置文件的扩展名有变化
  2. 配置文件中的语法有变化

注 :在使用SpringBoot时 ,我们可以使用全局配置文件 application.properties,也可以使用 yml ,他们两个的作用都是定义全局属性 ,我们根据自己的喜好选择

logback 日志记录讲解

1 导入相关的jar 包(使用SpringBoot项目时 ,自动导入)
2 添加logback.xml 配置文件

<?xml version="1.0" encoding="UTF-8" ?>
 <configuration>
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->  
    <property name="LOG_HOME" value="${catalina.base}/logs/" />  
    <!-- 控制台输出 -->   
    <appender name="Stdout" class="ch.qos.logback.core.ConsoleAppender">
       <!-- 日志输出编码 -->  
        <layout class="ch.qos.logback.classic.PatternLayout">   
             <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--> 
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n   
            </pattern>   
        </layout>   
    </appender>   
    <!-- 按照每天生成日志文件 -->   
    <appender name="RollingFile"  class="ch.qos.logback.core.rolling.RollingFileAppender">   
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${LOG_HOME}/server.%d{yyyy-MM-dd}.log</FileNamePattern>   
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>   
        <layout class="ch.qos.logback.classic.PatternLayout">  
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--> 
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n   
            </pattern>   
       </layout> 
        <!--日志文件最大的大小-->
       <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
         <MaxFileSize>10MB</MaxFileSize>
       </triggeringPolicy>
    </appender>     

    <!-- 日志输出级别 -->
    <root level="info">   
        <appender-ref ref="Stdout" />   
        <appender-ref ref="RollingFile" />   
    </root> 



<!--日志异步到数据库 -->  
<!--     <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
        日志异步到数据库 
        <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
           连接池 
           <dataSource class="com.mchange.v2.c3p0.ComboPooledDataSource">
              <driverClass>com.mysql.jdbc.Driver</driverClass>
              <url>jdbc:mysql://127.0.0.1:3306/databaseName</url>
              <user>root</user>
              <password>root</password>
            </dataSource>
        </connectionSource>
  </appender> -->

</configuration>
日志的访问级别(由高到低)
日志级别作用
FATAL致命的, 表示非常严重的错误, 一般是系统错误
ERROR错误, 表示代码错误, 比较严重
WARN警告, 不影响程序的运行, 但是可能存在风险.
INFO信息, 表示一个普通的输出信息
DEBUG调试, 表示程序员人为的一些调试信息

Spring Boot 的配置文件- 多环境配置

在不同的开发阶段需要不同的配置来运行项目,我们避免重复配置和打包,打包前配置多个配置文件,运行时使用参数指定加载的配置文件。

profile:代表的就是一个环境变量
语法结构:application-{profile}.properties

0 需求:

application-dev.properties 开发环境
application-test.properteis 测试环境
application-prod.properteis 生产环境

1 打包项目 , 放到相应的磁盘下(我放的是f盘 )
注意打包时可能出现如下图所示异常
在这里插入图片描述

解决方案
调整单元测试用例所在包路径 <= @SpringBootConfiguration标记的类和启动类

大意为: 测试SpringBoot应用时,不需要像Spring一样通过注解@ContextConfiguration(classes=…​)或者@Configuration来指定需要加载的配置文件, @*Test注解会在没有配置的情况下自动寻找.
#搜寻算法是从单元测试类所在包向上层寻找,被@SpringBootApplication 或@SpringBootConfiguration 注解的class.

2 运行项目:
java -jar xxx.jar --spring.profiles.active={profile}
3 完成的命令:
java -jar springbootDemo-0.0.1-SNAPSHOT.jar --spring.profiles.active=test|dev|prod

在这里插入图片描述

四. SpringBoot核心注解与异常处理

相关注解作用
@SpringBootApplication代表是SpringBoot 的启动类。
@SpringBootConfiguration通过bean 对象来获取配置信息
@Configuration通过对bean 对象的操作替代spring 中xml 文件
@EnableAutoConfiguration完成一些初始化环境的配置。
@ComponentScan来完成spring 的组件扫描。替代之前我们在xml 文件中配置组件扫描的配置<context:component-scan pacage=”....”>
@RestController:1、表示一个Controller。
2、表示当前这个Controller 下的所有的方法都会以json 格式的数据响应。

异常处理(见第二部分)
https://blog.csdn.net/qq_43371556/article/details/100108538

五. 如何监控 SpringBoot 的健康状况

使用Actuator 检查与监控的步骤

  1. 在pom 文件中添加Actuator 的坐标

    <dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-actuator</artifactId>
    		</dependency>
    
  2. 修改配置文件
    2.0.0版本一下需要在全局配置文件中设置关闭安全限制
    management.security.enabled=false
    2.0.0版本以上需要修改默认显示的信息,使显示所有
    management.endpoints.web.exposure.include=*

  3. 通过访问url获取信息。
    例如:http://localhost:8080/actuator

使用可视化的监控报表-Spring Boot Admin

搭建服务端

服务端其实也是一个SpringBoot 项目.
官网:https://github.com/codecentric/spring-boot-admin
官网有详细的安装流程
添加依赖

<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-server</artifactId>
    <version>2.1.6</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

注意:
使用的SpringBoot的版本最好是和spring-boot-admin管理工具的版本一样!!!,否则很大可能报错

在启动类上添加@EnableAdminServer注解

@SpringBootApplication
@EnableAdminServer
public class SpringBootAdminApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringBootAdminApplication.class, args);
	}
}
搭建客户端

添加依赖:

<dependency>
    <groupId>de.codecentric</groupId>
    <artifactId>spring-boot-admin-starter-client</artifactId>
    <version>2.1.6</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

注意:
1.使用的SpringBoot的版本最好是和spring-boot-admin管理工具的版本一样!!!,否则很大可能报错,客户端
2.客户端使用的 spring-boot-security需要在指定的仓库才能下载!!!所以将客户端的pom文件放在下面

<!-- SpringBoot的安装工具 -->
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
	<!-- 指定本项目的仓库 -->
	<repositories>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
		</repository>
	</repositories>
	<pluginRepositories>
		<pluginRepository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
		</pluginRepository>
	</pluginRepositories>

修改配置文件:

#2.0.0版本以后需要设置显示所有
management.endpoints.web.exposure.include=*
#设置SpringBootAdmin监控服务的地址(client端用)
spring.boot.admin.client.url=http://localhost:9090  

#设置安全管理的用户名密码
spring.security.user.name=admin
spring.security.user.password=admin

添加一个类使执行器端点可访问

@Configuration
public class SecurityPermitAllConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().permitAll()  
            .and().csrf().disable();
    }
}

第四章 RabbitMQ实战

一. 安装RabbitMQ单机版

  1. 安装环境依赖

    yum -y install make gc-c gcc-c++ kernel-devel m4 ncurses-devel openssl-devel
    yum -y install perl
    
  2. 上传安装包
    otp_src_18.3.tar.gz
    rabbitmq-server-generic-unix-3.6.1.tar.xz

    软件和客户端安装包分享 https://pan.baidu.com/s/1T1RwvZ_Rj3f_ui2i7Dsnzw

  3. 解压缩otp_src_18.3.tar.gz
    tar -xvf otp_src_18.3.tar.gz

  4. 建立erlang安装环境
    创建目录mkdir /usr/local/erlang
    在解压目录下运行
    ./configure --prefix=/usr/local/erlang --with-ssl -enable-threads -enable-smmp-support -enable-kernel-poll --enable-hipe --without-javac

  5. 编译安装erlang
    在解压目录运行
    make && make install

  6. 配置erlang的环境变量

    vim /etc/profile

    ERLANG_HOME=/usr/local/erlang
    PATH=$ERLANG_HOME/bin:$PATH
    export ERLANG_HOME
    export PATH
    

特别注意: 涉及修改环境变量 ,需要重启虚拟机或者重新加载修改环境变量的这个文件,重新加载环境变量文件命令如下 source /etc/profile

  1. 测试erlang安装
    执行命令:erl
    出现下图所示则表示安装成功!!!
    在这里插入图片描述

  2. 解压rabbitmq

    在压缩包所在目录下, 先执行yum -y install xz
    再执行xz -d rabbitmq-server-generic-unix-3.6.1.tar.xz
    最后执行tar -xvf rabbitmq-server-generic-unix-3.6.1.tar
    拷贝解压后的文件夹到/usr/local/rabbitmq

    [root@bogon Downloads]# cp rabbitmq_server-3.6.1/ /usr/local/rabbitmq/ -rf
    
  3. 运行rabbitmq

    进入rabbitmq的sbin目录

    • 启动命令,该命令ctrl+c后会关闭服务 [不推荐]
      ./rabbitmq-server
    • 在后台启动Rabbit
      ./rabbitmq-server -detached
    • 关闭服务
      ./rabbitmqctl stop
    • 关闭服务(kill) 找到rabbitmq服务的pid [不推荐]
      ps -ef|grep rabbitmq
      kill -9 ****
  4. 设置管理员用户
    在这里插入图片描述

    #创建用户(账号mquser密码mquser),最好手动输入
    ./rabbitmqctl add_user mquser mquser
    #设置管理员权限
    ./rabbitmqctl set_user_tags mquser administrator
    #查看用户和权限
    ./rabbitmqctl list_users
    #给用户访问虚拟机的权限
    ./rabbitmqctl set_permissions -p / mquser '.*' '.*' '.*'
    
  5. 开放端口5672和15672端口( 或者使用时关闭防火墙 )

    #编辑防火墙配置文件
    vim /etc/sysconfig/iptables
    # 开放5672(RabbitMQ的端口号). 15672(RabbitMQ客户端的端口号)
    -A INPUT -m state --state NEW -m tcp -p tcp --dport 5672 -j ACCEPT
    -A INPUT -m state --state NEW -m tcp -p tcp --dport 15672 -j ACCEPT
    
  6. 启动mq的管理ui
    ./rabbitmq-plugins enable rabbitmq_management

  7. 访问rabbitmq的管理员UI
    http://服务器IP地址:15672/

注: 如果访问不到管理界面配置文件没有生效,最好是重启防火墙 ,让配置文件生效
在这里插入图片描述

二. RabbitMQ介绍

简介

RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)。RabbitMQ服务器是用Erlang语言编写的,而集群和故障转移是构建在开放电信平台框架上的。所有主要的编程语言均有与代理接口通讯的客户端库。

作用

  • 同步变异步
    在这里插入图片描述
    在这里插入图片描述
  • 解耦合
    在这里插入图片描述
  • 流量削锋
    在这里插入图片描述
  1. 消息队列基础知识
  • Provider

消息生产者,就是投递消息的程序。

  • Consumer

消息消费者,就是接受消息的程序。

  • 没有使用消息队列时消息传递方式

在这里插入图片描述

  • 使用消息队列后消息传递方式

在这里插入图片描述

  • 什么是队列?

队列就像存放了商品的仓库或者商店,是生产商品的工厂和购买商品的用户之间的中转站

  • 队列里存储了什么?

在 rabbitMQ 中,信息流从应用程序出发,来到 Rabbitmq 的队列,所有信息可以只 存储在一个队列中。
队列可以存储很多信息,因为它基本上是一个无限制的缓冲区,前提是你的机器有足够的存储空间。

  • 队列和应用程序的关系?

多个生产者可以将消息发送到同一个队列中,多个消息者也可以只从同一个队列接收数据。

三. 入门案例

在这里插入图片描述
pom文件头部报错可能是eclipse或jdk版本问题 ,不影响程序运行, 可忽略

  1. 创建jar项目,修改pom文件

    <!-- 继承的启动器的父项目,版本根据自己需求修改  -->
    <parent>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-parent</artifactId>
    		<version>2.1.6.RELEASE</version>
    		<relativePath /> <!-- lookup parent from repository -->
    	</parent>
    
    	<dependencies>
    		<!-- springBoot 的启动器 -->
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-web</artifactId>
    		</dependency>
    		<!-- rabbitmq -->
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-amqp</artifactId>
    		</dependency>
    		<!-- Sprng test 测试 -->
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-test</artifactId>
    			<scope>test</scope>
    		</dependency>
    	</dependencies>
    
  2. 全局配置文件

    #项目名(随便起)/ RabbitMQ的ip/端口
    spring.application.name=springcloud-rabbitmq
    spring.rabbitmq.host=192.168.179.136
    spring.rabbitmq.port=5672
    #RabbitMQ的用户名与密码
    spring.rabbitmq.username=mquser
    spring.rabbitmq.password=mquser
    
  3. 创建消息队列,注意所导的包
    在这里new一个队列后 ,发送者和使用者必须通过这个名字才能对改消息进行发送和接收!!!

    import org.springframework.amqp.core.Queue;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    /**
     * 1. 创建消息队列
     * @Configuration即用来代替Spring配置文件的,
     *  它就是一个@Component组件,接收一个value值也就是bean的名字,value可以不填。
     * @author chy
     *
     */
    @Configuration
    public class QueueConfig {
    	/**
    	 * 创建队列
    	 * 
    	 * @return
    	 */
    	@Bean
    	public Queue createQueue() {
    		return new Queue("hello-queue-队列名称保持一致");
    	}
    }
    
    
  4. 消息发送者类

    /**
     * 2. 创建消息发送者
     * @author chy
     *
     */
    @Component
    public class Sender {
    	@Autowired
    	private AmqpTemplate rabbitAmqpTemplate;
    
    	/*
    	 * 发送消息的方法
    	 */
    	public void send(String msg) {
    		// 向消息队列发送消息
    		// 参数一:队列的名称。
    		// 参数二:消息
    		this.rabbitAmqpTemplate.convertAndSend("hello-queue-队列名称保持一致", msg);
    	}
    }
    
    
  5. 消息接收者类

    /**
     * 3. 消息接收者
     * 
     * @author chy
     *
     */
    @Component
    public class Receiver {
    	/**
    	 * 接收消息的方法。采用消息队列监听机制
    	 * @param msg
    	 */
    	@RabbitListener(queues = "hello-queue-队列名称保持一致")
    	public void process(String msg) {
    		System.out.println("receiver: " + msg);
    	}
    }
    
  6. 启动类

    /**
     *  4. 启动类
     * @author chy
     *
     */
    @SpringBootApplication
    public class Application {
    
    	public static void main(String[] args) {
    		SpringApplication.run(Application.class, args);
    	}
    
    }
    
  7. 测试类

    @RunWith(SpringRunner.class)
    @SpringBootTest(classes = Application.class)
    public class QueueTest {
    	@Autowired
    	private Sender sender;
    
    	/*
    	 * 测试消息队列
    	 */
    	@Test
    	public void test1() {
    		this.sender.send("Meeeage: Hello RabbitMQ");
    	}
    }
    

    测试结果-控制台
    在这里插入图片描述
    测试结果-RabbitMQ客户端
    在这里插入图片描述

四. RabbitMQ原理

在这里插入图片描述

组件/说明
1. Message 消息
消息是不具名的,它由消息头消息体组成。消息体是不透明的,而消息头则由一系列可选属性组成,这些属性包括:routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出消息可能持久性存储)等。
2. Publisher 消息的生产者
也是一个向交换器发布消息的客户端应用程序
3. Consumer 消息的消费者
表示一个从消息队列中取得消息的客户端应用程序。
4. Exchange 交换器
用来接收生产者发送的消息并将这些消息路由给服务器中的队列。
三种常用的交换器类型 1. direct(发布与订阅 完全匹配) 2. fanout(广播) 3. topic(主题,规则匹配)
5. Binding 绑定
用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息 队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表
6. Queue 消息队列
用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一 个消息可投入一个或多个队列。消息一直在队列里面,等待消费者链接到这个队列将其取走。
7. Routing-key 路由键
RabbitMQ 决定消息该投递到哪个队列的规则。 队列通过路由键绑定到交换器。 消息发送到 MQ 服务器时,消息将拥有一个路由键,即便是空的,RabbitMQ 也会将其 和绑定使用的路由键进行匹配。 如果相匹配,消息将会投递到该队列。 如果不匹配,消息将会进入黑洞。
8. Connection 链接
指 rabbit 服务器和服务建立的 TCP 链接。
9. Channel 信道
1. Channel 中文叫做信道,是 TCP 里面的虚拟链接。例如:电缆相当于 TCP,信道是 一个独立光纤束,一条 TCP 连接上创建多条信道是没有问题的。
2. TCP 一旦打开,就会创建 AMQP 信道。
3. 无论是发布消息、接收消息、订阅队列,这些动作都是通过信道完成的。
10. Virtual Host虚拟主机
表示一批交换器,消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在链接时指定, RabbitMQ 默认的 vhost 是"/"
11. Borker
表示消息队列服务器实体。
12. 交换器和队列的关系
交换器是通过路由键和队列绑定在一起的,如果消息拥有的路由键跟队列和交换器的 路由键匹配,那么消息就会被路由到该绑定的队列中。也就是说,消息到队列的过程中,消息首先会经过交换器,接下来交换器在通过路由 键匹配分发消息到具体的队列中。路由键可以理解为匹配的规则。

五. RabbitMQ为什么要使用信道

  1. TCP 的创建和销毁开销特别大。创建需要 3 次握手,销毁需要 4 次分手
  2. 如果不用信道,应用程序就会以TCP 链接 Rabbit,高峰时每秒成千上万条链接会造成资源巨大的浪费,而且操作系统每秒处理 TCP 链接数也是有限制的,必定造成性能瓶颈。
  3. 信道的原理是一条线程一条通道,多条线程多条通道同用一条 TCP 链接。 一条 TCP 链接可以容纳无限的信道,即使每秒成千上万的请求也不会成为性能的瓶颈。

六. RabbitMQ交换器

pom.xml

<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.21.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<dependencies>
		<!-- Spring Boot Web 启动器 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!-- Spring Boot RabbitMQ 消息队列启动器 -->
		<dependency>
			<groupId>org.springframework.amqp</groupId>
			<artifactId>spring-rabbit</artifactId>
		</dependency>
		<!-- Spring Boot 监控启动器 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
		<!-- Spring Boot Test 启动器 -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<!-- Spring Boot 监控客户端启动类 -->
		<dependency>
			<groupId>de.codecentric</groupId>
			<artifactId>spring-boot-admin-starter-client</artifactId>
			<version>1.5.7</version>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

Direct交换器(发布与订阅完全匹配)

在这里插入图片描述
消息提供者

1.配置文件

#项目名/ RabbitMQ的ip/端口
#spring.application.name=springcloud-provider
#spring.rabbitmq.host=192.168.179.136
#spring.rabbitmq.port=5672
#RabbitMQ的用户名与密码
#spring.rabbitmq.username=mquser
#spring.rabbitmq.password=mquser

# 交换器
mq.config.exchange=log-direct
# 队列
mq.config.queue.info=log.info 
# 路由键
mq.config.queue.info.routing.key=log.info.routing.key
# 队列
mq.config.queue.error=log.error 
# 路由键
mq.config.queue.error.routing.key=log.error.routing.key

2.发送者类(两个,作用相同)

/**
 * 消息发送者-发送错误消息
 *
 */
@Component
public class ErrorSender {

	@Autowired
	private RabbitTemplate rabbiteTemplate;
	@Value("${mq.config.exchange}")
	private String exchange;
	@Value("${mq.config.queue.error.routing.key}")
	private String key;
	/**
	 * 发送消息
	 * @param msg
	 */
	public void sendMsg(String msg) {
		/**
		 * 第一个参数表示 交换器的名称
		 * 第二个参数表示  路由键
		 * 第三个参数表示 消息,将要发送的消息
		 */
		this.rabbiteTemplate.convertAndSend(exchange,key,msg);
	}
	
}

/**
 * 消息发送者-发送info消息
 *
 */
@Component
public class InfoSender {

	@Autowired
	private RabbitTemplate rabbiteTemplate;
	@Value("${mq.config.exchange}")
	private String exchange;
	@Value("${mq.config.queue.info.routing.key}")
	private String key;
	/**
	 * 发送消息
	 * @param msg
	 */
	public void sendMsg(String msg) {
		/**
		 * 第一个参数表示 交换器的名称
		 * 第二个参数表示  路由键
		 * 第三个参数表示 消息,将要发送的消息
		 */
		this.rabbiteTemplate.convertAndSend(exchange,key,msg);
	}
	
}

3.启动类
注意
1.使用RabbitMQ启动类需要加@EnableRabbit注解!!!
2.在这里提供者的启动类需要配合测试类使用

@SpringBootApplication
@EnableRabbit
public class App {

	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}
	
}

4.测试类

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class QueueTest {

	@Autowired
	private ErrorSender errorSender;
	@Autowired
	private InfoSender infoSender;
	
	@Test
	public void testError() throws Exception{
		   // TODO 
		   int flag = 0; 
		  while(true) {
			  Thread.sleep(2000);
			  this.errorSender.sendMsg("Hello World RabbitMq"+(flag++));
		  }
		 
	}
	@Test
	public void testInfo() throws Exception{
		// TODO 
		int flag = 0; 
		while(true) {
			Thread.sleep(2000);
			this.infoSender.sendMsg("Hello World RabbitMq"+(flag++));
		}
		
	}
	
}

消息消费者
1.配置文件

#项目名/ RabbitMQ的ip/端口
spring.application.name=springcloud-consumer
spring.rabbitmq.host=192.168.179.136
spring.rabbitmq.port=5672
#RabbitMQ的用户名与密码
spring.rabbitmq.username=mquser
spring.rabbitmq.password=mquser

# 交换器
mq.config.exchange=log-direct
# 队列
mq.config.queue.info=log.info 
# 路由键
mq.config.queue.info.routing.key=log.info.routing.key
# 队列
mq.config.queue.error=log.error 
# 路由键
mq.config.queue.error.routing.key=log.error.routing.key

# 解决 ACK 反馈
#开启重试 
spring.rabbitmq.listener.retry.enabled=true 
#重试次数,默认为 3 次 
spring.rabbitmq.listener.retry.max-attempts=5

2.接收者类(两个,结构相同)
重点掌握相关注解的使用
@RabbitHandler用于令消费者的相关方法监听服务队列

/**
 * 消息接收者 消息接收者
 * 
 * @RabbitListener bindings:绑定队列
 * 
 * @QueueBinding value:绑定队列的名称 
 *               exchange:配置交换器 
 *               key:路由键
 * @Queue value:配置队列名称 
 *        autoDelete:是否是一个可删除的临时队列 
 * 
 * @Exchange value:为交换器起个名称  
 *           type:指定具体的交换器类型
 */
@Component
@RabbitListener(bindings = @QueueBinding(
		exchange = @Exchange(value = "${mq.config.exchange}", type = ExchangeTypes.DIRECT),
		value = @Queue(value = "${mq.config.queue.error}", autoDelete = "false"),
		key = "${mq.config.queue.error.routing.key}")
)
public class ErrorReceiver {

	/**
	 * 监听指定的消息队列
	 * 
	 * @param msg
	 * @throws Exception 
	 */
	@RabbitHandler
	public void receiveMsg(String msg) throws Exception {
		System.out.println("Error------"+msg);
		// 手动抛出异常
		//throw new Exception();
	}
}

/**
 * 消息接收者 消息接收者
 * 
 * @RabbitListener bindings:绑定队列
 * 
 * @QueueBinding value:绑定队列的名称 
 *               exchange:配置交换器 
 * @Queue value:配置队列名称 
 *        autoDelete:是否是一个可删除的临时队列 
 * 
 * @Exchange value:为交换器起个名称  
 *           type:指定具体的交换器类型
 */
@Component
@RabbitListener(bindings = @QueueBinding(
		exchange = @Exchange(value = "${mq.config.exchange}", type = ExchangeTypes.DIRECT),
		value = @Queue(value = "${mq.config.queue.info}", autoDelete = "true"),
		key = "${mq.config.queue.info.routing.key}")
)
public class InfoReceiver {

	/**
	 * 监听指定的消息队列
	 * 
	 * @param msg
	 */
	@RabbitHandler
	public void receiveMsg(String msg) {
		System.out.println("Info------"+msg);
	}
}

3.启动类(启动消费者)
注意使用RabbitMQ启动类需要加@EnableRabbit注解!!!

@SpringBootApplication
@EnableRabbit
public class App {

	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}
	
}

在这里插入图片描述

Topic交换器(主题,规则匹配)

在这里插入图片描述
消息生产者
1.配置文件

##项目名/ RabbitMQ的ip/端口
#spring.application.name=springcloud-provider
#spring.rabbitmq.host=192.168.179.136
#spring.rabbitmq.port=5672
##RabbitMQ的用户名与密码
#spring.rabbitmq.username=mquser
#spring.rabbitmq.password=mquser

# 交换器
mq.config.exchange=log.topic

2.发送者类(3个)

@Component
public class OrderSender {

	@Autowired
	private RabbitTemplate rabbiteTemplate;
	@Value("${mq.config.exchange}")
	private String exchange;
	
	/**
	 * 发送消息
	 * @param msg
	 */
	public void sendMsg(String msg) {
		/**
		 * 第一个参数表示 交换器的名称
		 * 第二个参数表示  路由键
		 * 第三个参数表示 消息,将要发送的消息
		 */
		this.rabbiteTemplate.convertAndSend(exchange,"order.log.debug","order.log.Debgu"+msg);
		this.rabbiteTemplate.convertAndSend(exchange,"order.log.info","order.log.Info"+msg);
		this.rabbiteTemplate.convertAndSend(exchange,"order.log.warn","order.log.Warn"+msg);
		this.rabbiteTemplate.convertAndSend(exchange,"order.log.error","order.log.Error"+msg);
	}
	
}

@Component
public class ProductSender {

	@Autowired
	private RabbitTemplate rabbiteTemplate;
	@Value("${mq.config.exchange}")
	private String exchange;
	
	/**
	 * 发送消息
	 * @param msg
	 */
	public void sendMsg(String msg) {
		/**
		 * 第一个参数表示 交换器的名称
		 * 第二个参数表示  路由键
		 * 第三个参数表示 消息,将要发送的消息
		 */
		this.rabbiteTemplate.convertAndSend(exchange,"product.log.debug","product.log.Debgu"+msg);
		this.rabbiteTemplate.convertAndSend(exchange,"product.log.info","product.log.Info"+msg);
		this.rabbiteTemplate.convertAndSend(exchange,"product.log.warn","product.log.Warn"+msg);
		this.rabbiteTemplate.convertAndSend(exchange,"product.log.error","product.log.Error"+msg);
	}
	
}

@Component
public class UserSender {

	@Autowired
	private RabbitTemplate rabbiteTemplate;
	@Value("${mq.config.exchange}")
	private String exchange;
	
	/**
	 * 发送消息
	 * @param msg
	 */
	public void sendMsg(String msg) {
		/**
		 * 第一个参数表示 交换器的名称
		 * 第二个参数表示  路由键
		 * 第三个参数表示 消息,将要发送的消息
		 */
		this.rabbiteTemplate.convertAndSend(exchange,"user.log.debug","user.log.Debgu"+msg);
		this.rabbiteTemplate.convertAndSend(exchange,"user.log.info","user.log.Info"+msg);
		this.rabbiteTemplate.convertAndSend(exchange,"user.log.warn","user.log.Warn"+msg);
		this.rabbiteTemplate.convertAndSend(exchange,"user.log.error","user.log.Error"+msg);
	}
	
}

3.启动类

@SpringBootApplication
@EnableRabbit
public class App {

	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}
	
}

4.测试类(生产者执行的入口,会默认启动上面的启动类)

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class QueueTest {

	@Autowired
	private OrderSender orderSender;
	@Autowired
	private ProductSender productSender;
	@Autowired
	private UserSender userSender;
	
	@Test
	public void test() throws Exception{
		  this.orderSender.sendMsg("UserSender----");
		  this.productSender.sendMsg("Producer----");
		  this.userSender.sendMsg("UserSnder------");
	}
	
	
}

消息消费者
1.配置文件

##项目名/ RabbitMQ的ip/端口
#spring.application.name=springcloud-consumer
#spring.rabbitmq.host=192.168.179.136
#spring.rabbitmq.port=5672
##RabbitMQ的用户名与密码
#spring.rabbitmq.username=mquser
#spring.rabbitmq.password=mquser

#设置交换器的名称
mq.config.exchange=log.topic
#info 队列名称
mq.config.queue.info=log.info
#error 队列名称
mq.config.queue.error=log.error
#log 队列名称
mq.config.queue.logs=log.all

2.接收者类(3个)

/**
 * 消息接收者
 * 
 * @RabbitListener bindings:绑定队列
 * 
 * @QueueBinding value:绑定队列的名称 
 *               exchange:配置交换器 
 *               key:路由键  :接收以el表达式内部代码格式结尾的消息
 * @Queue value:配置队列名称 
 *        autoDelete:是否是一个可删除的临时队列 
 * 
 * @Exchange value:为交换器起个名称  
 *           type:指定具体的交换器类型
 *
 */
@Component
@RabbitListener(bindings = @QueueBinding(
		exchange = @Exchange(value = "${mq.config.exchange}", type = ExchangeTypes.TOPIC),
		value = @Queue(value = "${mq.config.queue.error}", autoDelete = "true"),
		key = "*.log.error")
)
public class ErrorReceiver {

	/**
	 * 监听指定的消息队列
	 * 
	 * @param msg
	 * @throws Exception 
	 */
	@RabbitHandler
	public void Receiver(String msg){
		System.out.println("......Error........receiver:"+msg);
	
	}
}

@Component
@RabbitListener(bindings = @QueueBinding(
		exchange = @Exchange(value = "${mq.config.exchange}", type = ExchangeTypes.TOPIC),
		value = @Queue(value = "${mq.config.queue.info}", autoDelete = "true"),
		key = "*.log.info")
)
public class InfoReceiver {

	/**
	 * 监听指定的消息队列
	 * 
	 * @param msg
	 */
	@RabbitHandler
	public void Receiver(String msg) {
		System.out.println("......Info........receiver:"+msg);
	}
}


@Component
@RabbitListener(bindings = @QueueBinding(
		exchange = @Exchange(value = "${mq.config.exchange}", type = ExchangeTypes.TOPIC),
		value = @Queue(value = "${mq.config.queue.logs}", autoDelete = "true"),
		key = "*.log.*")
)
public class LogsReceiver {

	/**
	 * 监听指定的消息队列
	 * 
	 * @param msg
	 * @throws Exception 
	 */
	@RabbitHandler
	public void Receiver(String msg) throws Exception {
		System.out.println("......All........receiver:"+msg);
	
	}
}

3.启动类

@SpringBootApplication
@EnableRabbit
public class App {

	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}
	
}

Fanout交换器(广播)

在这里插入图片描述
消息生产者

1.配置文件

#项目名/ RabbitMQ的ip/端口
#spring.application.name=springcloud-provider
#spring.rabbitmq.host=192.168.179.136
#spring.rabbitmq.port=5672
#RabbitMQ的用户名与密码
#spring.rabbitmq.username=mquser
#spring.rabbitmq.password=mquser

# 交换器
mq.config.exchange=order.fanout

2.发送类

@Component
public class Sender {

	@Autowired
	private RabbitTemplate rabbiteTemplate;
	@Value("${mq.config.exchange}")
	private String exchange;
	/**
	 * 发送消息
	 * @param msg
	 */
	public void sendMsg(String msg) {
		/**
		 * 第一个参数表示 交换器的名称
		 * 第二个参数表示  路由键
		 * 第三个参数表示 消息,将要发送的消息
		 */
		this.rabbiteTemplate.convertAndSend(exchange,"",msg);
	}
	
}

消息消费者
1.配置文件

#项目名/ RabbitMQ的ip/端口
#spring.application.name=springcloud-consumer
#spring.rabbitmq.host=192.168.179.136
#spring.rabbitmq.port=5672
#RabbitMQ的用户名与密码
#spring.rabbitmq.username=mquser
#spring.rabbitmq.password=mquser

# 交换器
mq.config.exchange=order.fanout
# 设置短信服务队列名称
mq.config.queue.sms=order.sms 
# push服务队列名称
mq.config.queue.push=order.push

2.接收者类(两个)

/**
 * 消息接收者 消息接收者
 * @RabbitListener bindings:绑定队列
 * @QueueBinding value:绑定队列的名称 
 *               exchange:配置交换器 
 * @Queue value:配置队列名称 
 *        autoDelete:是否是一个可删除的临时队列 
 * 
 * @Exchange value:为交换器起个名称  
 *           type:指定具体的交换器类型
 */
@Component
@RabbitListener(bindings = @QueueBinding(
		exchange = @Exchange(value = "${mq.config.exchange}", type = ExchangeTypes.FANOUT),
		value = @Queue(value = "${mq.config.queue.push}", autoDelete = "true")
		)
)
public class PushReceiver {

	/**
	 * 监听指定的消息队列
	 * 
	 * @param msg
	 */
	@RabbitHandler
	public void receiveMsg(String msg) {
		System.out.println("push消费者消费信息:------"+msg);
	}
}

@Component
@RabbitListener(bindings = @QueueBinding(
		exchange = @Exchange(value = "${mq.config.exchange}", type = ExchangeTypes.FANOUT),
		value = @Queue(value = "${mq.config.queue.sms}", autoDelete = "false")
		)
)
public class SmsReceiver {

	/**
	 * 监听指定的消息队列
	 * 
	 * @param msg
	 * @throws Exception 
	 */
	@RabbitHandler
	public void receiveMsg(String msg) throws Exception {
		System.out.println("sms消费者消费信息------"+msg);
		// 手动抛出异常
		//throw new Exception();
	}
}

3.启动类(启动消费者项目)

@SpringBootApplication
@EnableRabbit
public class App {

	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}
	
}

测试结果
在这里插入图片描述

七. 实现服务的松耦合设计

在Fanout交换器演示案例中添加如下代码,实现松耦合功能

1.在消息消费者的配置文件中

# 新增红包队列, 作为松耦合实例
mq.config.queue.red=order.red
  1. 新增一个接收类
@Component
@RabbitListener(bindings = @QueueBinding(
		exchange = @Exchange(value = "${mq.config.exchange}", type = ExchangeTypes.FANOUT),
		value = @Queue(value = "${mq.config.queue.red}", autoDelete = "true")
		)
)
public class RedReceiver {

	/**
	 * 监听指定的消息队列
	 * 
	 * @param msg
	 * @throws Exception 
	 */
	@RabbitHandler
	public void receiveMsg(String msg) throws Exception {
		System.out.println("我自愿给chy发送888红包------"+msg);
	}
}

测试结果
在这里插入图片描述

通过上述案例可知 ,对新队列的添加不仅实现了松耦合的功能 ,而且实现开闭原则 ,即对拓展开放, 对修改关闭

八. 消息处理

1 RabbitMQ 的消息持久化处理

消息的可靠性是RabbitMQ 的一大特色,那么RabbitMQ 是如何保证消息可靠性的呢——消息持久化。

autoDelete 属性
@Queue: 当所有消费客户端连接断开后,是否自动删除
队列true:删除false:不删除
设置删除 ,会将系统宕机时发送给消息队列的消息删除 ; 反之则不会删除 ,待重启后还能读取消息队列中的消息

@Exchange:当所有绑定队列都不在使用时,是否自动
删除交换器true:删除false:不删除

定位到消息消费者项目的一个接收者类中

@Component
@RabbitListener(bindings = @QueueBinding(
		exchange = @Exchange(value = "${mq.config.exchange}", type = ExchangeTypes.DIRECT),
		value = @Queue(value = "${mq.config.queue.error}", autoDelete = "false"),
		key = "${mq.config.queue.error.routing.key}")
)
public class ErrorReceiver {
	@RabbitHandler
	public void receiveMsg(String msg) throws Exception {
		System.out.println("Error------"+msg);
		// 手动抛出异常
		throw new Exception();
	}
}

Features中带有AD表示设置了自动删除
在这里插入图片描述
关闭自动删除 ,项目宕机 ,仍会将消息发送到队列中 ,可通过Ready或total查看
在这里插入图片描述

重启后, 读取消息队列中保存的消息

在这里插入图片描述

2. RabbitMQ 中的消息确认ACK 机制

相关概念
在这里插入图片描述

解决方法

  1. 使用try-catch块捕获消费者中的异常
  2. 设置重试次数(在全局配置文件application.properties添加如下代码)
#开启重试
spring.rabbitmq.listener.simple.retry.enabled=true 
#最大重试次数
spring.rabbitmq.listener.simple.retry. max-attempts=5

第五章 Eureka注册中心

一. 服务的注册中心

服务注册中心就是将微服务实现服务化管理的核心组件,类似于目录服务的作用,主要用来存储服务信息,譬如提供者 url 串、路由信息等。服务注册中心是SOA 架构中最基础的设施之一。

  1. 服务注册中心的作用

    • 服务的注册
    • 服务的发现
  2. 常见的注册中心有哪些

    • Dubbo 的注册中心 Zookeeper
    • Sringcloud 的注册中心 Eureka
  3. 服务注册中心解决了什么问题

    • 服务管理
    • 服务的依赖关系管理
  4. 什么是 Eureka 注册中心
    Eureka 是 Netflix 开发的服务发现组件,本身是一个基于 REST 的服务。Spring Cloud 将它集成在其子项目 spring-cloud-netflix 中,以实现 Spring Cloud 的服务注册于发现,同时 还提供了负载均衡、故障转移等能力。

  5. Eureka 注册中心三种角色

角色作用
Eureka Server通过 Register、Get、Renew 等接口提供服务的注册和发现。
Application Service (Service Provider)服务提供方 , 把自身的服务实例注册到 Eureka Server 中
Application Client (Service Consumer)服务调用方 , 通过 Eureka Server 获取服务列表,消费服务。

二. 搭建Eureka注册中心

1. 搭建服务注册中心

pom.xml文件

<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>
  <!--SpringBoot项目打包插件 -->
   <build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

在启动类上添加注解@EnableEurekaServer

@SpringBootApplication
@EnableEurekaServer
public class SpringCloudEurekaApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringCloudEurekaApplication.class, args);
	}

}

修改配置文件 application.yml

server:
  port: 8888
spring:
  application:
    name: Eureka-Server

eureka:
  client:
    register-with-eureka: false  #是否将自己注册到 Eureka-Server 中,默认的为 true
    fetch-registry: false   #是否冲 Eureka-Server 中获取服务注册信息,默认为 true 

2. 搭建高可用 Eureka 注册中心(Eureka 集群)

  1. 创建项目,同上

  2. 配置文件 ,在上面基础上额外添加

    #设置 eureka 实例名称,与配置文件的变量为主 
    eureka.instance.hostname=服务名 
    #设置服务注册中心地址,指向另一个注册中心
    eureka.client.serviceUrl.defaultZone=http://ip+port/eu reka/ 
    

    在搭建 Eureka 集群时,需要添加多个配置文件,并且使用 SpringBoot 的多环境配置方式。集群中需要多少节点就添加多少个配置文件。

  3. 配置节点信息
    application-eureka1.properties(properties可以和yml文件相互转换)

    spring.application.name=eureka-server
    server.port=8761
    #设置eureka 实例名称,与配置文件的变量为主
    eureka.instance.hostname=eureka1
    #设置服务注册中心地址,指向另一个注册中心,如果是两个以上,只需要在下面加上逗号 ,然后加上完整连接即可
    eureka.client.serviceUrl.defaultZone=http://admin:admin@eureka2:8761/eureka/
    spring.security.user.name=admin
    spring.security.user.password=admin
    
    #Eureka注册中心
    #是否将自己注册到 Eureka-Server 中,默认的为 true,使用单机的时候需要改为false
    #eureka.client.register-with-eureka=false
    #是否从 Eureka-Server 中获取服务注册信息,默认为 true
    eureka.client.fetch-registry=false
    
    

    application-eureka2.properties

    spring.application.name=eureka-server
    server.port=8761
    #设置eureka 实例名称,与配置文件的变量为主
    eureka.instance.hostname=eureka2
    #设置服务注册中心地址,指向另一个注册中心,如果是两个以上,只需要在下面加上逗号 ,然后加上完整连接即可
    eureka.client.serviceUrl.defaultZone=http://admin:admin@eureka1:8761/eureka/
    
    spring.security.user.name=admin
    spring.security.user.password=admin
    
    #Eureka注册中心
    #是否将自己注册到 Eureka-Server 中,默认的为 true
    #eureka.client.register-with-eureka=false
    #是否从 Eureka-Server 中获取服务注册信息,默认为 true
    eureka.client.fetch-registry=false
    
    

    修改本地主机映射
    在这里插入图片描述
    修改虚拟机集群主机映射(集群中的每个主机都要配置)

    在这里插入图片描述

  4. 添加日志
    将logback.xml放入resources目录下

    <?xml version="1.0" encoding="UTF-8" ?>
     <configuration>
    <!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->  
        <property name="LOG_HOME" value="${catalina.base}/logs/" />  
        <!-- 控制台输出 -->   
        <appender name="Stdout" class="ch.qos.logback.core.ConsoleAppender">
           <!-- 日志输出编码 -->  
            <layout class="ch.qos.logback.classic.PatternLayout">   
                 <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--> 
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n   
                </pattern>   
            </layout>   
        </appender>   
        <!-- 按照每天生成日志文件 -->   
        <appender name="RollingFile"  class="ch.qos.logback.core.rolling.RollingFileAppender">   
            <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                <!--日志文件输出的文件名-->
                <FileNamePattern>${LOG_HOME}/server.%d{yyyy-MM-dd}.log</FileNamePattern>   
                <MaxHistory>30</MaxHistory>
            </rollingPolicy>   
            <layout class="ch.qos.logback.classic.PatternLayout">  
                <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--> 
                <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n   
                </pattern>   
           </layout> 
            <!--日志文件最大的大小-->
           <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
             <MaxFileSize>10MB</MaxFileSize>
           </triggeringPolicy>
        </appender>     
    
        <!-- 日志输出级别 -->
        <root level="info">   
            <appender-ref ref="Stdout" />   
            <appender-ref ref="RollingFile" />   
        </root> 
    
    
    
    <!--日志异步到数据库 -->  
    <!--     <appender name="DB" class="ch.qos.logback.classic.db.DBAppender">
            日志异步到数据库 
            <connectionSource class="ch.qos.logback.core.db.DriverManagerConnectionSource">
               连接池 
               <dataSource class="com.mchange.v2.c3p0.ComboPooledDataSource">
                  <driverClass>com.mysql.jdbc.Driver</driverClass>
                  <url>jdbc:mysql://127.0.0.1:3306/databaseName</url>
                  <user>root</user>
                  <password>root</password>
                </dataSource>
            </connectionSource>
      </appender> -->
    
    </configuration>
    
  5. 部署项目
    使用maven打包项目, 将项目上传到linux下 , 编写并使用脚本启动程序 server.sh

    #!/bin/bash
     
    cd `dirname $0`
     
    CUR_SHELL_DIR=`pwd`
    CUR_SHELL_NAME=`basename ${BASH_SOURCE}`
     
    JAR_NAME="项目名称"
    JAR_PATH=$CUR_SHELL_DIR/$JAR_NAME
     
    #JAVA_MEM_OPTS=" -server -Xms1024m -Xmx1024m -XX:PermSize=128m"
    JAVA_MEM_OPTS=""
     
     #配置集群时使用
    SPRING_PROFILES_ACTIV="-Dspring.profiles.active=配置文件变量名称"
    #配置单机Eureka使用
    #SPRING_PROFILES_ACTIV=""
    LOG_DIR=$CUR_SHELL_DIR/logs
    LOG_PATH=$LOG_DIR/${JAR_NAME%..log
     
    echo_help()
    {
        echo -e "syntax: sh $CUR_SHELL_NAME start|stop"
    }
     
    if [ -z $1 ];then
        echo_help
        exit 1
    fi
     
    if [ ! -d "$LOG_DIR" ];then
        mkdir "$LOG_DIR"
    fi
     
    if [ ! -f "$LOG_PATH" ];then
        touch "$LOG_DIR"
    fi
     
    if [ "$1" == "start" ];then
     
        # check server
        PIDS=`ps --no-heading -C java -f --width 1000 | grep $JAR_NAME | awk '{print $2}'`
        if [ -n "$PIDS" ]; then
            echo -e "ERROR: The $JAR_NAME already started and the PID is ${PIDS}."
            exit 1
        fi
     
        echo "Starting the $JAR_NAME..."
     
        # start
        nohup java $JAVA_MEM_OPTS -jar $SPRING_PROFILES_ACTIV $JAR_PATH >> $LOG_PATH 2>&1 &
     
        COUNT=0
        while [ $COUNT -lt 1 ]; do
            sleep 1
            COUNT=`ps  --no-heading -C java -f --width 1000 | grep "$JAR_NAME" | awk '{print $2}' | wc -l`
            if [ $COUNT -gt 0 ]; then
                break
            fi
        done
        PIDS=`ps  --no-heading -C java -f --width 1000 | grep "$JAR_NAME" | awk '{print $2}'`
        echo "${JAR_NAME} Started and the PID is ${PIDS}."
        echo "You can check the log file in ${LOG_PATH} for details."
     
    elif [ "$1" == "stop" ];then
     
        PIDS=`ps --no-heading -C java -f --width 1000 | grep $JAR_NAME | awk '{print $2}'`
        if [ -z "$PIDS" ]; then
            echo "ERROR:The $JAR_NAME does not started!"
            exit 1
        fi
     
        echo -e "Stopping the $JAR_NAME..."
     
        for PID in $PIDS; do
            kill $PID > /dev/null 2>&1
        done
     
        COUNT=0
        while [ $COUNT -lt 1 ]; do
            sleep 1
            COUNT=1
            for PID in $PIDS ; do
                PID_EXIST=`ps --no-heading -p $PID`
                if [ -n "$PID_EXIST" ]; then
                    COUNT=0
                    break
                fi
            done
        done
     
        echo -e "${JAR_NAME} Stopped and the PID is ${PIDS}."
    else
        echo_help
        exit 1
    fi
    

    注意:
    windows下文件格式可能在linux下无法使用,使用notepad++转换成unix:编辑–>文档格式转换–>转为unix。

    赋予脚本执行权限  chmod +R 755 server.sh
    启动脚本 server.sh -start
    关闭脚本 server.sh -stop
    查看日志:在catalina/logs/server.log中
    测试访问http://192.168.179.136:8761
    http://192.168.179.137:8761
    

3. 在高可用的 Eureka 注册中心中构建 provider 服务

  1. 创建jar项目

  2. 修改pom文件

<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.7.RELEASE</version>
	</parent>
<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
		<maven-jar-plugin.version>2.6</maven-jar-plugin.version>
	</properties>

<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>
  1. 修改启动类
    添加@EnableEurekaClient,表示可以被Eureka注册中心发现
@SpringBootApplication
@EnableEurekaClient
public class SpringCloudEurekaApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringCloudEurekaApplication.class, args);
	}

}
  1. 修改配置文件
server:
  port: 8081
spring:
  application:
    name: Eureka-Provider


eureka:
  client:
    serviceUrl:
      defaultZone: http://admin:admin@eureka1:8761/eureka/,http://admin:admin@eureka2:8761/eureka/




    

  1. 控制层:
@RestController
public class UserController {

	@RequestMapping("/user")
	public List<User> user() {
		List<User> list=new ArrayList<User>();
		
		list.add(new User(1, "张三", 21));
		list.add(new User(2, "李四", 22));
		list.add(new User(3, "王五", 23));
		list.add(new User(4, "赵六", 24));
		return list;
		
	}
}

在这里插入图片描述

4. 在高可用的 Eureka 注册中心中构建 consumer 服务

  1. 创建jar项目

  2. 修改pom文件

<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.7.RELEASE</version>
	</parent>
<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Greenwich.SR2</spring-cloud.version>
		<maven-jar-plugin.version>2.6</maven-jar-plugin.version>
	</properties>

<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>
  1. 修改启动类(添加@EnableEurekaClient)
@SpringBootApplication
@EnableEurekaClient
public class SpringCloudEurekaApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringCloudEurekaApplication.class, args);
	}

}
  1. 修改配置文件
server:
  port: 8091
spring:
  application:
    name: Eureka-Consumer


eureka:
  client:
    serviceUrl:
      defaultZone: http://admin:admin@eureka1:8761/eureka/,http://admin:admin@eureka2:8761/eureka/
  1. 服务层(重点)
@Service
public class UserService {

	
	@Autowired
	//ribbon 负载均衡器 
	private LoadBalancerClient balancerClient;
	
	public List<User> getUsers(){
		//选择调用的服务的名称   
		//ServiceInstance 封装了服务的基本信息,如 IP,端口 
		ServiceInstance si = this.balancerClient.choose("Eureka-Provider");
		String url="http://"+si.getHost()+":"+si.getPort()+"/user";
		
		//springMVC RestTemplate 
		//发送http请求
		RestTemplate restTemplate=new RestTemplate();
		
		//参数化类型引用
		ParameterizedTypeReference<List<User>> responseType=new ParameterizedTypeReference<List<User>>() { };
		
		//ResponseEntity:封装了返回值信息                       exchange发送请求         路径    方式                   参数       
		ResponseEntity<List<User>> responseEntity = restTemplate.exchange(url, HttpMethod.GET, null, responseType);
		
		//获取响应体
		List<User> list=responseEntity.getBody();
		return list;
	}
}
  1. controller

在consumer端访问http://localhost:8091/consumer.
会获取到provider的控制层产生的数据。

三. 理解Eureka注册中心

1. Eureka 注册中心架构原理

1 Eureka 架构图

在这里插入图片描述

Register(服务注册):把自己的 IP 和端口注册给 Eureka。
Renew(服务续约):发送心跳包,每 30 秒发送一次。告诉 Eureka 自己还活着。
Cancel(服务下线):当 provider 关闭时会向 Eureka 发送消息,把自己从服务列表中删除。防止consumer 调用到不存在的服务。
Get Registry(获取服务注册列表):获取其他服务列表。
Replicate(集群中数据同步):eureka 集群中的数据复制与同步。
Make Remote Call(远程调用):完成服务的远程调用。

2. 基于分布式 CAP 定理,分析注册中心两大主流框架:Eureka与 Zookeeper 的区别

1. 什么是 CAP 原则
CAP 原则又称 CAP 定理,指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得。
CAP 由 Eric Brewer 在 2000 年 PODC 会议上提出。该猜想在提出两年后被证明成 立,成为我们熟知的 CAP 定理
在这里插入图片描述
cap定理研究策略
在这里插入图片描述
2. Zookeeper 与 Eureka 的区别

在这里插入图片描述

四. 优雅停服

1. 在什么条件下,Eureka 会启动自我保护
在这里插入图片描述

2 为什么要启动自我保护
在这里插入图片描述

3 如何关闭自我保护
修改 Eureka Server 配置文件

#关闭自我保护:true 为开启自我保护,false 为关闭自我保护
eureka.server.enableSelfPreservation=false
#清理间隔(单位:毫秒,默认是60*1000)
eureka.server.eviction.interval-timer-in-ms=60000

4 如何优雅停服

  1. 不需要在 Eureka Server 中配置关闭自我保护

  2. 需要在服务中添加 actuator.jar 包

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
  3. 修改配置文件

    #启用 shutdown 
    endpoints.shutdown.enabled=true 
    #禁用密码验证 
    endpoints.shutdown.sensitive=false 
    #2.0版本以上
    management.endpoints.web.exposure.include: "*"
    
  4. 使用httpclient或者curl发送请求
    http://ip:port/acyuator/shutdown
    (curl:使用windows的cmd或者linux的终端:curl -X POST http://ip:port/shutdown)

五. 加强Eureka注册中心的安全认证

1. 在 Eureka Server 中添加 security 包

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>

2.修改 Eureka Server 配置文件( 添加账户密码 )

spring.application.name=Eureka-Server

server.port=8080

spring.security.basic.enabled=true
spring.security.user.name=admin
spring.security.user.password=admin

eureka.client.serviceUrl.defaultZone=http://admin:admin@eureka1:8761/eureka/,http://admin:admin@eureka2:8761/eureka/

3.添加配置类 关闭csrf防护(2.0以上)

@Configuration
public class Config extends WebSecurityConfigurerAdapter{

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.csrf().disable();
		super.configure(http);
	}
}

4. 修改微服务的配置文件添加访问注册中心的用户名与密码
用户名 admin
密码 admin

eureka:
  client:
    serviceUrl:
      defaultZone: http://admin:admin@192.168.40.128:8080/eureka,http://admin:admin@192.168.40.129:8080/eureka

传送门时间到~~~
SpringCloud高级阶段上 传送门
第一章 负载均衡Ribbon
第二章 声明式调用Feign
第三章 服务容错保护Hystrix

SpringCloud高级阶段中 传送门
第四章 如何设计微服务
第五章 服务网关Zuul
第六章 分布式配置中心

SpringCloud高级阶段下 传送门
第七章 消息总线Bus
第八章 消息驱动Stream
第九章 分布式服务跟踪Sleuth

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐