SpringCloud Netflix

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包下
演进
那么随着架构的演进我们逐渐把功能拆分出来,代码并没有什么变化
但是一旦并发量高起来,这样的架构,我们的机器无法让业务正常实行了
现在
解决方法应运而生,微服务架构
把功能拆分多个设备上去,来解决性能上导致业务无法正常运行的问题
微服务四大核心问题:
- 服务很多,用户如何访问,注册中心
- 服务中如何通信 : rpc
- 这么多服务,服务治理?
- 服务挂了怎么办?
对于这些问题,spring cloud是个生态
用来解决这四个问题的
-
Spring cloud netfix 一站式解决方案
api网关,zuul组件
Feign --HttpClinet ----Http通信方式
服务注册发现:Eureka
熔断机制: Hystrix
。。。。
-
Apache Dubbo zookeeper 半自动,需要整合别人的
API 没有,找第三方组件,或者自己实现
Dubbo 通信,高性能 rpc框架
服务注册发现:zookeeper
熔断机制并没有:借助 Hystrix
-
Spring Cloud Alibaba 最新的一站式解决方案!
新概念:服务网格!server mesh
概念都是一样的:
- API
- http rpc
- 注册与发现
- 熔断机制
为什么会出现这个情况,因为:网络不可靠!
微服务
- 就目前而言微服务,业界没有统一标准的定义
- 微服务架构是一种架构模式,或者是一种架构风格他提倡单一应用程序划分一组小服务,每个服务运行在自己独立的进程内,服务之间,互相协调,互相配置,为用户提倡最终的价值,体现最终价值,服务之间采用轻量级的通信机制互相沟通,每个服务围绕具体的业务构建,并且能够被独立的部署在生产环境中,另外,尽量避免统一的,集中式管理的服务管理机制,对具体的一个服务而言,根据上下文,选择合适的语言,工具,对齐构建,可以有一个非常轻量的集中式管理来协调业务,可以使用不同的语言编写,也可以用不同的数据储存
我们从技术维度理解一下
就是微服务的作用就是将传统的一个项目解决业务(一站式应用)根据业务拆分成一个一个的服务,去彻底的去耦合,每个微服务提供单个业务的功能服务,一个服务做一个事情,从技术角度来说就是一个小而独立的处理过程,类的进程的概念,能够自行单独启动或销毁,拥有自己独立的数据库
微服务与微服务架构
微服务
强调的是服务的大小 ,他关注的是一个点,是具体解决某一个问题提供落地对服务的应用,就是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/springCloud | Motan | grpc | thrift | Dubbo/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 | 支持(客户端) | 否 | 否 | 支持(客户端) |
典型应用案例 | Netflix | sina | |||
社区活跃度 | 高 | 一般 | 高 | 一般 | 2017才开始重新维护,之前中断了五年 |
学习难度 | 中 | 低 | 高 | 高 | 低 |
文档丰富程度 | 高 | 一般 | 一般 | 一般 | 高 |
其他 | SpringCloud bus为我们的应用带来更多的管理端点 | 支持降级 | netflix内部在开发集成grpc | idl定义 | 实践的公司比较多 |
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的大版本说明
springboot | springcloud | 关系 |
---|---|---|
1.2.x | 天使版angel | 兼容boot1.2.x |
1.3.x | brixton版本 | 兼容spring1.3,也兼容1.4 |
1.4.x | camden版本 | 兼容spring1.4,也兼容1.5 |
1.5.x | dalston版本 | 兼容spring1.5,不兼容2.0.x |
1.5.x | edgware | 兼容spring1.5,不兼容2.0 |
2.0.x | finchley | 兼容spring2.0,不兼容1.5 |
2.1.x | greenwich |
到了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的服务端新老版本也有变化
之后就是配置了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的注册中心啦
服务的注册
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
配置解读:
register-with-eureka:true
表示自己会被注册到服务中心fetchRegistry:true
表示自己不是服务中心,需要检索服务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
表示自己的客户端,
之后就是测试了
- 先启动注册中心
- 之后启动需要注册的注册中心
- 访问注册中心即可
微服务名称配置
Eureka的自我保护机制
当Eureka一定时间内没有检测到服务的心跳或者短时间内丢失了多个服务,那么服务端就会认为是网络故障或者是一系列i意外的发生
此时不应该注销任何的服务。同时新的服务也可以继续进来,
设计哲学
好死不如赖活着
Eureka宁可保护错误的服务信息,也不会去轻易的注销服务
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KVWJjlqP-1648908821910)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/%20img%20/image-20210819233339779.png)]
Eureka集群原理
Eureka集群原理说明:
搭建注册中心集群
参考我们的注册中心7001,搭建一个一模一样的注册中心
在搭建之前请去修改一下映射文件,
找到C:\Windows\System32\drivers\etc路径下的hosts文件,修改
因为我们之前使用都是一个单机的项目,我们要集群那么不能使用 localhost,他会被识别成一样的路径
我们修改一些映射
如:
- 127.0.0.1 eureka7001.com
- 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 #集群版
测试:
- 先要启动EurekaServer,7001/7002服务
- 再要启动服务提供者provider,8001服务
- 再要启动消费者,80
- http://localhost/consumer/payment/get/1
查看结果即可
支付服务提供者8001集群环境构建
同上搭建一个除了端口号和8001一抹一样的模块
cloud-provider-payment8002
Ribbon 负载均衡
现在的RIbbon已经进入维护模式了
现有他的代替解决方案是
LB(负载均衡)
负载均衡分类
集中式LB
即在服务的消费方和提供方之间使用独立的LB设施,(可以是硬件,如f5,也可以是软件,如nginx),由该设施负责把访问请求通过某种请求策略转发至服务的提供方;
进程内LB
将L B逻辑集成在消费方,消费方从服务注册中心获取那些地址可用,让后自己再从这些地址选择出一个合适的服务器.
Ribbon 就是属于进程内LB,他只是一个类库,集成于消费房的进程,消费房通过他来获取到服务器的提供方地址
Ribbon能够做些什么
就是在消费者中对服务提供者进行负载均衡操作,
默认是轮询的方式,
1 2 3 再来一遍 1 2 3
负载均衡+RestTemplate调用架构说明
总结:
Ribbon其实就是一个软负载均衡的客户端组件,他可以和其他所需请求的客户端结合使用,和eureka结合只是其中的一个实例。
我们之前导入的 Netflix-Eureka的jar包中包含了 ribbon 新版本不包含,
现在的最新版本 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
getForObject方法/getForEntity方法
postForObject/postForEntity
开启负载均衡
默认的开启负载均衡操作就一个注解
在你的消费者config中的restTemoplate上添加一个注解
@Configuration
public class orderConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
这样就开启的Ribbon的默认轮询的负载均衡
IRule:根据特定算法从服务列表中选取一个要访问的服务,用哪一种方式做负载均衡
我们首先要强调一下配置细节
如果我们想改变默认的负载均衡算法我们需要在 这样建立一个新包
主启动类在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负载均衡算法(这里由于算法实在不是咱的擅长就跳过了手写算法那个一节课)
轮询算法原理
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有什么区别嘛,
区别如下
也就是说,我们要使用openFeign需要使用
接口+注解的方式来开发:微服务调用接口+@FeignClient
使用之前强调一点,也是官方文档说的,fegin 在消费端使用
前置知识强调完之后我们开始编码吧!
老规矩,新建一个模块: 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();
}
}
测试
- 先启动2个eureka集群7001/7002
- 再启动2个微服务8001/8002
- 启动OpenFeign启动
- http://localhost/consumer/payment/get/1
- Feign自带负载均衡配置项
这里是调用的服务名称的对应和服务调用的地址
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,默认超过一秒钟就会超时
怎么配置呢 ? :我们在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
之后查看日志内容
Hystrix断路器
我们在使用分布式系统的时候总会面临着一个问题
数十个的依赖关系,有时候会不可避免的出错,
而多个接口调用一个服务有一个挂了,就会导致整个调用的接口无法使用
我们称这个为:服务雪崩
服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”.
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。
所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。
有需求那就有人出手解决于是乎:Hystrix出现了
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
"断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
讲了这么多他能解决什么呢?
- 服务降级
- 服务熔断
- 接近实时的监控
官网连接:https://github.com/Netflix/Hystrix/wiki/How-To-Use
然而这么好的东西 : Hystrix官宣,停更进维
为了后面新的学习,我们需要理解思想,主要学习思想
Hystrix重要概念
服务降级fallback
服务器忙,请稍候再试,不让客户端等待并立刻返回一个友好提示,fallback方法
要是让客户端看到这个
哪些情况会触发降级:
- 程序运行异常
- 超时
- 服务熔断触发服务降级
- 线程池/信号量打满也会导致服务降级
不太好
我们可以在服务器出问题的时候编写兜底方法,如果服务出问题,有一个兜底,调用友好提示
服务熔断机制
类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示
服务的降级->进而熔断->恢复调用链路
最核心的是可以重启服务
他就是类似一个机制,如果服务器出现触发机制的问题,停止服务,调用服务降级,尝试恢复服务
服务限流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服务
这个时候我们再次访问的时候哪怕是第一个方法也会出现转圈加载的情况
为什么会被卡死?
因为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访问有两种情况
- 要么转圈圈等待
- 要么超时
为什么会出现上述情况呢?
- 8001同一层次的其他接口服务被困死,因为tomcat线程里面的工作线程已经被挤占完毕
- 80此时调用8001,客户端访问响应缓慢,转圈圈
正因为有上述故障或不佳表现,才有我们的降级/容错/限流等技术诞生
如何解决?解决的要求
这个时候我们的问题有了解决方案
- 超时导致服务器变慢(转圈)--------->超时不再等待,超过一定时间调用fallback方法
- 出错(宕机或程序运行出错)---------->出错要有兜底有方法兜底
解决:
- 对方服务(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 = "")
接下来就是80Controller的改变
业务逻辑混乱
我们在思考一下,还能不能再优化?
本次案例服务降级处理是在客户端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)]
什么时候会触发熔断呢?
涉及到断路器的三个重要参数:快照时间窗、请求总数阀值、错误百分比阀值。
- 快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
- 请求总数阀值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。
- 错误百分比阀值:当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开。
断路器开启或者关闭的条件
- 当满足一定阀值的时候(默认10秒内超过20个请求次数)
- 当失败率达到一定的时候(默认10秒内超过50%请求失败)
- 到达以上阀值,断路器将会开启
- 当开启的时候,所有请求都不会进行转发
- 一段时间之后(默认是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进行路由,以及提供一些强大的过滤器功能,例如:熔断、限流、重试等
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通讯框架.
架构:
技术背景
有了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;
上述模式的缺点:
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,就可以实现一个具体的路由了
Gateway工作流程
核心逻辑: 路由转发+执行过滤器链
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 这个是什么东东?
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
我们来看一下常用的断言:
- 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
常用的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/
config是什么?
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
添加上这些:
本地硬盘上新建git仓库并clone
上手实战
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
读取规则
//-.yml(最推荐使用这种方式)
成功实现了用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
主启动类
@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
- 必须是Post请求
- 命令行 : curl -X POST "http://localhost:3355/actuator/refresh"
请求完之后
不用重启也可以动态的刷新配置
- http://localhost:3355/configInfo
- 成功实现了客户端3355刷新到最新配置内容
- 避免了服务的重启
还有遗留问题
这个时候新的问题又来了
假如有多个微服务客户端3355/3366/3377。。。。
每个微服务都要执行一次post请求,手动刷新?
可否广播,一次通知,处处生效?
我们想大范围的自动刷新,求方法,有需求就会有人来解决
Bus 消息总线
一言以蔽之
- 分布式自动刷新配置功能
- Spring Cloud Bus配合Spring Cloud Config使用可以实现配置的动态刷新
Bus是什么?
他能干什么?
为什么称之为总线?
什么是总线:
在微服务架构的系统中,通常会使用轻量级的消息代理来构建一个共用的消息主题,并让系统中所有微服务实例都连接上来。由于该主题中产生的消息会被所有实例监听和消费,所以称它为消息总线。在总线上的各个实例,都可以方便地广播一些需要让其他连接在该主题上的实例都知道的消息。
基本原理:
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)]
安装RabbitMQ,下载地址 :https://dl.bintray.com/rabbitmq/all/rabbitmq-server/3.7.14/rabbitmq-server-3.7.14.exe
进入RabbitMQ安装目录下的sbin目录
D:\rabbitmq_server-3.7.14\sbin
打开命令行 : rabbitmq-plugins enable rabbitmq_management
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3MXua8zG-1648908821938)(https://cdn.jsdelivr.net/gh/Doomwatcher2004/my-image-host@master/%20img%20/image-20210911013147089.png)]
执行完就可以看到
访问地址查看是否安装成功 : http://localhost:15672/
SpringCloud Bus动态刷新全局广播
必须先具备良好的RabbitMQ环境先
演示广播效果,增加复杂度,再以3355为模板再制作一个3366
设计思想设计思想
-
利用消息总线触发一个客户端/bus/refresh,而刷新所有客户端的配置
-
利用消息总线触发一个服务端ConfigServer的/bus/refresh端点,而刷新所有客户端的配置(更加推荐)
-
图二的架构显然更加合适,图一不适合的原因如下
- 打破了微服务的职责单一性,因为微服务本身是业务模块,它本不应该承担配置刷新职责
- 破坏了微服务各节点的对等性
- 有一定的局限性。例如,微服务在迁移时,它的网络地址常常会发生变化,此时如果想要做到自动刷新,那就会增加更多的修改
给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"
一次发送,处处生效
此时查看配置中心
http://config-3344.com/config-dev.yml
查看客户端
http://localhost:3355/configInfo
http://localhost:3366/configInfo
获取配置信息,发现都已经刷新了 , 一次修改,广播通知,处处生效
SpringCloud Bus动态刷新置指定通知
不想全部通知,只想定点通知
- 只通知3355
- 不通知3366
指定具体某一个实例生效而不是全部
公式:http://localhost:配置中心的端口号/actuator/bus-refresh/
/bus/refresh请求不再发送到具体的服务实例上,而是发给config server并通过destination参数类指定需要更新配置的服务或实例
发送完之后会发现
只有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
- 生产者/消费者之间靠消息媒介传递信息内容 : 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通道,使得应用程序不需要再考虑各种不同的消息中间件实现。
默认情况下,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标准流程套路
- Binder 很方便的连接中间件,屏蔽差异
- Channel 通道,是队列Queue的一种抽象,在消息通讯系统中就是实现存储和转发的媒介,通过对Channel对队列进行配置
- Source和Sink 简单的可理解为参照对象是Spring Cloud Stream自身,从Stream发布消息就是输出,接受消息就是输入
编码API和常用注解
案例说明
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同时都收到了,存在重复消费问题
如何解决?
同一组的消费者是竞争关系,只有一个可以消费
原理
微服务应用放置于同一个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)]
在我们服务调用的时候经常会有
一个服务调一个服务多个微服务调用
为了方便我们查看服务之间的调用层次
我们产生的了链路追踪
搭建链路监控步骤
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/
上图看起来链路十分的复杂
下图相对简单清晰一些
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
查看层级 查看依赖关系
到这里 springcloud netflix 的学习就结束啦 !!! 完结撒花