Skip to main content

springcloud2020

整理知识

串一下自己之前的东西

这个阶段该如何学

在学习新知识的同时不忘回顾自己以及拥有的知识

我自己的东西

  • javaSE
  • 数据库
  • 前端知识
  • Servlet
  • springboot
  • Mybatis
  • spring
  • Maven
  • Ajax
  • dubbo+zookeeper

我差了的东西

  • Http

标准的变化

我们在之前以学过了 ssm

我们的开发核心在哪里

javaEE 标准

spring javaEE 开发标准

spring几乎连接着我们开发中的一切,他就像一个中央集成一样

但是慢慢的我们发现,他真的轻量吗?

  • 随着开发量不断增多的配合内容

让他变得不再轻量,于是乎新的解决方案诞生了

javaEE 新标准

springboot javaEE 开发新的标准

	他简化了繁琐的配置,自动化的帮助我们干了很多的配置上需要重复干的事情,

给了我们一套默认的解决方案,我们可以把boot理解成一个 spring的plus版,他集成了很多的启动器,从而让springboot逐渐取代了 ssm,springboot慢慢的变成了javaEE最好的解决方案,让无数企业喜爱

不管是新标准还是旧标准,他的特点是统一的:约定大于配置

最开始

我们现在开发的程序 all in one 的形式,所有的模块全在一个 jar 或者 war 包下

演进

那么随着架构的演进我们逐渐把功能拆分出来,代码并没有什么变化

但是一旦并发量高起来,这样的架构,我们的机器无法让业务正常实行了

现在

解决方法应运而生,微服务架构

把功能拆分多个设备上去,来解决性能上导致业务无法正常运行的问题

微服务四大核心问题:

  1. 服务很多,用户如何访问,注册中心
  2. 服务中如何通信 : rpc
  3. 这么多服务,服务治理?
  4. 服务挂了怎么办?

对于这些问题,spring cloud 是个生态

用来解决这四个问题的

  1. Spring cloud netfix 一站式解决方案

    api 网关,zuul 组件

    Feign --HttpClinet ----Http 通信方式

    服务注册发现:Eureka

    熔断机制: Hystrix

    。。。。

  2. Apache Dubbo zookeeper 半自动,需要整合别人的

    API 没有,找第三方组件,或者自己实现

    Dubbo 通信,高性能 rpc 框架

    服务注册发现:zookeeper

    熔断机制并没有:借助 Hystrix

  3. Spring Cloud Alibaba 最新的一站式解决方案!

新概念:服务网格!server mesh

概念都是一样的:

  1. API
  2. http rpc
  3. 注册与发现
  4. 熔断机制

为什么会出现这个情况,因为:网络不可靠!

微服务

  • 就目前而言微服务,业界没有统一标准的定义
  • 微服务架构是一种架构模式,或者是一种架构风格他提倡单一应用程序划分一组小服务,每个服务运行在自己独立的进程内,服务之间,互相协调,互相配置,为用户提倡最终的价值,体现最终价值,服务之间采用轻量级的通信机制互相沟通,每个服务围绕具体的业务构建,并且能够被独立的部署在生产环境中,另外,尽量避免统一的,集中式管理的服务管理机制,对具体的一个服务而言,根据上下文,选择合适的语言,工具,对齐构建,可以有一个非常轻量的集中式管理来协调业务,可以使用不同的语言编写,也可以用不同的数据储存

我们从技术维度理解一下

就是微服务的作用就是将传统的一个项目解决业务(一站式应用)根据业务拆分成一个一个的服务,去彻底的去耦合,每个微服务提供单个业务的功能服务,一个服务做一个事情,从技术角度来说就是一个小而独立的处理过程,类的进程的概念,能够自行单独启动或销毁,拥有自己独立的数据库

微服务与微服务架构

微服务

强调的是服务的大小 ,他关注的是一个点,是具体解决某一个问题提供落地对服务的应用,就是 idea 中一个个微服务的工程或者 moudel

idea 工具里面使用 maven 建立的一个个独立的小 moudle,他具体是使用 springboot 开发的一个个小模块,专业的事情交给专业的模版来做,一个模块做着一件事情

强调的是一个个的个体,每个个体完成一个具体的任务或者功能!

微服务架构

一钟新的架构形式,Martin Fowler

2014 推出

  • 微服务架构是一种架构模式,或者是一种架构风格他提倡单一应用程序划分一组小服务,每个服务运行在自己独立的进程内,服务之间,互相协调,互相配置,为用户提倡最终的价值,体现最终价值,服务之间采用轻量级的通信机制互相沟通,每个服务围绕具体的业务构建,并且能够被独立的部署在生产环境中,另外,尽量避免统一的,集中式管理的服务管理机制,对具体的一个服务而言,根据上下文,选择合适的语言,工具,对齐构建,可以有一个非常轻量的集中式管理来协调业务,可以使用不同的语言编写,也可以用不同的数据储存

微服务的有缺点

优点

  • 单一职责原则
  • 每个服务足够内聚,足够小,代码容易理解,这个能聚焦一个指定的业务功能和业务需求
  • 开发简单,开发效率提高,一个服务可能就是转义的只干一件事情
  • 微服务能够被小团队单独开发,这个小团队是 2~5 的开发人员组成
  • 微服务是松耦合的,是具有功能意义的服务,无论是在开发阶段或者部署阶段都是独立的
  • 微服务能使用不同的预言开发
  • 易于是第三方集成,微服务允许容易且灵活的方式集成自动部署,通过持续集成工具,如 jenkins,hudson,bamboo
  • 微服务易于被一个开发人员理解,修改和维护,这样小团队能够更加关注自己的工作成果。无需通过合作才能体现价值
  • 微服务允许你利用融合最新技术
  • 微服务只是业务逻辑的代码,不会喝 html,css 或其他的界面混合
  • 每个微服务都有自己的储存能力,可以有自己的数据库,也可以有统一数据库

缺点:

  • 开发人员要处理分布式系统的复杂性
  • 多服务运维难度,随着服务的增加,运维压力也在增大
  • 系统部署依赖
  • 服务间通信成本
  • 数据一致性
  • 系统集成测试
  • 性能监控

微服务技术栈

微服务技术条目落地技术
服务开发SpringBoot,Spring,SpringMVC
服务配置与管理netflix 公司的 archaius 和阿里的 diamond 等
服务注册与发现eureka,consul,zookeeper
服务调用rest,rpc,grpc
服务熔断器Hystrix,Envoy 等
负载均衡RIbbon,nginx 等
服务接口调用(服务端调用服务的简化工具)Feign 等
消息队列kafka,rabbitMQ,ActiveMQ
服务配置中心管理SpringCloudconfig,chef 等
服务路由Zuul 等
服务监控zabbix,Nagios,M ertrics,Specatator
全链路追踪Zipkin,Brave,Dapper
服务部署DOCKER,openStack,kubernetes
数据操作开发包Springcloud Stream(封装与 rides,rabbit,kafka 等发送接收消息)
事件消息总线springcloud Bus

为什么我们要选择 SpringCloud 作为微服务架构呢

1、选型依据

  • 整体解决方案和框架成熟度
  • 社区热度
  • 可维护性
  • 学习曲线

2、当前各大公司微服务架构是那些

  • 阿里:dubbo+hfs
  • 京东:jsf
  • 新浪:Motan
  • 当当: bubbox
功能和服务框架Netflix/springCloudMotangrpcthriftDubbo/dubbox
功能定位完整的微服务框架rpc 框架但是整合了 zk 或者 consul 可以实现集群环境和基本的服务注册发现rpc 框架rpc 框架服务框架
支持 rest支持,ribbon 支持多种可插拔序列化选择
支持 rpc否(但是可以和 dubbo 兼容)
支持多语言支持(rest 形式)
负载均衡支持(服务端 zuul+客户端 ribbon),zuul 服务,动态路由,云端负载均衡,eureka 针对中间层服务器支持(客户端)是(客户端)
配置服务netfix archaius spring cloud config Server 集中配置是(zookeeper 提供)
服务调用链监支持,zuul,zuul 提供边缘服务,api 网关
高可用/容错支持,服务端 hystrix+ribbon支持(客户端)支持(客户端)
典型应用案例Netflixsinagooglefacebook
社区活跃度一般一般2017 才开始重新维护,之前中断了五年
学习难度
文档丰富程度一般一般一般
其他SpringCloud bus 为我们的应用带来更多的管理端点支持降级netflix 内部在开发集成 grpcidl 定义实践的公司比较多

springcloud 入门概述

springcloud 是什么?

springcloud 基于 springboot 提供了一套微服务解决方案,包括服务注册,发现,配置中心,全链路监控

服务网管,负载均衡,熔断器等组件,除了基于 netflix 的开源组件做高度抽象封装之外,还有一些选型中立得 1 开源组件。

springcloud 利用 springboot 的开发便利性,巧妙的简化了分布式系统基础设施额开发,springcloud 为开发人员提供了快速构建分布式系统的一些工具,包括配置管理,服务发现,断路器,路由,微代理,事件总线,全局锁,决策竞选,分布式会话等等,他们都可以用 springboot 的开发风格做到一键启动部署

springboot 并没有重复造轮子,他只是将目前各家公司开发的比较成熟经得起实际考研的 服务框架组合起来,通过 springboot 风格进行封装,屏蔽掉了复杂的配置,和实现原理,最终给开发者留出一套简单易懂,易部署,和易维护的分布式系统开发工具包

springcloud 是分布式微服务架构下的一站式解决方案,是各个微服务架构落地技术的集合体,俗称微服务全家桶

springboot 和 springcloud 的关系

  • springboot 专注于快速方便的开发单个个体微服务
  • springcloud 是关注全局微服务协调治框架,他将 springboot 开发的一个个单体微服务,整合并管理起来,为各个微服务之间提供了配置管理,服务发现,断路器,路由,事件总线全局锁,决策竞选,分布式会话等等集成服务
  • springboot 可以离开 springcloud 独立使用,开发项目,但是 springcloud 离不开 springboot,属于依赖关系
  • springboot 专注于快速方便的开发单个个体微服务,springcloud 关注全局的服务治理框架

Dubbo 和 springcloud 技术选型

分布式+服务治理 Dubbo

目前成熟的互联网架构:应用服务化拆分+消息中间件

Dubbo 和 springcloud 对比

dubbo 应为停更了之后,社区并不活跃,垂死状态,未来未知

springcloud 的社区十分活跃,正值壮年,

对比图:

最大区别:springcloud 抛弃了 Dubbo 的 rpc 通信,采用基于 http 的 rest 方式

严格来说,两种方式各有优劣,从一定程度上,后者牺牲了服务调用的性能,但是也比避免了原生 rpc 带来的问题,rest 相比 rpc 更加的灵活,服务提供方和调用方的依赖只靠一个七月,不存在在吗级别的强依赖,这就是强调快速烟花的微服务环境下,显得更加合适

品牌机和组装机的区别

springcloud(品牌机):

很明显的一点就是,springcloud的功能比dubbo强大的太多,覆盖面更广,而且作为spring的明星项目,他也能够和其他的spring项目完美融合。

dubbo(组装机):

使用dubbo构建微服务架构就像组装电脑,各环节,我们的选择自由度非常高,但是最终结果可能是就是一个内存条不点亮了,总是不让人那么放心,但是如果是一个高手,那这些都不是问题,

springcloud就像品牌机,在spring source的整合下,做了大量的兼容性测试,保证了机器拥有更高的稳定性,但是如果要在使用非原装组件外的东西,就需要对其基础足够了解,

社区支持和更新力度

最重要的是,dubbo 停止了 5 年狗熊,虽然 17 年重启了,对于技术发展的需求,更需要爱发展着自行拓展升级,比如 dubbox,对于这很多想要采纳微服务的中小软件组织,显然是不太合适的,中小公司没有那么强大的技术去修改 dubbo 的源码+周边的一整套解决方案。并不是每个公司都有阿里的大牛+真实线上生产环境测试过

总结

曾风靡国内的 rpc 框架 Dubbo 在重启维护后,让很多用户为之雀跃,但是同时也要有质疑的声音,发展迅速的时代,dubbo 能否跟上?dubbo 和 springcloud 的差异 ?是否会有相关的举措保证 dubbo 的后续更新频率

dubbo 是一个专注 rpc 框架,springcloud 的目标是微服务架构下的一站式解决方案

设计模式+微服务拆分思想:不一定善于表达的技术人才,你可以领导他,软实力是职场关键的一点,你可能技术没有人才好,但是你的设计思维,架构理解和表达能力让你可以成为只会技术人才的团队 leader,

springcloud 下载

springcloud 的不同版本

以伦敦地铁站和字符开头来命名

接下来是需要知道的几个文档

springcloud 的中文文档:https://www.springcloud.cc/spring-cloud-dalston.html

社区官网:http://docs.springcloud.cn/

以上的理论内容是和代码挂钩的,很多面试中淡资也是很重要的东西

上手实战咯

  • 我们使用一个支付模块做一个微服务通用案例,用 rest 风格
  • 回忆 ssm 所学的知识
  • maven 分包分模块的架构复习 在这里插入图片描述

一个父工程带着多个子模块

动手!

springcloud 的大版本说明

springbootspringcloud关系
1.2.x天使版 angel兼容 boot1.2.x
1.3.xbrixton 版本兼容 spring1.3,也兼容 1.4
1.4.xcamden 版本兼容 spring1.4,也兼容 1.5
1.5.xdalston 版本兼容 spring1.5,不兼容 2.0.x
1.5.xedgware兼容 spring1.5,不兼容 2.0
2.0.xfinchley兼容 spring2.0,不兼容 1.5
2.1.xgreenwich

到了 2020 我们发现技术一代一代的换,有的技术慢慢的停止更新维护,又会有新的更全面的解决方案跟上,时代快速发展 在这里插入图片描述

cloud 项目搭建

我们采用的是用 maven 聚合项目作为父项目 在这里插入图片描述

在里面编写子模块,主要是可以解决一个问题 在这里插入图片描述

就是子模块依赖版本的统一管理 这里呢我们就要用到 dependencyManagement+properties 来控制版本 下图就是关于 dependencyManagement 的一些知识复习 在这里插入图片描述在这里插入图片描述

父项目 pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>

<groupId>com.hyc.springcloud</groupId>
<artifactId>cloud2020</artifactId>
<version>1.0-SNAPSHOT</version>
<modules>
<module>cloud-provider-payment8001</module>
</modules>
<packaging>pom</packaging>


<!-- 统一管理jar包版本 -->
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<junit.version>4.12</junit.version>
<log4j.version>1.2.17</log4j.version>
<lombok.version>1.16.18</lombok.version>
<mysql.version>8.0.11</mysql.version>
<druid.version>1.1.16</druid.version>
<mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version>
</properties>

<!-- 子模块继承之后,提供作用:锁定版本+子modlue不用写groupId和version -->
<dependencyManagement>
<dependencies>
<!--spring boot 2.2.2-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.2.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud Hoxton.SR1-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR1</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--spring cloud alibaba 2.1.0.RELEASE-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.1.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>${mybatis.spring.boot.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
<addResources>true</addResources>
</configuration>
</plugin>
</plugins>
</build>

</project>

搞定这边之后,我们就可以去编写子模块了

支付模块,服务提供者

建 cloud-provider-payment8001 在 springboot 的学习中我们发现一个模块的构建也是有迹可循的

  • 创建 moudle
  • 编写 pom,导入依赖
  • 编写 boot 配置文件 yml
  • 主启动类
  • 编写业务类
  • 测试 一般来说都是这个几个步骤 那我们跟着来 首先是创建模块 在这里插入图片描述 之后引入需要的依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020</artifactId>
<groupId>com.atguigu.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>cloud-provider-payment8001</artifactId>

<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-jdbc -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>


</dependencies>


</project>

之后编写相关的配置文件 application.Yml

server:
port: 8001

spring:
application:
name: cloud-payment-service
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.gjt.mm.mysql.Driver
url: jdbc:mysql://localhost:3306/db2019?useUnicode=true&characterEncoding=utf-8&useSSL=false
username: root
password: 123456

mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.atguigu.springcloud.entities

编写业务类

实体类

package com.atguigu.springcloud.entities;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Payment implements Serializable {
private Long id;
private String serial;
}

Json 封装体 CommonResult

package com.atguigu.springcloud.entities;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonResult <T>{

private Integer code;
private String message;
private T data;

public CommonResult(Integer code,String message){
this(code,message,null);
}
}

mapper 与映射文件

mapper 接口

package com.hyc.cloud.mapper;

import com.hyc.cloud.pojo.payment;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@Mapper
public interface PaymentMapper {
public int create(payment payment);
public payment getPaymentByid(@Param("id") long id);
}

对应的映射文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.hyc.cloud.mapper.PaymentMapper">

<insert id="create" parameterType="com.hyc.cloud.pojo.payment" useGeneratedKeys="true" keyProperty="id">
insert into db01.paymemt (serial) values (#{name});
</insert>
<select id="getPaymentByid" resultType="com.hyc.cloud.pojo.payment" parameterType="long" resultMap="BaseResultMap">
select *
from paymemt where id = #{id};
</select>
<resultMap id="BaseResultMap" type="com.hyc.cloud.pojo.payment">
<id column="id" property="id" jdbcType="BIGINT"/>
<id column="serial" property="serial" jdbcType="VARCHAR"/>
</resultMap>
</mapper>

服务层

package com.atguigu.springcloud.service;

import com.atguigu.springcloud.entities.Payment;
import org.apache.ibatis.annotations.Param;

public interface PaymentService {

public int create(Payment payment); //写

public Payment getPaymentById(@Param("id") Long id); //读取
}


实现类

package com.atguigu.springcloud.service.impl;

import com.atguigu.springcloud.dao.PaymentDao;
import com.atguigu.springcloud.entities.Payment;
import com.atguigu.springcloud.service.PaymentService;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class PaymentServiceImpl implements PaymentService {

@Resource
private PaymentDao paymentDao;

public int create(Payment payment){
return paymentDao.create(payment);
}

public Payment getPaymentById( Long id){

return paymentDao.getPaymentById(id);

}
}



最后就是 controller

package com.hyc.cloud.controller;

import com.hyc.cloud.pojo.CommonResult;
import com.hyc.cloud.pojo.payment;
import com.hyc.cloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
@Slf4j
public class PaymentController {
@Resource
private PaymentService paymentService;

@PostMapping("payment/create")
public CommonResult create(payment payment){
int result = paymentService.create(payment);
log.info("****新增结果:"+result);
if (result>0){
return new CommonResult(200,"插入数据库成功",result);
}else {
return new CommonResult(444,"插入数据库失败",null);
}
}
@GetMapping("payment/get/{id}")
public CommonResult getPaymentByid(@PathVariable("id") long id){
payment payment = paymentService.getPaymentByid(id);
log.info("****新增结果:"+payment);
if (payment!=null){
return new CommonResult(200,"查询成功",payment);
}else {
return new CommonResult(444,"没有对应的记录,失败,查询id"+id,null);
}
}
}

编写主启动类

package com.hyc.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

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

测试即可 在这里插入图片描述

支付模块,消费者者

走过一遍流程,那我们就加快速度 新建消费者子模块 cloud-consumer-order80 修改 pom 文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>cloud2020</artifactId>
<groupId>com.hyc.springcloud</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>cloud-consumer-order80</artifactId>


<dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

</project>

配置文件消费者十分的简单,就是配置一下端口号 server: port: 80 之后复制实体类到消费者项目里 在这里插入图片描述

那么思考一个问题 我们现在不再是单一的项目而是两个项目,那么如果调动到接口呢??? 在这里插入图片描述

springboot 中有很多的 template 供我们使用 这里我们要用到的就是其中的resttemplate 他和网络编程中的 httpclient 有异曲同工之妙 这里我们需要编写一个 config 类,springboot 需要什么我们就 new 什么

@Configuration
public class orderConfig {
@Bean
public RestTemplate restTemplate(){
return new RestTemplate();
}
}

有了这个我们就可以在消费者里面调用resttemplate了 因为是消费者所以我们只需要知道怎么使用服务就可以了 这里我们编写 controller

package com.hyc.cloud.controller;

import com.hyc.cloud.pojo.CommonResult;
import com.hyc.cloud.pojo.payment;
import io.micrometer.core.instrument.Meter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;

@RestController
public class orderContorller {
@Resource
private RestTemplate restTemplate;

public final static String PAYMENT_URL = "http://localhost:8001";//服务提供者的地址(后面做负载均衡的时候会替换成application.name)
@GetMapping("/consumer/payment/create")
public CommonResult create(payment payment){
return restTemplate.postForObject(PAYMENT_URL+"/payment/create",payment,CommonResult.class);
}
@GetMapping("/consumer/payment/get/{id}")
public CommonResult getPament(@PathVariable("id") long id){
return restTemplate.getForObject(PAYMENT_URL+"/payment/get/"+ id,CommonResult.class);
}

}

到这里启动测试 在这里插入图片描述

是不是以为这样就结束了?

当然不是 这里是的插入数据是存在问题的 会出现只有自增的主键没有内容 在这里插入图片描述 这个时候我们要回到服务提供者 在新增的对象参数前加上注解@requestbody这个主机再次测试就解决了

    @PostMapping("payment/create")
public CommonResult create(@RequestBody payment payment){
int result = paymentService.create(payment);
log.info("****新增结果:"+result);
if (result>0){
return new CommonResult(200,"插入数据库成功",result);
}else {
return new CommonResult(444,"插入数据库失败",null);
}
}

在这里插入图片描述 此时的数据库也新增成功了 在这里插入图片描述 到这里呢支付模块为例 体验 demo 就结束了

Eureka 服务注册与发现

什么是服务治理

springcloud 封装了 netflix 的 Eureka 模块实现服务治理 在传统的 rpc 远程调用框架中,管理每个服务与服务之间依赖关系比较复杂,管理比较复杂所以需要有一个东西去治理他,管理服务与服务之间的依赖关系,负载均衡,容错等 实现服务发现与注册

这个是有人要问了:什么时服务的注册与发现呢??? 问得好,

答案: Eureka 采用了 CS 也就是服务器和客户端的架构模式,Eureka Server 作为服务注册中心,来管理微服务,也可以理解成用 springboot 来开发的一个个微服务,他们在 Eureka 的位置就是 Eureka client,他们用心跳来告诉服务端自己是可以用的 我们可以用 Eureka server 就可以监控各个微服务的状态,同时也有一系列的保证机制,比如心跳检测, 我们的服务提供者和消费者的例子就是这样的, 服务提供者:在启动后将把自己当前的信息,通讯地址等以别名的方式注册到注册中心也就是 Eureka server 中 消费者:用别名的的形式,去获取服务的信息和通讯地址,之后实现本地调用 RPC 调用框架的设计思想 注册中心负责管理服务之间的依赖关系(服务治理),在任何的远程 RPC 中都会有一个注册中心,通过注册中心来获取服务的信息和接口地址

在这里插入图片描述 下图是脑图中对于 Eureka 两大组件的作用和功能介绍

  • Eureka server
  • Eureka client

在这里插入图片描述

单项目 Eurekademo

老步骤:

  • 建模块
  • 添加依赖 pom
  • yml 配置文件
  • 主启动类
  • 业务编码

创建 cloud-eureka-server7001 模块

之后导入我们要用到的依赖

    <dependencies>
<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>

</dependencies>

这里 Eureka 的服务端新老版本也有变化

image-20210819225527988

之后就是配置了 yml

server:
port: 7001

eureka:
instance:
hostname: localhost #eureka服务端的实例名字
client:
register-with-eureka: false #表识不向注册中心注册自己
fetch-registry: false #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/ #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址


之后的主启动类的编写

package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer
@SpringBootApplication
public class EurekaMain7001 {
public static void main(String[] args) {
SpringApplication.run(EurekaMain7001.class,args);
}
}

@EnableEurekaServer加上个注解就是 Eureka 的服务端了,我们并不需要写什么业务员,启动之后访问

7001 端口就可以看道 Eureka 的注册中心啦

image-20210819225943551

服务的注册

cloud-provider-payment8001我们的目光回到服务提供者

Eurekashi1 C/S 架构

所以我们想要将服务提供者注册道服务中心,还需要导入一个依赖,就是 Eureka client 的依赖抱

 <!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kBh2RWke-1648908821908)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/%20img%20/image-20210819230600170.png)]

之后就是配置文件了

eureka:
client:
register-with-eureka: true
fetchRegistry: true
service-url:
defaultZone: http://localhost:7001/eureka


配置解读:

  1. register-with-eureka:true 表示自己会被注册到服务中心
  2. fetchRegistry:true表示自己不是服务中心,需要检索服务
  3. defaultZone要注册的服务中心的地址

主启动类

package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@EnableEurekaClient
@SpringBootApplication
public class PaymentMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain8001.class,args);
}
}

@EnableEurekaClient表示自己的客户端,

之后就是测试了

  1. 先启动注册中心
  2. 之后启动需要注册的注册中心
  3. 访问注册中心即可

image-20210819232956305

微服务名称配置

image-20210819233006085

Eureka 的自我保护机制

image-20210819233134638

image-20210819233036533

当 Eureka 一定时间内没有检测到服务的心跳或者短时间内丢失了多个服务,那么服务端就会认为是网络故障或者是一系列 i 意外的发生

此时不应该注销任何的服务。同时新的服务也可以继续进来,

设计哲学

好死不如赖活着

Eureka 宁可保护错误的服务信息,也不会去轻易的注销服务

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KVWJjlqP-1648908821910)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/%20img%20/image-20210819233339779.png)]

Eureka 集群原理

Eureka 集群原理说明:

image-20210819233515492

搭建注册中心集群

参考我们的注册中心 7001,搭建一个一模一样的注册中心

在搭建之前请去修改一下映射文件,

找到 C:\Windows\System32\drivers\etc 路径下的 hosts 文件,修改

image-20210819233652511

因为我们之前使用都是一个单机的项目,我们要集群那么不能使用 localhost,他会被识别成一样的路径

我们修改一些映射

如:

  1. 127.0.0.1 eureka7001.com
  2. 127.0.0.1 eureka7002.com

本质还是 localhost 但是却是两个不一样的映射

导入和 7001相同的依赖

之后分别修改

7001 配置文件

server:
port: 7001

eureka:
instance:
hostname: eureka7001.com #eureka服务端的实例名字
client:
register-with-eureka: false #表识不向注册中心注册自己
fetch-registry: false #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://eureka7002.com:7002/eureka/ #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址


7002

server:
port: 7002

eureka:
instance:
hostname: eureka7002.com #eureka服务端的实例名字
client:
register-with-eureka: false #表识不向注册中心注册自己
fetch-registry: false #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://eureka7001.com:7001/eureka/ #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址

集群其实就是一句话:相互守望,

之后我们还要修改一下 服务提供者 8001 的注册地址,他现在同时需要注册到两个注册中心去

service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版

测试:

  1. 先要启动 EurekaServer,7001/7002 服务
  2. 再要启动服务提供者 provider,8001 服务
  3. 再要启动消费者,80
  4. http://localhost/consumer/payment/get/1

查看结果即可

支付服务提供者 8001 集群环境构建

同上搭建一个除了端口号和 8001 一抹一样的模块

cloud-provider-payment8002

Ribbon 负载均衡

image-20210831155425465

现在的 RIbbon 已经进入维护模式了

image-20210831155523817

现有他的代替解决方案是

image-20210831155539759

LB(负载均衡)

负载均衡分类

集中式 LB

即在服务的消费方和提供方之间使用独立的 LB 设施,(可以是硬件,如 f5,也可以是软件,如 nginx),由该设施负责把访问请求通过某种请求策略转发至服务的提供方;

进程内 LB

将 L B 逻辑集成在消费方,消费方从服务注册中心获取那些地址可用,让后自己再从这些地址选择出一个合适的服务器.

Ribbon 就是属于进程内 LB,他只是一个类库,集成于消费房的进程,消费房通过他来获取到服务器的提供方地址

Ribbon 能够做些什么

就是在消费者中对服务提供者进行负载均衡操作,

默认是轮询的方式,

1 2 3 再来一遍 1 2 3

负载均衡+RestTemplate 调用架构说明

image-20210831160415484

总结:

	Ribbon其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。

我们之前导入的 Netflix-Eureka 的 jar 包中包含了 ribbon 新版本不包含,

image-20210831160604160

现在的最新版本 h12 Ribbon 以被上图提到的 lb 给代替了(之前全部最新版的时候踩到的坑)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s1Y5cCOF-1648908821913)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/%20img%20/image-20210831160552808.png)]

再次复习 RestTemplate 的使用

官网连接:https://docs.spring.io/spring-framework/docs/5.2.2.RELEASE/javadoc-api/org/springframework/web/client/RestTemplate.html

image-20210831160653268

getForObject 方法/getForEntity 方法

image-20210831160739588

postForObject/postForEntity

image-20210831160804564

开启负载均衡

默认的开启负载均衡操作就一个注解

在你的消费者 config 中的 restTemoplate 上添加一个注解

@Configuration
public class orderConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}

这样就开启的 Ribbon 的默认轮询的负载均衡

IRule:根据特定算法从服务列表中选取一个要访问的服务,用哪一种方式做负载均衡

image-20210831160935110

我们首先要强调一下配置细节

image-20210831161010919

如果我们想改变默认的负载均衡算法我们需要在 这样建立一个新包

image-20210831161128681

主启动类在 cloud 的中,所以他会扫描 cloud 下所有的子包

接下来是自定义负载均衡的规则编写(使用起来越方便的,他的底层就越精妙)

@SpringBootApplication
@EnableEurekaClient
@RibbonClient(value = "CLOUD-PAYMENT-SERVICE",configuration = Myselgrule.class )
public class orderMain {
public static void main(String[] args) {
SpringApplication.run(orderMain.class,args);
}
}

添加了这个之后我们要使用自己设置的规则我们需要使用到注解@RibbonClient

在主启动类上添加这个注解 ,并且设置两个参数

value:需要负载均衡的服务名

configuration : 要使用的规则类 Myselgrule.class

即可

Ribbon 负载均衡算法(这里由于算法实在不是咱的擅长就跳过了手写算法那个一节课)

轮询算法原理

image-20210831161946018

openFeign

链接:https://github.com/spring-cloud/spring-cloud-openfeign

Feign 是什么?

​ Feign 是一个声明式的 web 服务客户端,让编写 web 服务客户端变得非常容易,只需创建一个接口并在接口上添加注解即可.

Feign 能干什么?

使用 Feign 可以让我们在编写 java HTTP 客户端的时候变得更加的容易

我们在之前的项目中使用的是 Ribbon + Resttemplate 的时候,利用 Resttemplate 对请求的封装处理,形成了一套模版话的调用方法,但是实际开发中,由于对服务的依赖调用可能不止一处**,往往一个接口要被多处调用,所以通常都会针对每个微服务自行封装一些客户端类来包装**.**这个依赖服务的调用.**所以,Feign 在此基础上,做了进一步的封装,由他来帮助我们定义和实现依赖服务接口的定义.在 Feign 的实现下,**我们只需要创建一个接口并使用注解的方式来配置他(以前是 dao 接口上面标注 mapper 注解,现在是一个微服务接口上面标注一个 Feign 注解即可),**即可完成对服务提供方的接口绑定,简化了使用 spring cloud ribbon 时候,自动封装服务调用客户端的开发量

同时 Feign 集成了 Ribbon

利用 Ribbon 维护了 Payment 的服务列表信息,并且通过轮询实现了客户端的负载均衡.而与 Ribbon 不同的是.通过 feign 只需要定义服务绑定的接口且以声明式的方法,简单的实现了服务的调用

这个时候有人要提问了

我们学习 openFeign 和 Feign 有什么区别嘛,

区别如下

image-20210905145616103

也就是说,我们要使用 openFeign 需要使用

接口+注解的方式来开发:微服务调用接口+@FeignClient

使用之前强调一点,也是官方文档说的,fegin 在消费端使用

image-20210905145806447

前置知识强调完之后我们开始编码吧!

老规矩,新建一个模块: cloud-consumer-feign-order80

导入相关的依赖

  <!--openfeign-->
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

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

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>

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

底下的依赖是平常的使用的,主要是第一个依赖

        <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

之后编写配置文件:编写端口和 Eureka 的相关配置

server:
port: 80
eureka:
client:
register-with-eureka: false
service-url:
defaultZone: http://eureka7001.com:7001/eureka, http://eureka7002.com:7002/eureka

我们可以学习的时候就发现,我们每次接触到新的组件,主启动类就会多一个新的注解@EnableFeignClients

@SpringBootApplication
@EnableFeignClients
public class feignOrder80Main {
public static void main(String[] args) {
SpringApplication.run(feignOrder80Main.class,args);
}
}

之后就编写业务类就可以了

业务逻辑接口+@FeignClient 配置调用 provider 服务

新建服务层接口

@Component
@FeignClient(value = "CLOUD-PAYMENT-SERVICE")
public interface PaymentFeignService {
@GetMapping("/payment/get/{id}")
public CommonResult getPaymentByid(@PathVariable("id") long id);
@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout();
}


@FeignClient(value = "CLOUD-PAYMENT-SERVICE")这里是我们需要使用 fegin 来调用的服务提供者的,服务名称

编写控制层之后测试

@RestController
public class openFeginController {
@Resource
private PaymentFeignService paymentFeignService;


@GetMapping("/consumer/payment/get/{id}")
public CommonResult getPaymentByid(@PathVariable("id") long id){
System.out.println(id);
return paymentFeignService.getPaymentByid(id);
}
@GetMapping(value = "/consumer/payment/feign/timeout")
public String paymentFeignTimeout(){
return paymentFeignService.paymentFeignTimeout();
}
}

测试

这里是调用的服务名称的对应和服务调用的地址

image-20210905153344028

OpenFeign 超时控制

场景演示

超时设置,故意设置超时演示出错情况

服务提供方 8001 故意写暂停程序

@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout(){
try { TimeUnit.SECONDS.sleep(3); }catch (Exception e) {e.printStackTrace();}
return serverPort;
}

服务消费方 80 添加超时方法 PaymentFeignService

@GetMapping(value = "/payment/feign/timeout")
public String paymentFeignTimeout();

服务消费方 80 添加超时方法 OrderFeignController

@GetMapping(value = "/consumer/payment/feign/timeout")
public String paymentFeignTimeout(){
return paymentFeignService.paymentFeignTimeout();
}

测试方法

访问延时方法:http://localhost/consumer/payment/feign/timeout

错误页面:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7cjtDIAK-1648908821916)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/%20img%20/image-20210905153649722.png)]

OpenFeign 默认等待一秒钟,超过后报错,因为集成了 ribbon,默认超过一秒钟就会超时

image-20210905153751258

怎么配置呢 ? :我们在 yml 中配置 ribbon 的超时即可

ribbon:
ReadTimeout: 5000
ConnectTimeout: 5000

OpenFeign 日志打印功能

feign 支持日志打印功能

我们可以通过日志来获取到 feign 的 http 请求中的细节

对使用了 feign 的接口调用情况监控和输出

opnefeign 的日志级别

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Jm7bL43A-1648908821917)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/%20img%20/image-20210905154015536.png)]

要使用 opnefeign 的日志

我们需要去配置 config

package com.atguigu.springcloud.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FeignConfig {

@Bean
Logger.Level feignLoggerLevel(){
return Logger.Level.FULL;
}
}

之后在 yml 配置日志的级别

logging:
level:
com.hyc.springcloud.service.PaymentFeignService: debug

之后查看日志内容

image-20210905154433224

Hystrix 断路器

我们在使用分布式系统的时候总会面临着一个问题

数十个的依赖关系,有时候会不可避免的出错,

而多个接口调用一个服务有一个挂了,就会导致整个调用的接口无法使用

我们称这个为:服务雪崩

image-20210905154720000

服务雪崩

​ 多个微服务之间调用的时候,假设微服务 A 调用微服务 B 和微服务 C,微服务 B 和微服务 C 又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务 A 的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”. 对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。 ​ 所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。

有需求那就有人出手解决于是乎:Hystrix 出现了

​ Hystrix 是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix 能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。 ​ "断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

讲了这么多他能解决什么呢?

  • 服务降级
  • 服务熔断
  • 接近实时的监控

官网连接:https://github.com/Netflix/Hystrix/wiki/How-To-Use

然而这么好的东西 : Hystrix 官宣,停更进维

image-20210905155233076

为了后面新的学习,我们需要理解思想,主要学习思想

Hystrix 重要概念

服务降级 fallback

服务器忙,请稍候再试,不让客户端等待并立刻返回一个友好提示,fallback 方法

要是让客户端看到这个

哪些情况会触发降级:

  1. 程序运行异常
  2. 超时
  3. 服务熔断触发服务降级
  4. 线程池/信号量打满也会导致服务降级

image-20210905153649722

不太好

我们可以在服务器出问题的时候编写兜底方法,如果服务出问题,有一个兜底,调用友好提示

服务熔断机制

类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示

服务的降级->进而熔断->恢复调用链路

最核心的是可以重启服务

他就是类似一个机制,如果服务器出现触发机制的问题,停止服务,调用服务降级,尝试恢复服务

服务限流 flowlimit(这里我暂时没有实践,等到 Alibaba 的版本的时候在使用)

秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟 N 个,有序进行

服务降级: 实例模块编写

新建项目cloud-provider-hystrix-payment8001

导入核心依赖

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


<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>


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

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

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>

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

yml 配置

server:
port: 8001


eureka:
client:
register-with-eureka: true #表识不向注册中心注册自己
fetch-registry: true #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
service-url:
# defaultZone: http://eureka7002.com:7002/eureka/ #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
defaultZone: http://eureka7001.com:7001/eureka/
# server:
# enable-self-preservation: false
spring:
application:
name: cloud-provider-hystrix-payment
# eviction-interval-timer-in-ms: 2000

编写主启动类

@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
}

之后编写服务(这里我们直接使用实现类)

@Service
public class PaymentService {

//成功
public String paymentInfo_OK(Integer id){
return "线程池:"+Thread.currentThread().getName()+" paymentInfo_OK,id: "+id+"\t"+"哈哈哈" ;
}

//失败
public String paymentInfo_TimeOut(Integer id){
int timeNumber = 3;
try { TimeUnit.SECONDS.sleep(timeNumber); }catch (Exception e) {e.printStackTrace();}
return "线程池:"+Thread.currentThread().getName()+" paymentInfo_TimeOut,id: "+id+"\t"+"呜呜呜"+" 耗时(秒)"+timeNumber;
}

}

编写控制层

@RestController
@Slf4j
public class PaymentController {

@Resource
private PaymentService paymentService;

@Value("${server.port}")
private String serverPort;

@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_OK(id);
log.info("*******result:"+result);
return result;
}
@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentService.paymentInfo_TimeOut(id);
log.info("*******result:"+result);
return result;
}
}

之后启动测试方法

两个方法都 OK

业务场景

带入场景

开启 Jmeter,来 20000 个并发压死 8001,20000 个请求都去访问 paymentInfo_TimeOut 服务

image-20210905175524136

这个时候我们再次访问的时候哪怕是第一个方法也会出现转圈加载的情况

为什么会被卡死?

因为 tomcat 的默认的工作线程数被打满了,没有多余的线程来分解压力和处理。

上面还是服务提供者 8001 自己测试,

假如此时外部的消费者 80 也来访问,那消费者只能干等,最终导致消费端 80 不满意,服务端 8001 直接被拖死

以上还只是服务提供者的自测,这个时候我们就需要使用 hystrix 的时候

我们一般使用在服务端,客户端也可以做两重保险

客户端

看热闹不嫌事情大,这个时候我们加入客户端,来更接近真实业务

新建模块cloud-consumer-feign-hystrix-order80

导入依赖

 <dependencies>
<!--新增hystrix-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

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

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>

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


配置 yml

server
port: 80


eureka:
client:
register-with-eureka: true #表识不向注册中心注册自己
fetch-registry: true #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
service-url:
defaultZone: http://eureka7001.com:7001/eureka/

spring:
application:
name: cloud-provider-hystrix-order

主启动类

@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8001 {
public static void main(String[] args) {
SpringApplication.run(PaymentHystrixMain8001.class,args);
}
}

package com.atguigu.springcloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients
public class OrderHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(OrderHystrixMain80.class,args);
}
}

服务接口


package com.atguigu.springcloud.service;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService {
@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id);

@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
}


controller 接口

@RestController
@Slf4j
public class OrderHystrixController {

@Resource
private PaymentHystrixService paymentHystrixService;

@Value("${server.port}")
private String serverPort;

@GetMapping("/consumer/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_OK(id);
log.info("*******result:"+result);
return result;
}
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_TimeOut(id);
log.info("*******result:"+result);
return result;
}

}

之后测试

再高并发场景下会出现什么情况

  • 2W 个线程压 8001
  • 消费端 80 微服务再去访问正常的 OK 微服务 8001 地址

此时的 80 访问有两种情况

  1. 要么转圈圈等待
  2. 要么超时

image-20210905180340825

为什么会出现上述情况呢?

  1. 8001 同一层次的其他接口服务被困死,因为 tomcat 线程里面的工作线程已经被挤占完毕
  2. 80 此时调用 8001,客户端访问响应缓慢,转圈圈

正因为有上述故障或不佳表现,才有我们的降级/容错/限流等技术诞生

如何解决?解决的要求

这个时候我们的问题有了解决方案

  1. 超时导致服务器变慢(转圈)--------->超时不再等待,超过一定时间调用 fallback 方法
  2. 出错(宕机或程序运行出错)---------->出错要有兜底有方法兜底

解决:

  • 对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级
  • 对方服务(8001)down 机了,调用者(80)不能一直卡死等待,必须有服务降级
  • 对方服务(8001)OK,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者),自己处理降级

有了方案呢我们可以去执行

8001fallback

设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处理,作服务降级 fallback

业务类启用 我们需要使用到 Hystrix 的注解

@HystrixCommand报异常后如何处理: 一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand 标注好的 fallbackMethod 调用类中的指定方法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ruDUmb2p-1648908821920)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/%20img%20/image-20210905180818599.png)]

老规矩用了新的组件

主启动类就要激活

添加新注解@EnableCircuitBreaker

此时的主启动类

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class paymentHystrix8001 {
public static void main(String[] args) {
SpringApplication.run(paymentHystrix8001.class,args);
}

}

80fallback

80 订单微服务,也可以更好的保护自己,自己也依样画葫芦进行客户端降级保护

配置 yml

feign:
hystrix:
enabled: true #如果处理自身的容错就开启。开启方式与生产端不一样。

主启动

@SpringBootApplication
@EnableFeignClients
@EnableHystrix
public class feignHystrixMain80 {
public static void main(String[] args) {
SpringApplication.run(feignHystrixMain80.class,args);
}

}

业务类

@GetMapping("/consumer/payment/hystrix/timeout/{id}")
@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500") //3秒钟以内就是正常的业务逻辑
})
public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
String result = paymentHystrixService.paymentInfo_TimeOut(id);
return result;
}

//兜底方法
public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
return "我是消费者80,对付支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,(┬_┬)";
}

代码膨胀

解决了服务兜底的问题,我们这个时候审视场景,会发现,如果每个方法都有一个兜底那是不是太膨胀了

于是乎我们需要一套公用的兜底方案,个别案例再去用一对一兜底

新注解:@DefaultProperties(defaultFallback = "")

image-20210905181325246

接下来就是 80Controller 的改变

image-20210905181613140

业务逻辑混乱

我们在思考一下,还能不能再优化?

本次案例服务降级处理是在客户端 80 实现完成的,与服务端 8001 没有关系,只需要为 Feign 客户端定义的接口添加一个服务降级处理的实现类即可实现解耦

我们只需要将统一的兜底方法抽象出来,和业务代码分离,就可以解决客户端,混乱的问题, 业务就只关心业务,兜底就专门负责兜底.

接下来

根据 cloud-consumer-feign-hystrix-order80 已经有的 PaymentHystrixService 接口,重新新建一个类(PaymentFallbackService)实现该接口,统一为接口里面的方法进行异常处理

package com.atguigu.springcloud.service;

import org.springframework.stereotype.Component;

@Component
public class PaymentFallbackService implements PaymentHystrixService {
@Override
public String paymentInfo_OK(Integer id) {
return "-----PaymentFallbackService fall back-paymentInfo_OK , (┬_┬)";
}

@Override
public String paymentInfo_TimeOut(Integer id) {
return "-----PaymentFallbackService fall back-paymentInfo_TimeOut , (┬_┬)";
}
}


使用 fegin 的好处就来了

fegin 里集成了 fegin 于是乎我们可以这样来改建我们的 80 服务接口 fallback = PaymentFallbackService.class设置参数指向我们的兜底类

@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {

@GetMapping("/payment/hystrix/ok/{id}")
public String paymentInfo_OK(@PathVariable("id") Integer id);

@GetMapping("/payment/hystrix/timeout/{id}")
public String paymentInfo_TimeOut(@PathVariable("id") Integer id);


}

测试:

  • 单个 eureka 先启动 7001
  • PaymentHystrixMain8001 启动
  • 正常访问测试
  • 故意关闭微服务 8001
  • 客户端自己调用提升: 此时服务端 provider 已经 down 了,但是我们做了服务降级处理,让客户端在服务端不可用时也会获得提示信息而不会挂起耗死服务器

小总结:

​ 到这里我们就解决了有关客户端和服务端两方面的服务降级解决方案

服务熔断 : 实力模块编写

其实,服务熔断就是一种调用机制,

他来判断什么时候触发服务熔断,达到触发雕件,就暂停服务的使用,调用服务降级,和并且在一定时间或者是其他条件的完成尝试重启服务

马丁福勒提出的理论:https://martinfowler.com/bliki/CircuitBreaker.html

修改业务类cloud-provider-hystrix-payment8001

//服务熔断
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
@HystrixProperty(name = "circuitBreaker.enabled",value = "true"), //是否开启断路器
@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"), //请求次数
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), //时间范围
@HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"), //失败率达到多少后跳闸
})
public String paymentCircuitBreaker(@PathVariable("id") Integer id){
if (id < 0){
throw new RuntimeException("*****id 不能负数");//抛出异常
}
String serialNumber = IdUtil.simpleUUID();

return Thread.currentThread().getName()+"\t"+"调用成功,流水号:"+serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
return "id 不能负数,请稍候再试,(┬_┬)/~~ id: " +id;
}

CONTROLLER 编写

//===服务熔断
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id){
String result = paymentService.paymentCircuitBreaker(id);
log.info("*******result:"+result);
return result;
}

之后我们测试

一直用参数为-1 的错误请求访问

之后访问正确的请求(参数为正数)会发现

服务暂时调用不了正确的业务代码

多次错误,然后慢慢正确,发现刚开始不满足条件,就算是正确的访问地址也不能进行访问,等待片刻之后就可以访问到了

原理(小总结)

熔断类型

熔断打开:

​ 请求不再进行调用当前服务,内部设置时钟一般为 MTTR(平均故障处理时间),当打开时长达到所设时钟则进入熔断状态

熔断关闭:

​ 熔断关闭不会对服务进行熔断,而是暂时关闭服务,等待片刻就会进入半开状态

熔断半开:

​ 部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断

官网的流程步骤:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jl4s7lNJ-1648908821921)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/%20img%20/image-20210905204203408.png)]

什么时候会触发熔断呢?

image-20210905204230488

涉及到断路器的三个重要参数:快照时间窗、请求总数阀值、错误百分比阀值。

  • 快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的 10 秒。
  • 请求总数阀值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为 20,意味着在 10 秒内,如果该 hystrix 命令的调用次数不足 20 次,即使所有的请求都超时或其他原因失败,断路器都不会打开。
  • 错误百分比阀值:当请求总数在快照时间窗内超过了阀值,比如发生了 30 次调用,如果在这 30 次调用中,有 15 次发生了超时异常,也就是超过 50%的错误百分比,在默认设定 50%阀值情况下,这时候就会将断路器打开。

断路器开启或者关闭的条件

  1. 当满足一定阀值的时候(默认 10 秒内超过 20 个请求次数)
  2. 当失败率达到一定的时候(默认 10 秒内超过 50%请求失败)
  3. 到达以上阀值,断路器将会开启
  4. 当开启的时候,所有请求都不会进行转发
  5. 一段时间之后(默认是 5 秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启。重复 4 和 5

Gateway 新一代网关

gateway 官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/

他是什么?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1vTkY1mr-1648908821921)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/%20img%20/image-20210911002632034.png)]

​ Gateway 是在 Spring 生态系统之上构建的 API 网关服务,基于 Spring 5,Spring Boot 2 和 Project Reactor 等技术。 Gateway 旨在提供一种简单而有效的方式来对 API 进行路由,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等

image-20210911002642902

​ SpringCloud Gateway 是 Spring Cloud 的一个全新项目,基于 Spring 5.0+Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供—种简单有效的统一的 API 路由管理方式。 ​ SpringCloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Zuul,在 Spring Cloud 2.0 以上版本中,没有对新版本的 Zuul .0 以上最新高性能版本进行集成,仍然还是使用的 Zuul 1.x 非 Reactor 模式的老版本。而为了提升网关的性能 ​ SpringCloud Gateway 是基于 WebFlux 框架实现的,而 WebFlux 框架底层则使用了高性能的 Reactor 模式通信框架 Netty。 ​ Spring Cloud Gateway 的目标提供统一的路由方式且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。

一句话概括就是:Spring Cloud Gateway 使用的 Webflux 中的 reactor-netty 响应式编程组件,底层使用了 Netty 通讯框架.

架构:

image-20210911002757348

技术背景

有了 Zuul 了怎么又出来了 gateway??

我们为什么选择 Gatway?:

neflix 不太靠谱,zuul2.0 一直跳票,迟迟不发布

​ 一方面因为 Zuul1.0 已经进入了维护阶段,而且 Gateway 是 SpringCloud 团队研发的,是亲儿子产品,值得信赖。而且很多功能 Zuul 都没有用起来也非常的简单便捷。 ​ Gateway 是基于异步非阻塞模型上进行开发的,性能方面不需要担心。虽然 Netflix 早就发布了最新的 Zuul 2.x,但 Spring Cloud 貌似没有整合计划。而且 Netflix 相关组件都宣布进入维护期;不知前景如何 ? 多方面综合考虑 Gateway 是很理想的网关选择。

SpringCloud Gateway 具有如下特性

  • 基于 Spring Framework 5, Project Reactor 和 Spring Boot 2.0 进行构建;动态路由:能够匹配任何请求属性;
  • 可以对路由指定 Predicate(断言)和 Filter (过滤器);集成 Hystrix 的断路器功能;
  • 集成 Spring Cloud 服务发现功能;
  • 易于编写的 Predicate(断言)和 Filter (过滤器);请求限流功能;
  • 支持路径重写。

技术对比

Zuul

Springcloud 中所集成的 Zuul 版本,采用的是 Tomcat 容器,使用的是传统的 Servlet lO 处理模型。 学过尚硅谷 web 中期课程都知道一个题目,Servlet 的生命周期?servlet 由 servlet container 进行生命周 期管理。 container 启动时构造 servlet 对象并调用 servlet init)进行初始化; container 运行时接受请求,并为每个请求分配一个线程(一般从线程池中获取空闲线程)然后调用 service()。container 关闭时调用 servlet destory0 销毁 servlet;

image-20210911003206886

上述模式的缺点:

​ servlet 是一个简单的网络 IO 模型,当请求进入 servlet container 时,servlet container 就会为其绑定一个线程,在并发不高的场景下这种模型是适用的。但是一旦高并发(此比如抽风用 jemeter),线程数量就会上涨,而线程资源代价是昂贵的(上线文切换,内存消耗大)严重影响请求的处理时间。

在一些简单业务场景下,不希望为每个 request 分配一个线程,只需要 1 个或几个线程就能应对极大并发的请求,这种业务场景下 servlet 模型没有优势

所以 Zuul 1.X 是基于 servlet 之上的一个姐塞式处理模型,即 spring 实现了处理所有 request 请求的一个 servlet (DispatcherServiet)并由该 servletse 塞式处理处理。所以 Springcloud Zuul 无法摆脱 servlet 模型的弊端

gateway(webflux + netty)

​ 传统的 Web 框架,比如说: struts2,springmvc 等都是基于 Servlet API 与 Servlet 容器基础之上运行的。 但是 ​ 在 Servlet3.1 之后有了异步非阻塞的支持。而 WebFlux 是一个典型非阻塞异步的框架,它的核心是基于 Reactor 的相关 API 实现的。相对于传统的 web 框架来说,它可以运行在诸如 Netty, Undertow 及支持 Servlet3.1 的容器上。非阻塞式+函数式编程(Spring5 必须让你使用 java8)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VA68uSuE-1648908821923)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/%20img%20/image-20210911003521718.png)]

​ Spring WebFlux 是 Spring 5.0 引入的新的响应式框架,区别于 Spring MVC,它不需要依赖 Servlet API,它是完全异步非阻塞的,并且基于 Reactor 来实现响应式流规范。

Gateway 三大核心概念

Route(路由)

​ 路由是构建网关的基本模块,它由 ID,目标 URI,一系列的断言和过滤器组成,如果断言为 true 则匹配该路由

Predicate(断言):

​ 参考的是 java8 的 java.util.function.Predicate 开发人员可以匹配 HTTP 请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由

Filter(过滤)

​ 指的是 Spring 框架中 GatewayFilter 的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改。

总体:

  • web 请求,通过一些匹配条件,定位到真正的服务节点。并在这个转发过程的前后,进行一些精细化控制。
  • predicate 就是我们的匹配条件;
  • flter,就可以理解为一个无所不能的拦截器。有了这两个元素,再加上目标 uri,就可以实现一个具体的路由了

image-20210911003737963

Gateway 工作流程

image-20210911003852702

核心逻辑: 路由转发+执行过滤器链

demo 实战

新建模块: cloud-gateway-gateway9527

导入依赖

  <dependencies>
<!--新增gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</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-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>



</dependencies>


配置对应的 yml

server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由

- id: payment_routh2
uri: http://localhost:8001
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由

eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka

编写主启动类:

@SpringBootApplication
@EnableEurekaClient
public class GateWayMain9527 {
public static void main(String[] args) {
SpringApplication.run( GateWayMain9527.class,args);
}
}

构建好基本的网关模块之后,我们需要思考

9527 网关如何做路由映射那???

我们打开服务提供者:cloud-provider-payment8001看看controller的访问地址

我们不想暴露 8001 服务端口,希望在 8001 外面套一层 9527

于是乎

配置网管 9527 的配置文件

server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
uri: http://localhost:8001 #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由

- id: payment_routh2
uri: http://localhost:8001
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由


eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka



这个时候我们可以来测试一下

添加网关前:http://localhost:8001/payment/get/1,我们访问会暴露出端口

添加网关后:http://localhost:9527/payment/get/1,我们访问网管,他会去找到配置文件对路由的匹配路由地址,之后断言按照规则匹配路由

通过微服务名实现动态路由

​ 简单看到了网管效果之后,继续看看问题,我们现在的配置十分的膨胀,url 是匹配死的,我们需要的是动态的,

​ 默认情况下 Gateway 会根据注册中心的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能

这个时候我们启动 : 一个 eureka7001+两个服务提供者 8001/8002

此时我们要去继续修改 9527 的配置:

server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/get/** #断言,路径相匹配的进行路由

- id: payment_routh2
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service
predicates:
- Path=/payment/lb/** #断言,路径相匹配的进行路由

eureka:
instance:
hostname: cloud-gateway-service
client:
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka

PS :

  • 需要注意的是 uri 的协议为 lb,表示启用 Gateway 的负载均衡功能。
  • lb://serviceName 是 spring cloud gateway 在微服务中自动为我们创建的负载均衡 uri

之后再次测试

http://localhost:9527/payment/lb

就可以发现采用了轮询的方式做负载均衡,在 8001/8002 两个端口切换

Predicate 的使用断言的使用

断言是什么?

启动我们的 gatewat9527

我们会看到他在启动的时候

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uTo0vBkk-1648908821924)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/%20img%20/image-20210911005125073.png)]

Route Predicate Factory 这个是什么东东?

image-20210911005203095

Spring Cloud Gateway 将路由匹配作为 Spring WebFlux HandlerMapping 基础架构的一部分。

  • Spring Cloud Gateway 包括许多内置的 Route Predicate 工厂。所有这些 Predicate 都与 HTTP 请求的不同属性匹配。多个 RoutePredicate 工厂可以进行组合
  • Spring Cloud Gateway 创建 Route 对象时,使用 RoutePredicateFactory 创建 Predicate 对象,Predicate 对象可以赋值给 Route。Spring Cloud Gateway 包含许多内置的 Route Predicate Factories。
  • 所有这些谓词都匹配 HTTP 请求的不同属性。多种谓词工厂可以组合

常用的 Route Predicate

image-20210911005340872

我们来看一下常用的断言:

- Path=/payment/lb/** #断言,路径相匹配的进行路由
#- After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai]
#- Cookie=username,zhangshuai #并且Cookie是username=zhangshuai才能访问
#- Header=X-Request-Id, \d+ #请求头中要有X-Request-Id属性并且值为整数的正则表达式
#- Host=**.atguigu.com
#- Method=GET
#- Query=username, \d+ #要有参数名称并且是正整数才能路由

说白了,Predicate 就是为了实现一组匹配规则,让请求过来找到对应的 Route 进行处理

Filter 的使用

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G306Lawo-1648908821924)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/%20img%20/image-20210911005755967.png)]

路由过滤器可用于修改进入的 HTTP 请求和返回的 HTTP 响应,路由过滤器只能指定路由进行使用。 Spring Cloud Gateway 内置了多种路由过滤器,他们都由 GatewayFilter 的工厂类来产生

Spring Cloud Gateway 的 Filter

image-20210911005905238

常用的 GatewayFilter:AddRequestParameter

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OI1A2z64-1648908821925)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/%20img%20/image-20210911005929316.png)]

自定义全局 GlobalFilter

两个主要接口介绍:

  • GlobalFilter : 全局过滤器
  • Ordered : 执行顺序

能干嘛?

  • 全局日志记录
  • 统一网关鉴权
  • 等等等....

过滤器代码

@Component
@Slf4j
public class MyLogGateWayFilter implements GlobalFilter,Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

log.info("*********come in MyLogGateWayFilter: "+new Date());
String uname = exchange.getRequest().getQueryParams().getFirst("username");
if(StringUtils.isEmpty(username)){
log.info("*****用户名为Null 非法用户,(┬_┬)");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);//给人家一个回应
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}

@Override
public int getOrder() {
return 0;
}
}

使用了这个之后

我们的请求如果是没有带有 uname 这个参数就会被过滤,可以用来作为一些必要参数的筛选和鉴权

config 分布式配置中心

概述:分布式系统面临的配置问题?

​ 微服务意味着要将单体应用中的业务拆分成一个个子服务,每个服务的粒度相对较小,因此系统中会出现大量的服务。由于每个服务都需要必要的配置信息才能运行,所以一套集中式的、动态的配置管理设施是必不可少的。 SpringCloud 提供了 ConfigServer 来解决这个问题,我们每一个微服务自己带着一个 application.yml,上百个配置文件的管理.…

官网 : https://cloud.spring.io/spring-cloud-static/spring-cloud-config/2.2.1.RELEASE/reference/html/

image-20210911010739344

config 是什么?

image-20210911010444164

​ SpringCloud Config 为微服务架构中的微服务提供集中化的外部配置支持,配置服务器为各个不同微服务应用的所有环境提供了一个中心化的外部配置。

怎么做?

SpringCloud Config 分为服务端和客户端两部分:

  • ​ 服务端也称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并为客户端提供获取配置信息,加密/解密信息等访问接口

  • ​ 客户端则是通过指定的配置中心来管理应用资源,以及与业务相关的配置内容,并在启动的时候从配置中心获取和加载配置信息配置服务器默认采用 git 来存储配置信息,这样就有助于对环境配置进行版本管理,并且可以通过 git 客户端工具来方便的管理和访问配置内容

我们用它可以做什么?

  • 集中管理配置文件
  • 不同环境不同配置,动态化的配置更新,分环境部署比如 dev/test/prod/beta/release
  • 运行期间动态调整配置,不再需要在每个服务部署的机器上编写配置文件,服务会向配置中心统一拉取配置自己的信息
  • 当配置发生变动时,服务不需要重启即可感知到配置的变化并应用新的配置
  • 将配置信息以 REST 接口的形式暴露 : post、curl 访问刷新均可....

前置条件

与 Github 整合配置

由于 SpringCloud Config 默认使用 Git 来存储配置文件(也有其它方式,比如支持 svn 和本地文件,但最推荐的还是 Git,而且使用的是 http/https 访问的形式)

用你自己的账号在 Github 上新建一个名为 sprincloud-config 的新 Repository

添加上这些:

image-20210911010932750

本地硬盘上新建 git 仓库并 clone

image-20210911010942167

上手实战

Config 服务端配置与测试

新建 Module 模块cloud-config-center-3344它既为 Cloud 的配置中心模块 cloudConfig Center

导入依赖

<dependencies>


<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-config-server</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

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

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>

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

相关配置

server:
port: 3344
spring:
application:
name: cloud-config-center
cloud:
config:
server:
git:
uri: 填写你自己的github路径
search-paths:
- springcloud-config
label: master
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka

主启动

@SpringBootApplication
@EnableConfigServer
public class ConfigCenterMain3344 {
public static void main(String[] args) {
SpringApplication.run(ConfigCenterMain3344 .class,args);
}
}

测试通过 Config 微服务是否可以从 Github 上获取配置内容

启动微服务 3344 : http://config-3344.com:3344/master/config-dev.yml

读取规则

/{label}/{application}-{profile}.yml(最推荐使用这种方式)

image-20210911011316792

image-20210911011337425

成功实现了用 SpringCloud Config 通过 GitHub 获取配置信息

Config 客户端配置与测试

新模块 cloud-config-client-3355

依赖

  <dependencies>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

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

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>

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

bootstrap.yml 为什么要这个样写

applicaiton.ym1 是用户级的资源配置项 bootstrap.ym1 是系统级的,优先级更加高 I Spring Cloud 会创建一个“Bootstrap Context”,作为 Spring 应用的Application Context的父上下文。初始化的时候,BootstrapContext'负责从外部源加载配置属性并解析配置。这两个上下文共享一个从外部获取的Environment'。 'Bootstrap')属性有高优先级,默认情况下,它们不会被本地配置覆盖。Bootstrap context和Application Context有着不同的约定,所以新增了一个'bootstrap.yml'文件,保证Bootstrap Context 和Application Context'配置的分离。 要将 Client 模块下的 application.yml 文件改为 bootstrap.yml,这是很关键的, 因为 bootstrap.yml 是比 application.yml 先加载的。bootstrap.yml 优先级高于 application.yml

server:
port: 3355

spring:
application:
name: config-client
cloud:
config:
label: master
name: config
profile: dev
uri: http://localhost:3344
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka

image-20210911011620049

主启动类

@SpringBootApplication
@EnableEurekaClient
public class ConfigClientMain3355 {
public static void main(String[] args) {
SpringApplication.run( ConfigClientMain3355.class,args);
}
}

业务类

@RestController
public class ConfigClientController {

@Value("${config.info}")
private String configInfo;

@GetMapping("/configInfo")
public String getConfigInfo(){
return configInfo;
}
}

启动 Config 配置中心 3344 微服务并自测

http://config-3344.com:3344/master/config-dev.yml

就可以读取到 git 仓库中的配置文件信息

启动 3355 作为 Client 准备访问

http://localhost:3355/configInfo

访问到配置中心的配置信息

成功实现了客户端 3355 访问 SpringCloud Config3344 通过 GitHub 获取配置信息

问题随时而来,分布式配置的动态刷新

场景:Linux 运维修改 GitHub 上的配置文件内容做调整

  • 刷新 3344,发现 ConfigServer 配置中心立刻响应
  • 刷新 3355,发现 ConfigServer 客户端没有任何响应
  • 3355 没有变化除非自己重启或者重新加载

难道每次运维修改配置文件,客户端都需要重启??噩梦 !!!!!!!

Config 客户端之动态刷新

避免每次更新配置都要重启客户端微服务 3355

POM 引入 actuator 监控

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


修改配置暴露全部的信息

server:
port: 3355

spring:
application:
name: config-client
cloud:
config:
label: master
name: config
profile: dev
uri: http://localhost:3344
eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka

management:
endpoints:
web:
exposure:
include: "*"

@RefreshScope业务类 Controller 修改

@RefreshScope
@RestController
public class ConfigClientController {
@Value("${config.info}")
private String configInfo;

@GetMapping("/configInfo")
public String getConfigInfo(){
return configInfo;
}
}

配置好之后需要需要运维人员发送 Post 请求刷新 3355

请求完之后

不用重启也可以动态的刷新配置

还有遗留问题

这个时候新的问题又来了

假如有多个微服务客户端 3355/3366/3377。。。。

每个微服务都要执行一次 post 请求,手动刷新?

可否广播,一次通知,处处生效?

我们想大范围的自动刷新,求方法,有需求就会有人来解决

Bus 消息总线

一言以蔽之

  • 分布式自动刷新配置功能
  • Spring Cloud Bus 配合 Spring Cloud Config 使用可以实现配置的动态刷新

Bus 是什么?

image-20210911012507764

他能干什么?

image-20210911012529080

为什么称之为总线?

什么是总线: 在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称它为消息总线。在总线上的各个实例,都可以方便地广播一些需要让其他连接在该主题上的实例都知道的消息。 基本原理: ConfigClient 实例都监听 MQ 中同一个 topic(默认是 springCloudBus)。当一个服务刷新数据的时候,它会把这个信息放入到 Topic 中,这样其它监听同一 Topic 的服务就能得到通知,然后去更新自身的配置。

RabbitMQ 环境配置

安装 Erlang,下载地址:http://erlang.org/download/otp_win64_21.3.exe

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yAMb74mp-1648908821935)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/%20img%20/image-20210911012803187.png)]

image-20210911012807670

image-20210911012813221

安装 RabbitMQ,下载地址 :https://dl.bintray.com/rabbitmq/all/rabbitmq-server/3.7.14/rabbitmq-server-3.7.14.exe

image-20210911012745023

进入 RabbitMQ 安装目录下的 sbin 目录

D:\rabbitmq_server-3.7.14\sbin

image-20210911012845944

打开命令行 : rabbitmq-plugins enable rabbitmq_management

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3MXua8zG-1648908821938)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/%20img%20/image-20210911013147089.png)]

执行完就可以看到

image-20210911013217828

访问地址查看是否安装成功 : http://localhost:15672/

SpringCloud Bus 动态刷新全局广播

必须先具备良好的 RabbitMQ 环境先

演示广播效果,增加复杂度,再以 3355 为模板再制作一个 3366

设计思想设计思想

  • 利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置

    image-20210911013435038

  • 利用消息总线触发一个服务端 ConfigServer 的/bus/refresh 端点,而刷新所有客户端的配置(更加推荐)

    image-20210911013532421

  • 图二的架构显然更加合适,图一不适合的原因如下

    • 打破了微服务的职责单一性,因为微服务本身是业务模块,它本不应该承担配置刷新职责
    • 破坏了微服务各节点的对等性
    • 有一定的局限性。例如,微服务在迁移时,它的网络地址常常会发生变化,此时如果想要做到自动刷新,那就会增加更多的修改

给 cloud-config-center-3344 配置中心服务端添加消息总线支持

pom

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

更新配置文件

server:
port: 3344
spring:
application:
name: cloud-config-center
cloud:
config:
server:
git:
uri: https://github.com/hhf19906/springcloud-config.git #git@github.com:hhf19906/springcloud-config.git
search-paths:
- springcloud-config
label: master

rabbitmq:
host: localhost
port: 5672
username: guest
password: guest

eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka

management:
endpoints:
web:
exposure:
include: "bus-refresh"

给 cloud-config-center-3355 客户端添加消息总线支持

pom

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bus-amqp</artifactId>
</dependency>

配置

server:
port: 3355

spring:
application:
name: config-client
cloud:
config:
label: master
name: config
profile: dev
uri: http://localhost:3344

rabbitmq:
host: localhost
port: 5672
username: guest
password: guest

eureka:
client:
service-url:
defaultZone: http://eureka7001.com:7001/eureka
management:
endpoints:
web:
exposure:
include: "*"

3366 和 3355 照猫画虎

测试

修改 Github 上配置文件增加版本号

发送 Post 请求

curl -X POST "http://localhost:3344/actuator/bus-refresh"

image-20210911013853924

一次发送,处处生效

此时查看配置中心

http://config-3344.com/config-dev.yml

查看客户端

http://localhost:3355/configInfo

http://localhost:3366/configInfo

获取配置信息,发现都已经刷新了 , 一次修改,广播通知,处处生效

SpringCloud Bus 动态刷新置指定通知

不想全部通知,只想定点通知

  • 只通知 3355
  • 不通知 3366

指定具体某一个实例生效而不是全部

公式:http:/localhost:配置中心的端口号/actuator/bus-refresh/{destination}

/bus/refresh 请求不再发送到具体的服务实例上,而是发给 config server 并通过 destination 参数类指定需要更新配置的服务或实例

image-20210911014033662

发送完之后会发现

只有 3355 更新了

3366 没更新

全局通知流程图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tCMdlQYB-1648908821942)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/%20img%20/image-20210911014053498.png)]

Stream 消息驱动

消息驱动概述

什么是 SpringCloudStream : 官方定义 Spring Cloud Stream 是一个构建消息驱动微服务的框架。

  • 应用程序通过 inputs 或者 outputs 来与 Spring Cloud Stream 中 binder 对象交互。
  • 通过我们配置来 binding(绑定),而 Spring Cloud Stream 的 binder 对象负责与消息中间件交互。所以,我们只需要搞清楚如何与 Spring Cloud Stream 交互就可以方便使用消息驱动的方式。
  • 通过使用 Spring Integration 来连接消息代理中间件以实现消息事件驱动。
  • Spring Cloud Stream 为一些供应商的消息中间件产品提供了个性化的自动化配置实现,
    • 引用了发布-订阅、消费组、分区的三个核心概念。 目前仅支持 RabbitMQ、Kafka。

屏蔽底层消息中间件的差异,降低切换版本,统一消息的编程模型

官网 : https://spring.io/projects/spring-cloud-stream#overview

中文指导手册 : https://m.wang1314.com/doc/webapp/topic/20971999.html

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xuCmGUaT-1648908821942)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/%20img%20/image-20210911014511919.png)]

设计思想

标准的 mq

image-20210911014551406

  • 生产者/消费者之间靠消息媒介传递信息内容 : Message
  • 消息必须走特定的通道 消息通道 MessageChannel
  • 消息通道里的消息如何被消费呢,谁负责收发处理 :消息通道 MessageChannel 的子接口 SubscribableChannel,由 MessageHandler 消息处理器订阅

为什么用 Cloud Stream

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SOsiX8sO-1648908821943)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/%20img%20/image-20210911014655210.png)]

这些中间件的差异性导致我们实际项目开发给我们造成了一定的困扰,我们如果用了两个消息队列的其中一种,后面的业务需求,我想往另外一种消息队列进行迁移,这时候无疑就是一个灾难性的,一大堆东西都要重新推倒重新做,因为它跟我们的系统耦合了,这时候 springcloud Stream 给我们提供了一种解耦合的方式。

stream 凭什么可以统一底层差异?

在没有绑定器这个概念的情况下,我们的 SpringBoot 应用要直接与消息中间件进行信息交互的时候,由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性

  • 通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离。
  • 通过向应用程序暴露统一的 Channel 通道,使得应用程序不需要再考虑各种不同的消息中间件实现。

image-20210911015050453

默认情况下,RabbitMQ 绑定器实现将每个目标映射到 TopicExchange。对于每个消费者群体。

Binder 绑定器

​ 在没有绑定器这个概念的情况下,我们的 SpringBoot 应用要直接与消息中间件进行信息交互的时候,由于各消息中间件构建的初衷不同,它们的实现细节上会有较大的差异性.通过定义绑定器作为中间层,完美地实现了应用程序与消息中间件细节之间的隔离。

​ Stream 对消息中间件的进一步封装,可以做到代码层面对中间件的无感知,甚至于动态的切换中间件(rabbitmq 切换为 kafka),使得微服务开发的高度解耦,服务可以关注更多自己的业务流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ytNoZzAl-1648908821944)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/%20img%20/image-20210911015213198.png)]

通过定义绑定器 Binder 作为中间层,实现了应用程序与消息中间件细节之间的隔离。

  • INPUT 对应于消费者
  • OUTPUT 对应于生产者

Stream 中的消息通信方式遵循了发布-订阅模式

Topic 主题进行广播

  • 在 RabbitMQ 就是 Exchange
  • 在 kafka 中就是 Topic

Spring Cloud Stream 标准流程套路

image-20210911015401977

  • Binder 很方便的连接中间件,屏蔽差异
  • Channel 通道,是队列 Queue 的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过对 Channel 对队列进行配置
  • Source 和 Sink 简单的可理解为参照对象是 Spring Cloud Stream 自身,从 Stream 发布消息就是输出,接受消息就是输入

编码 API 和常用注解

image-20210911015452441

案例说明

RabbitMQ 环境已经 OK

工程中新建三个子模块

  • cloud-stream-rabbitmq-provider8801,作为生产者进行发消息模块
  • cloud-stream-rabbitmq-consumer8802,作为消息接收模块
  • cloud-stream-rabbitmq-consumer8803,作为消息接收模块

消息驱动之生产者

cloud-stream-rabbitmq-provider8801

  <dependencies>


<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>


<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>


<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>



</dependencies>


配置文件

server:
port: 8801

spring:
application:
name: cloud-stream-provider
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: # 服务的整合处理
output: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置

eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://localhost:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
instance-id: send-8801.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为IP地址

主启动类 StreamMQMain8801

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

业务类

发送消息接口

public interface IMessageProvider
{
public String send();
}


发送消息接口实现类

@EnableBinding(Source.class) //定义消息的推送管道
public class MessageProviderImpl implements IMessageProvider
{
@Resource
private MessageChannel output; // 消息发送管道

@Override
public String send()
{
String serial = UUID.randomUUID().toString();
output.send(MessageBuilder.withPayload(serial).build());
System.out.println("*****serial: "+serial);
return null;
}
}

Controller

@RestController
public class SendMessageController
{
@Resource
private IMessageProvider messageProvider;

@GetMapping(value = "/sendMessage")
public String sendMessage()
{
return messageProvider.send();
}

}

测试 访问: http://localhost:8801/sendMessage

看到控制带输出的流水号和端口号

消息驱动之消费者

cloud-stream-rabbitmq-consumer8802

    <dependencies>


<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.cloud/spring-cloud-starter-eureka-server -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>


<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>


<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

</dependencies>


配置文件

server:
port: 8802

spring:
application:
name: cloud-stream-consumer
cloud:
stream:
binders: # 在此处配置要绑定的rabbitmq的服务信息;
defaultRabbit: # 表示定义的名称,用于于binding整合
type: rabbit # 消息组件类型
environment: # 设置rabbitmq的相关的环境配置
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
bindings: # 服务的整合处理
input: # 这个名字是一个通道的名称
destination: studyExchange # 表示要使用的Exchange名称定义
content-type: application/json # 设置消息类型,本次为json,文本则设置“text/plain”
binder: defaultRabbit # 设置要绑定的消息服务的具体设置

eureka:
client: # 客户端进行Eureka注册的配置
service-url:
defaultZone: http://localhost:7001/eureka
instance:
lease-renewal-interval-in-seconds: 2 # 设置心跳的时间间隔(默认是30秒)
lease-expiration-duration-in-seconds: 5 # 如果现在超过了5秒的间隔(默认是90秒)
instance-id: receive-8802.com # 在信息列表时显示主机名称
prefer-ip-address: true # 访问的路径变为IP地址

主启动类 StreamMQMain8802

@SpringBootApplication
public class StreamMQMain8802 {

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

}

业务类

@Component
@EnableBinding(Sink.class)
public class ReceiveMessageListenerController {
@Value("${server.port}")
private String serverPort;

@StreamListener(Sink.INPUT)
public void input(Message<String> message) {
System.out.println("消费者1号,接受:"+message.getPayload()+"\t port:"+serverPort);
}

}

测试 8801 发送 8802 接收消息

http://localhost:8801/sendMessage

这个时候 8802 会就显示收到的消息

依照 8802,clone 出来一份运行 8803

运行后两个问题

有重复消费问题

目前是 8802/8803 同时都收到了,存在重复消费问题

如何解决?

image-20210911020152504

同一组的消费者是竞争关系,只有一个可以消费

原理

微服务应用放置于同一个 group 中,就能够保证消息只会被其中一个应用消费一次。不同的组是可以消费的,同一个组内会发生竞争关系,只有其中一个可以消费。

8802/8803 实现了轮询分组,每次只有一个消费者 8801 模块的发的消息只能被 8802 或 8803 其中一个接收到,这样避免了重复消费

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3fpi7N3K-1648908821945)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/%20img%20/image-20210911020259779.png)]

消息持久化问题

通过上述,解决了重复消费问题,再看看持久化

  • 8803 的分组 group:atguiguA 没有去掉
  • 8803 的分组 group:atguiguA 没有去掉

8801 先发送 4 条信息到 rabbitmq

  • 先启动 8802,无分组属性配置,后台没有打出来消息
  • 先启动 8803,有分组属性配置,后台打出来了 MQ 上的消息

总结:有分组的消费者,在启动后可以读取分组的信息

Sleuth 分布式请求链路追踪

概述

为什么会出现这个技术?需要解决哪些问题? 官网:https://github.com/spring-cloud/spring-cloud-sleuth

  • Spring Cloud Sleuth 提供了一套完整的服务跟踪的解决方案
  • 在分布式系统中提供追踪解决方案并且兼容支持了 zipkin

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2WRKDbHA-1648908821946)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/%20img%20/image-20210911020733858.png)]

在我们服务调用的时候经常会有

image-20210911020849669

一个服务调一个服务多个微服务调用

为了方便我们查看服务之间的调用层次

我们产生的了链路追踪

搭建链路监控步骤

1.zipkin

SpringCloud 从 F 版起已不需要自己构建 Zipkin server 了,只需要调用 jar 包即可

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gx7k62J2-1648908821946)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/%20img%20/image-20210911021126436.png)]

运行 jar

查看 http://localhost:9411/zipkin/

image-20210911021200920

上图看起来链路十分的复杂

下图相对简单清晰一些

image-20210911021217716

2.服务提供者

继续找到我们最初的服务提供者cloud-provider-payment8001

依赖

<!--包含了sleuth+zipkin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>

配置

server:
port: 8001

spring:
application:
name: cloud-payment-service
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
probability: 1
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: org.gjt.mm.mysql.Driver
url:
username: root
password:

mybatis:
mapperLocations: classpath:mapper/*.xml
type-aliases-package: com.atguigu.springcloud.entities

eureka:
client:
register-with-eureka: true
fetchRegistry: true
service-url:
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka #集群版
instance:
instance-id: payment8001
prefer-ip-address: true

添加一个新方法


@GetMapping("/payment/zipkin")
public String paymentZipkin()
{
return "hi ,i'am paymentzipkin server fall back,welcome to atguigu,O(∩_∩)O哈哈~";
}

3.服务消费者(调用方)

久违的服务者 cloud-consumer-order80

依赖

 <!--包含了sleuth+zipkin-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>

配置

server:
port: 80

spring:
application:
name: cloud-order-service
zipkin:
base-url: http://localhost:9411
sleuth:
sampler:
probability: 1

eureka:
client:
#表示是否将自己注册进EurekaServer默认为true。
register-with-eureka: false
#是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
fetchRegistry: true
service-url:
#单机
#defaultZone: http://localhost:7001/eureka
# 集群
defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka # 集群版

业务类 OrderController

 // ====================> zipkin+sleuth
@GetMapping("/consumer/payment/zipkin")
public String paymentZipkin()
{
String result = restTemplate.getForObject("http://localhost:8001"+"/payment/zipkin/", String.class);
return result;
}


4.依次启动 eureka7001/8001/80

80 调用 8001 几次测试下

5.打开浏览器访问:http:localhost:9411

image-20210911021543445

查看层级 查看依赖关系

image-20210911021554312

到这里 springcloud netflix 的学习就结束啦 !!! 完结撒花