Spring Cloud 微服务 详解
微服务架构:
在说明该架构之前,再次的说明互联网应用架构演进(虽然之前说明过了)
随着互联网的发展,用户群体逐渐扩大,网站的流量成倍增长,常规的单体架构已无法满足请求压力 和业务的快速迭代
架构的变化势在必行,下面我们就以拉勾网的架构演进为例,从最开始的单体架构 分析,⼀步步的到现在的微服务架构
比如早期的淘宝:LAMP(Linux、Apache、MySQL、PHP)
后面的可能比较繁琐,可以看看79章博客的简单的说明(微服务是类似于RPC基础上的升级)
单体应用架构:
在诞生之初,拉勾的用户量、数据量规模都比较小
项目所有的功能模块都放在一个工程中编码、编译、打包并且部署在一个Tomcat容器中的架构模式就是单体应用架构
这样的架构既简单实用、便于维护,成本又低,成为了那个时代的主流架构方式
优点:
高效开发:项目前期开发节奏快,团队成员少的时候能够快速迭代
架构简单:MVC架构,只需要借助IDE开发、调试即可
易于测试:只需要通过单元测试或者浏览器完成
易于部署:打包成单⼀可执行的jar或者打成war包放到容器内启动
单体架构的应用比较容易部署、测试, 在项目的初期,单体应用可以很好地运行
然而,随着需求的不断增加, 越来越多的人加入开发团队,代码库也在飞速地膨胀
慢慢地,单体应用变得越来越臃肿,可维护性、灵活性逐渐降低,维护成本越来越高,特别的是打开idea初始化时非常的慢
缺点:
可靠性差: 某个应用Bug,例如死循环、内存溢出等, 可能会导致整个应用的崩溃
复杂性高: 以一个百万行级别的单体应用为例
整个项目包含的模块多、模块的边界模糊、 依赖关系不清晰、 代码质量参差不齐、 混乱地堆砌在一起。使得整个项目非常复杂
扩展能力受限: 单体应用只能作为一个整体进行扩展,无法根据业务模块的需要进行伸缩
例如,应用中有的模块是计算密集型的,它需要强劲的CPU
有的模块则是IO密集型的,需要更大的内存,由于这些模块部署在一起(同一个服务器,所以都要考虑)
不得不在硬件的选择上做出妥协
业务量上涨之后,单体应用架构进一步丰富变化
比如应用集群部署、使用Nginx进行负载均衡、增加缓存服务器、增加文件服务器、数据库集群并做读写分离等
通过以上措施增强应对高并发的能力、应对一定的复杂业务场景,但依然属于单体应用架构
垂直应用架构:
为了避免上面提到的那些问题,开始做模块的垂直划分,做垂直划分的原则是基于拉勾现有的业务特性来做
核心目标是第⼀个是为了业务之间互不影响,第二个是在研发团队的壮大后为了提高效率,减少组件之间的依赖
优点:
系统拆分实现了流量分担,解决了一定并发问题 ,虽然单体可以做集群,但对机器来说,是不好的,且浪费一定空间
总不能为了一部分的访问慢,而加上服务器吧
可以针对不同模块进行优化
方便水平扩展,负载均衡,容错率提⾼
系统间相互独立(业务不在同一个服务器了,比如可以单独的考虑硬件了),互不影响,新的业务迭代时更加高效
缺点:
服务之间相互调用(基本是这样的),如果某个服务的端口或者ip地址发生改变,调用的系统得手动改变
搭建集群之后,实现负载均衡比较复杂,如:内网负载,在迁移机器时会影响调用方的路 由,导致线上故障
服务之间调用方式不统⼀,基于 httpclient 、 webservice ,接⼝协议不统⼀
服务监控不到位:除了依靠端⼝、进程的监控,调用的成功率、失败率、总耗时等等这些监 控指标是没有的
SOA应用架构:
在做了垂直划分以后,模块随之增多,维护的成本在也变⾼
⼀些通用的业务和模块(如相同业务部分不同的模块)重复的越来越多
为了解决上⾯提到的接口协议不统⼀、服务无法监控、服务的负载均衡,引入了阿里巴巴开源的 Dubbo
⼀款高性能、轻量级的开源Java RPC框架,可以和Spring框架无缝集成
它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现
SOA (Service-Oriented Architecture),即面向服务的架构,操作了RPC,所以以RPC为主
根据实际业务(业务的拆分,实际上我们只是拆分出生产者,即产生数据的)
把系统拆分成合适的、独立部署的模块,模块之间相互独立(通过Webservice/Dubbo等技术进行通信)
优点:分布式、松耦合、扩展灵活、可重用
缺点:服务抽取粒度较大(需要抽取很多)
服务调用方和提供方耦合度较高(接口耦合度,因为要进行调用,需要对应的接口编写和对应)
主要是接口对应,因为需要保证地址对应
微服务应用架构:
微服务架构可以说是SOA架构的一种拓展,这种架构模式下它拆分粒度更小、服务更独立
把应用拆分成为一个个微小的服务,不同的服务可以使用不同的开发语言和存储
服务之间往往通过Restful等轻量级通信,微服务架构关键在于微小、独立、轻量级通信
微服务是在 SOA 上做的升华粒度更加细致,微服务架构强调的⼀个重点是业务需要彻底的组件化和服务化
通过通信降低了服务调用方和提供方耦合度较高,而不用去写接口对应了
微服务架构和SOA架构相似又不同:
微服务架构和SOA架构很明显的一个区别就是服务拆分粒度的不同,但是对于拉勾的架构发展来说
我们所看到的SOA阶段其实服务拆分粒度相对来说已经比较细了(超前哦!)
所以上述拉勾SOA到拉勾微服务,从服务拆分上来说变化并不大,只是引入了相对完整的新一代Spring Cloud微服务技术
自然,上述我们看到的都是拉勾架构演变的阶段结果,每一个阶段其实都经历了很多变化
拉勾的服务拆分其实也是走过了从粗到细,并非绝对的一步到位
举个拉勾案例来说明SOA和微服务拆分粒度不同:
我们在SOA架构的初期,"简历投递模块"和"人才搜索模块"都有简历内容展示的需求,只不过说可能略有区别
一开始在两个模块中各维护了一套简历查询和展示的代码,后续我们将服务更细粒度拆分
拆分出简历基础服务,那么不同模块调用这个基础服务即可
因为我们的SOA可能并不是非常的细,就如登录来说,一个登录我们操作SOA时,可能会给出多个生成者
因为登录有验证,是否登录,等等操作,而微服务就是将他们再次的拆分,实际上SOA也可以
所以说,微服务与SOA相似,那么为什么不(都)使用SOA继续拆分呢
因为在SOA的情况下,既然有登录了,那么并不需要继续拆分,浪费人力成本
而微服务,因为有他的各种更加细腻的功能,所以导致,拆分为更小的服务时,可以更好的维护
虽然也浪费人力成本,但收获更大
微服务架构体现的思想及优缺点:
微服务架构设计的核心思想就是"微",拆分的粒度相对比较小
这样的话单一职责、开发的耦合度就会降低、微小的功能可以独立部署扩展、灵活性强,升级改造影响范围小
微服务架构的优点:微服务架构和微服务
微服务很小,便于特定业务功能的聚焦
微服务很小,每个微服务都可以被一个小团队单独实施(开发、测试、部署上线、运维)
团队合作一定程度解耦,便于实施敏捷开发
微服务很小,便于重用和模块之间的组装
微服务很独立,那么不同的微服务可以使用不同的语言开发(因为基本是http访问),松耦合
类似于比如Dubbo也可以操作不同的语言调用,因为好像只操作返回值
微服务架构下,我们更容易引入新技术
当然,并没有完美的架构,即通常都会有缺点,有失必有得
微服务架构的缺点:
微服务架构下,分布式复杂难以管理,当服务数量增加,管理将越加复杂
微服务架构下,分布式链路跟踪难等
而正是因为这些缺点,所以并不是必须要微服务,如并发少的,那么使用前三个架构即可解决
当然,若是太多的,则可以使用微服务
在79章Dubbo就说过,(完全分开,或者留下少数)分开后就是微服务,所以也的确分开了,但无论是不分开还是分开
都会有对应的缺点,如SOA访问优化了RPC的平均,有提示,微服务虽然使得优化更加的细腻些,但更复杂等等
微服务架构中的核心概念:
服务注册与服务发现
例如:职位搜索 ->(调用) 简历服务
服务提供者(生产者):简历服务
服务消费者:职位搜索
服务注册:服务提供者将所提供服务的信息(服务器IP地址和端口、服务访问协议等)注册/登记到注册中心
服务消费者一般也会注册
服务发现:服务消费者能够从注册中心获取到较为实时的服务列表,然后根究一定的策略选择一个服务访问
通过注册中心,消费者可以得到提供者的列表(一般包括提供者的ip地址,端口号等等信息,使得调用不会出错)
从而可以选择操作对应的提供者
通常该注册中心中该列表若有对应的服务宕机了(根据心跳)
就会操作移除列表,使得消费者访问集群或者其他的服务时基本不会使得访问失败
注意:这里的微服务可以看成是一台服务器,因为划分的更细了,当然也有可能不是,因为在一个服务器里面也是可以的
但这样就使得没有更加细了
负载均衡:
负载均衡即将请求压力分配到多个服务器(应用服务器、数据库服务器等),以此来提高服务的性能、可靠性
熔断:
熔断即断路保护,微服务架构中
如果下游服务(如下面的调用服务B和调用服务C)因访问压力过大而响应变慢(如后面多次说明的延时)或失败
上游服务为了保护系统整体可用性
可以暂时切断对下游服务的调用,这种牺牲局部,保全整体的措施就叫做熔断
假设服务C宕机了,那么一般操作服务B中的默认服务C数据或者自己的默认数据给服务A
链路追踪:
微服务架构越发流行,一个项目往往拆分成很多个服务,那么一次请求就需要涉及到很多个服务
不同的微服务可能是由不同的团队开发、可能使用不同的编程语言实现
整个项目也有可能部署在了很多服务器上(甚至百台、千台)横跨多个不同的数据中心
所谓链路追踪,就是对一次请求涉及的很多个服务链路进行日志记录、性能监控
从而可以知道优化什么地方或者访问在那个地方出问题了等等操作或者信息
API 网关:
微服务架构下,不同的微服务往往会有不同的访问地址
客户端可能需要调用多个服务的接口才能完成一个业务需求(统一的操作)
如果让客户端直接与各个微服务通信可能出现:
1:客户端需要调用不同的url地址,增加了维护调用难度
2:在一定的场景下,也存在跨域请求的问题
前后端分离就会碰到跨域问题,原本我们在后端采用Cors就能解决,现在利用网关,那么就放在网关这层做好了
3:每个微服务都需要进行单独的身份认证
那么,API网关就可以较好的统一处理上述问题,API请求调用统一接入API网关层,由网关转发请求
API网关更专注在安全、路由、流量等问题的处理上(微服务团队专注于处理业务逻辑即可),它的功能比如
1:统一接入(路由)
2:安全防护(统一鉴权,负责网关访问身份认证验证,与"访问认证中心"通信,实际认证业务逻辑交移"访问认证中心"处理)
3:黑白名单(实现通过IP地址控制禁止访问网关功能,控制访问)
4:协议适配(实现通信协议校验、适配转换的功能)
5:流量管控(限流)
6:长短链接支持
7:容错能力(负载均衡)
Spring Cloud 综述:
Spring Cloud 是什么:
Spring Cloud是一系列框架的有序集合(框架集合,一般是有序的,即框架之间的执行顺序是有序的,这是必然的)
因为一环通常套一环,虽然可能可以单独存在,但多个框架之间是有一定的联系来连接,从而出现执行顺序
就如过滤器和Servlet一样,会先操作过滤器,而没有过滤器就操作Servlet
这个Servlet就相当于单独存在,而加上过滤器就是多个了,上面只是一种解释而已
它利用Spring Boot的开发便利性巧妙地简化了分布式系统基础设施的开发
如服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等,都可以用 Spring Boot的开发风格做到一键启动和部署
Spring Cloud并没有重复制造轮子,它只是将目前各家公司开发的比较成熟、经得起实际考验的服务框架组合起来
通过Spring Boot风格进行再封装屏蔽掉了复杂的配置和实现原理
最终给开发者留出了一套简单易懂、易部署和易维护的分布式系统开发工具包,所以微服务在一定程度上也是一种分布式的解决方案,即属于分布式的一种,而微服务就是他的一个另外的侧重名称
即:
Spring Cloud是一系列框架的有序集合(Spring Cloud是一个规范)
开发服务发现注册、配置中心、消息总线、负载均衡、断路器、数据监控等
利用Spring Boot的开发便利性简化了微服务架构的开发(自动装配)
这里,我们需要注意,Spring Cloud其实是一套规范,是一套用于构建微服务架构的规范
而不是一个可以拿来即用的框架(所谓规范就是应该有哪些功能组件,然后组件之间怎么配合,共同完成什么事情)
在这个规范之下第三方的Netflix公司开发了一些组件、Spring官方开发了一些框架/组件
包括第三方的阿里巴巴开发了一套框架/组件集合Spring Cloud Alibaba,这些才是Spring Cloud规范的实现
即:
Netflix搞了一套 ,简称SCN
Spring Cloud (或者说Spring官方) 吸收了Netflix公司的产品基础之上自己也搞了几个组件
阿里巴巴在之前的基础上搞出了一堆微服务组件,如Spring Cloud Alibaba(SCA)
Spring Cloud 解决什么问题:
Spring Cloud 规范及实现意图要解决的问题其实就是微服务架构实施过程中存在的一些问题
比如微服务架构中的服务注册发现问题、网络问题(比如熔断场景)、统一认证安全授权问题、负载均衡问题、链路追踪等问题
对应的部分专业术语:
Distributed/versioned configuration (分布式/版本化配置)
Service registration and discovery (服务注册和发现)
Routing (智能路由)
Service-to-service calls (服务调用)
Load balancing (负载均衡)
Circuit Breakers (熔断器)
Global locks (全局锁)
Leadership election and cluster state ( 选举与集群状态管理)
Distributed messaging (分布式消息传递平台)
Spring Cloud 架构:
如前所述,Spring Cloud是一个微服务相关规范,这个规范意图为搭建微服务架构提供一站式服务
采用组件(框架)化机制定义一系列组件,各类组件针对性的处理微服务中的特定问题
这些组件共同来构成Spring Cloud微服务技术栈
Spring Cloud 核心组件:
Spring Cloud 生态圈中的组件,按照发展可以分为第一代 Spring Cloud组件和第二代 Spring Cloud组件
Spring Cloud 体系结构(组件协同工作机制):
Spring Cloud中的各组件协同工作,才能够支持一个完整的微服务架构,比如:
注册中心负责服务的注册与发现,很好将各服务连接起来
API网关负责转发所有外来的请求
断路器负责监控服务之间的调用情况,连续多次失败进行熔断保护
配置中心提供了统一的配置信息管理服务,可以实时的通知各个服务获取最新的配置信息
Spring Cloud 与 Dubbo 对比:
Dubbo是阿里巴巴公司开源的一个高性能优秀的服务框架,基于RPC调用,对于目前使用率较高的
对于Spring Cloud Netflix来说,它是基于HTTP的,且他的效率没有Dubbo高(并不是说http就比tcp慢,只是大多数来说tcp的形式更加快点,即他们并没有可比性),虽然dubbo效率通常高点,但是一个功能最少需要两个(项目),而微服务就单纯一个,并且也可以互相调用,这也是一个问题(不是主要的)
其余主要问题在于Dubbo体系的组件不全,不能够提供一站式解决方案,比如服务注册与发现需要借助于Zookeeper等实现
而Spring Cloud Netflix则是真正的提供了一站式服务化解决方案,且有Spring大家族背景
前些年, Dubbo使用率高于SpringCloud,但目前Spring Cloud在服务化/微服务解决方案中已经有 了非常好的发展趋势
Spring Cloud 与 Spring Boot 的关系:
Spring Cloud 只是利用了Spring Boot 的特点,让我们能够快速的实现微服务组件开发
否则不使 用Spring Boot的话,我们在使用Spring Cloud时
每一个组件的相关Jar包都需要我们自己导入配置以 及需要开发人员考虑兼容性等各种情况
所以Spring Boot是我们快速把Spring Cloud微服务技术应用起 来的一种方式
案例准备:
本部分我们按照普通方式模拟一个微服务之间的调用,后续我们将一步步使用Spring Cloud的组件对案 例进行改造
需求:
完整业务流程图:
案例数据库环境准备:
这里使用mysql 5.7.x版本
商品信息表:
CREATE DATABASE lagou CHARACTER SET utf8;
USE lagou;
CREATE TABLE products(
id INT PRIMARY KEY AUTO_INCREMENT ,
NAME VARCHAR ( 50 ) ,
price DOUBLE ,
flag VARCHAR ( 2 ) ,
goods_desc VARCHAR ( 100 ) ,
images VARCHAR ( 400 ) ,
goods_stock INT ,
goods_type VARCHAR ( 20 )
) ;
INSERT INTO products ( id, NAME, price, flag, goods_desc, images, goods_stock, goods_type)
VALUE ( 1 , 'aa' , 1.1 , 1 , 1 , 1 , 1 , 1 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
案例工程:
我们基于SpringBoot来构造工程环境,我们的工程模块关系如下所示:
父工程 lagou-parent:
在Idea中新建一个项目,命名为lagou-parent(Maven项目)
操作spring boot并不是一定要创建spring boot项目(自动加依赖),只需要有对应的依赖即可
对应的依赖:
< packaging> pom packaging>
< parent>
< groupId> org.springframework.boot groupId>
< artifactId> spring-boot-starter-parent artifactId>
< version> 2.1.6.RELEASE version>
parent>
< dependencies>
< dependency>
< groupId> org.springframework.boot groupId>
< artifactId> spring-boot-starter-web artifactId>
dependency>
< dependency>
< groupId> org.springframework.boot groupId>
< artifactId> spring-boot-starter-logging artifactId>
dependency>
< dependency>
< groupId> org.springframework.boot groupId>
< artifactId> spring-boot-starter-test artifactId>
< scope> test scope>
dependency>
< dependency>
< groupId> org.projectlombok groupId>
< artifactId> lombok artifactId>
< version> 1.18.4 version>
< scope> provided scope>
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>
< optional> true optional>
dependency>
dependencies>
< build>
< plugins>
< plugin>
< groupId> org.apache.maven.plugins groupId>
< artifactId> maven-compiler-plugin artifactId>
< configuration>
< source> 11 source>
< target> 11 target>
< encoding> utf-8 encoding>
configuration>
plugin>
< plugin>
< groupId> org.springframework.boot groupId>
< artifactId> spring-boot-maven-plugin artifactId>
< executions>
< execution>
< goals>
< goal> repackage goal>
goals>
execution>
executions>
plugin>
plugins>
build>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
公共组件微服务(lagou-parent的子模块lagou-service-common):
在公共组件微服务中引入数据库驱动及mybatis -plus:
< dependencies>
< dependency>
< groupId> com.baomidou groupId>
< artifactId> mybatis-plus-boot-starter artifactId>
< version> 3.3.2 version>
dependency>
< dependency>
< groupId> javax.persistence groupId>
< artifactId> javax.persistence-api artifactId>
< version> 2.2 version>
dependency>
< dependency>
< groupId> mysql groupId>
< artifactId> mysql-connector-java artifactId>
< scope> runtime scope>
dependency>
dependencies>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
在java资源文件下,创建com包,然后再在里面创建lagou包,然后再在里面创建common包,然后再在里面创建pojo 包
然后再在里面创建Products类,总体来说就是创建com.lagou.common.pojo.Products类
除了最后面的Products是类外,其他的都是接口或者类,为了防止过于啰嗦的介绍,后面就以这样的进行说明
我们可以使用idea自带的操作(一般是自带的,可能需要插件),点击如下:
生成表对应的类,然后自己检查修改即可,修改成如下:
package com. lagou. common. pojo ;
import lombok. Data ;
import javax. persistence. Id ;
import javax. persistence. Table ;
@Data
@Table ( name = "products" )
public class Products {
@Id
private long id;
private String name;
private double price;
private String flag;
private String goodsDesc;
private String images;
private long goodsStock;
private String goodsType;
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
商品微服务(后面我们也将lagou-service-product称为商品微服务,注意即可):
商品微服务是服务提供者,页面静态化微服务是服务的消费者
创建商品微服务lagou-service-product,继承lagou-parent
对应的依赖:
< dependencies>
< dependency>
< groupId> com.lagou groupId>
< artifactId> lagou-service-common artifactId>
< version> 1.0-SNAPSHOT version>
dependency>
dependencies>
在资源文件夹下,创建application.yml文件,内容如下:
server :
port : 9000
Spring :
application :
name : lagou- service- product
datasource :
driver-class-name : com.mysql.jdbc.Driver
url : jdbc: mysql: //localhost: 3306/lagou? useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username : root
password : 123456
创建一个启动类com.lagou.product.ProductApplication:
package com. lagou. product ;
import org. mybatis. spring. annotation. MapperScan ;
import org. springframework. boot. SpringApplication ;
import org. springframework. boot. autoconfigure. SpringBootApplication ;
@SpringBootApplication
@MapperScan ( "com.lagou.product" )
public class ProductApplication {
public static void main ( String [ ] args) {
SpringApplication . run ( ProductApplication . class , args) ;
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
mapper层开发:
在product包下,创建接口mapper.ProductMapper:
package com. lagou. product. mapper ;
import com. baomidou. mybatisplus. core. mapper. BaseMapper ;
import com. baomidou. mybatisplus. core. mapper. Mapper ;
import com. lagou. common. pojo. Products ;
public interface ProductMapper extends BaseMapper < Products > {
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
serive层开发:
在product包下,创建接口service.ProductService:
package com. lagou. product. service ;
import com. lagou. common. pojo. Products ;
public interface ProductService {
public Products queryById ( Integer id) ;
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
对应的实现类(实现类一般是在当前接口的impl包下的),名称一般是接口加上后缀"Impl",没有包或者该实现类,创建即可:
package com. lagou. product. service. impl ;
import com. lagou. common. pojo. Products ;
import com. lagou. product. mapper. ProductMapper ;
import com. lagou. product. service. ProductService ;
import org. springframework. beans. factory. annotation. Autowired ;
import org. springframework. stereotype. Service ;
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductMapper productMapper;
@Override
public Products queryById ( Integer id) {
return productMapper. selectById ( id) ;
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
controller层开发:
在product包下,创建类controller.ProductController:
package com. lagou. product. controller ;
import com. lagou. common. pojo. Products ;
import com. lagou. product. service. ProductService ;
import org. springframework. beans. factory. annotation. Autowired ;
import org. springframework. web. bind. annotation. GetMapping ;
import org. springframework. web. bind. annotation. PathVariable ;
import org. springframework. web. bind. annotation. RequestMapping ;
import org. springframework. web. bind. annotation. RestController ;
@RestController
@RequestMapping ( "/product" )
public class ProductController {
@Autowired
private ProductService productService;
@GetMapping ( "/query/{id}" )
public Products queryById ( @PathVariable Integer id) {
return productService. queryById ( id) ;
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
启动启动类,访问"localhost:9000/product/query/1",发现得到了数据,至此操作成功
页面静态化微服务(lagou-parent的子模块lagou-service-page)
后面我们也将lagou-service-page称为静态化微服务或者页面静态化微服务,注意即可:
对应的依赖:
< dependencies>
< dependency>
< groupId> com.lagou groupId>
< artifactId> lagou-service-common artifactId>
< version> 1.0-SNAPSHOT version>
dependency>
dependencies>
在资源文件夹下,创建application.yml文件,内容如下
因为spring boot自动配置的原因,需要写上对应没有默认的配置,所以这里需要写上配置
当然有默认的可以省略,比如下面的驱动:
server :
port : 9100
Spring :
application :
name : lagou- service- page
datasource :
driver-class-name : com.mysql.jdbc.Driver
url : jdbc: mysql: //localhost: 3306/lagou? useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username : root
password : 123456
创建启动类com.lagou.page.PageApplication:
package com. lagou. page ;
import org. springframework. boot. SpringApplication ;
import org. springframework. boot. autoconfigure. SpringBootApplication ;
import org. springframework. context. annotation. Bean ;
import org. springframework. web. client. RestTemplate ;
@SpringBootApplication
public class PageApplication {
public static void main ( String [ ] args) {
SpringApplication . run ( PageApplication . class , args) ;
}
@Bean
public RestTemplate restTemplate ( ) {
return new RestTemplate ( ) ;
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
在page包下,创建controller.PageController类:
package com. lagou. page. controller ;
import com. lagou. common. pojo. Products ;
import org. springframework. beans. factory. annotation. Autowired ;
import org. springframework. web. bind. annotation. GetMapping ;
import org. springframework. web. bind. annotation. PathVariable ;
import org. springframework. web. bind. annotation. RequestMapping ;
import org. springframework. web. bind. annotation. RestController ;
import org. springframework. web. client. RestTemplate ;
@RestController
@RequestMapping ( "/page" )
public class PageController {
@Autowired
private RestTemplate restTemplate;
@GetMapping ( "/getProduct/{id}" )
public Products getProduct ( @PathVariable Integer id) {
String url = "http://localhost:9000/product/query/" ;
Products forObject = restTemplate. getForObject ( url + id, Products . class ) ;
return forObject;
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
启动启动类,浏览器访问"localhost:9100/page/getProduct/1",发现得到了数据,至此操作成功
当然可以操作Postman来进行测试,56章博客里有
我们可以发现,我们访问他,然后他去访问其他服务得到信息,有点像dubbo一样的操作
案例代码问题分析:
我们在页面静态化微服务中使用RestTemplate调用商品微服务的商品状态接口时(Restful API 接口)
在微服务分布式集群环境下会存在什么问题呢?怎么解决?
存在的问题:
1:在服务消费者中,我们把url地址硬编码到代码中,不方便后期维护
2:服务提供者只有一个服务,即便服务提供者形成集群,服务消费者还需要自己实现负载均衡
3:在服务消费者中,不清楚服务提供者的状态
4:服务消费者调用服务提供者时候,如果出现故障能否及时发现不向用户抛出异常页面?
5:RestTemplate这种请求调用方式是否还有优化空间?能不能类似于Dubbo那样玩?
6:这么多的微服务统一认证如何实现?
7:配置文件每次都修改好多个很麻烦!?
上述分析出的问题,其实就是微服务架构中必然面临的一些问题:
我们通常使用下面几种情况对应来解决上面的情况,当然并不是必须对应的
1:服务管理:自动注册与发现、状态监管
2:服务负载均衡
3:熔断
4:远程过程调用
5:网关拦截、路由转发
6:统一认证
7:集中式配置管理,配置信息实时自动更新
这些问题,Spring Cloud 体系都有解决方案,后续我们会逐个学习
第一代 Spring Cloud 核心组件(Netflix,SCN):
说明:上面提到网关组件Zuul性能一般,未来将退出Spring Cloud 生态圈,所以我们直接讲解GateWay
在课程章节规划时,我们就把GateWay划分到第一代Spring Cloud 核心组件这一部分了
各组件整体结构如下:
从形式上来说,Feign一个顶三,Feign = RestTemplate + Ribbon + Hystrix
服务降级(一般在熔断后给出的操作),一般可以理解为给你一个默认值,或者其他的使得变得简单的方式来释放资源等等
Eureka服务注册中心:
常用的服务注册中心:Eureka、Nacos、Zookeeper、Consul
关于服务注册中心:
注意:服务注册中心本质上是为了解耦服务提供者和服务消费者
服务消费者 --> 服务提供者
服务消费者 --> 服务注册中心 --> 服务提供者
对于任何一个微服务,原则上都应存在或者支持多个提供者(比如商品微服务部署多个实例),这是由微服务的分布式属性决定的
更进一步,为了支持弹性扩、缩容特性,一个微服务的提供者的数量和分布往往是动态变化的,也是无法预先确定的
因此,原本在单体应用阶段常用的静态LB机制就不再适用了,需要引入额外的组件来管理微服务提供者的注册与发现
而这个组件就是服务注册中心
注册中心实现原理:
分布式微服务架构中,服务注册中心用于存储服务提供者地址信息、服务发布相关的属性信息
消费者通过主动查询和被动通知的方式获取服务提供者的地址信息,而不再需要通过硬编码方式得到提供者的地址信息
消费者只需要知道当前系统发布了那些服务,而不需要知道服务具体存在于什么位置,这就是透明化路由
具体的步骤(注册中心是看成已经启动的):
1:服务提供者启动
2:服务提供者将相关服务信息主动注册到注册中心
3:服务消费者获取服务注册信息
通常情况下,消费者也需要注册到注册中心的,但那是内部的,外部的调用一般是不会注册
实际上也要看你是否想要注册到里面去,一般情况下都会注册(虽然前面操作Zookeeper时,消费者没有注册):
pull模式:服务消费者可以主动拉取可用的服务提供者清单
push模式:服务消费者订阅服务(当服务提供者有变化时,注册中心也会主动推送更新后的服务清单给消费者)
4:服务消费者直接调用服务提供者
另外,注册中心也通常需要完成服务提供者的健康监控,当发现服务提供者失效时需要及时剔除
主流服务中心对比:
Zookeeper:
比如前面用过的Dubbo + Zookeeper
Zookeeper它是一个分布式服务框架,是Apache Hadoop 的一个子项目
它主要是用来解决分布式应用中经常遇到的一些数据管理问题
如:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等
简单来说zookeeper本质 = 存储(文件系统) + 监听通知
Zookeeper 用来做服务注册中心,主要是因为它具有节点变更通知功能
只要客户端监听相关服务节点,服务节点的所有变更,都能及时的通知到监听客户端
这样作为调用方只要使用 Zookeeper 的客户端就能实现服务节点的订阅和变更通知功能了,非常方便
另外,Zookeeper 可用性也可以,因为只要半数以上的选举节点存活,整个集群就是可用的,使得最少节点数为3
Eureka:
由Netflix开源,并被Pivatal集成到SpringCloud体系中,它是基于 RestfulAPI 风格开发的服务注册与发现组件
Consul:
Consul是由HashiCorp基于Go语言开发的支持多数据中心分布式高可用的服务发布和注册服务软件
采用Raft算法保证服务的一致性,且支持健康检查
Nacos:
Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台
简单来说 Nacos 就是 注册中心 + 配置中心的组合
帮助我们解决微服务开发必会涉及到的服务注册 与发现,服务配置,服务管理等问题
Nacos 是 Spring Cloud Alibaba 核心组件之一,负责服务注册与发现,还有配置
分布式数据库的CAP原理:
CAP定理(又称CAP原则):
指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用 性)、 Partition tolerance(分区容错性)
最多只能同时三个特性中的两个,三者不可兼得
P:分区容错性:分布式系统在遇到某节点或网络分区故障的时候
仍然能够对外提供满足一致性或可用 性的服务(一定的要满足的)
C:数据一致性: all nodes see the same data at the same time
A:高可用: Reads and writes always succeed
CAP不可能同时满足三个,要么是AP,要么是CP
那么为什么不能同时满足呢,具体的介绍80章博客里有,这里就不做说明了,可以使用ctrl+f,然后输入CAP查找即可
注意:只是不能同时而已,实际上可以分开进行
服务注册中心组件 Eureka :
服务注册中心的一般原理、对比了主流的服务注册中心方案,目光聚焦Eureka
Eureka 基础架构:
Eureka 交互流程及原理:
Eureka 包含两个组件: Eureka Server (集群)和 Eureka Client(注册者,即上面的Application Client或者Application Service)
Eureka Client是一个Java客户端,用于简化 与Eureka Server的交互
Eureka Server提供服务发现的能力,各个微服务启动时
会通过Eureka Client向Eureka Server 进行注册自己的信息(例如网络信息,如ip地址,端口号等等)
Eureka Server会存储该服务的信息,使得消费者调用时,会获取这些信息,从而操作访问,使得基本不会出现访问不了的问题
具体步骤:
1:图中us-east-1c、 us-east-1d, us-east-1e代表不同的区也就是不同的机房
2:图中每一个Eureka Server都是一个集群
3:图中Application Service作为服务提供者向Eureka Server中注册服务
Eureka Server接受到注 册事件会在集群和分区中进行数据同步
Application Client作为消费端(服务消费者)可以从Eureka Server中获取到服务注册信息,进行服务调用
4:微服务启动后,会周期性地向Eureka Server对应注册的提供者发送心跳
默认周期为30秒,若90秒还没有回应(即续约,不是下线的情况,下线就直接没有了)
会将还没有续约的给剔除,以更新自己的信息
这个90秒一般是客户端(客户端在这里对注册中心来说一般是消费者和提供者,统称为注册者)的访问
虽然注册中心也可以是客户端(也回应)
没有访问会通知注册中心,从而剔除对应的提供者
5:Eureka Server在一定时间内没有接收到某个微服务节点的心跳, Eureka Server将会注销该微 服务节点(默认90秒)
6:每个Eureka Server同时也是Eureka Client,也需要注册到所有对方里面
因为多个Eureka Server之间通过复制的方式完成服务 注册列表的同步,自然需要对应的信息(如ip地址,端口号)来完成同步操作
所以互相之间也可以称为Eureka Client
但实际上却并不是注册得到同步的信息(如ip地址,端口号)
而是一开始的列表操作的,有对应的信息了(如ip地址,端口号),后面说明两个true时就知道了
即分开操作高可用和一致性,而复制时,那一瞬间是会出现高可用不了的,不能同时存在,复制后,那么就基本只有高可用了
7:Eureka Client会缓存Eureka Server中的信息
即使所有的Eureka Server节点都宕掉,服务消 费者依然可以使用缓存中的信息找到服务提供者
一般缓存在本地,通常在访问不了Eureka Server节点时,才会使用缓存的,否则一般使用访问的
且每次的读取信息,一般都会缓存,但统称都是根据续约(回应)时间来缓存的,而不是根据读取
Eureka通过心跳检测、健康检查和客户端缓存等机制,提高系统的灵活性、可伸缩性和高可用性
高可用:基本任何时候,我们都可以访问
且可以得到正确的数据(通常一致性后的访问,因为在复制中间的这段时间,可能数据不高可用)
一致性:基本任何时候,对应的集群的信息都是同步的,由于保持一致性,那么对应的就不会读取
为了更加的解释高可用和一致性不能共存的原因,来个简单的解释:
假设有服务器a和服务器b,简称为a,b,他们是集群
如果是高可用,那么我们给a加数据,那么这时b读取数据,必然不是同步的,即这个时候是不一致
如果是一致,那么我们给a加数据,那么这时b读取必然不能给读,必须复制后才可,即这个时候不可用
那么操作之后,不就是共存了吗,实际上只是结果共存,高可用和一致性,并不是对结果来说明的,而是对操作来说明的
即整体来说,就是不会共存
因为有不共存的地方,这里是延时(服务器是可以访问的,即没有程序方向),当然挂掉或者不可连接,也是不能共存的
具体在80章博客已经说明了挂掉,不可连接与挂掉基本是一样的,所以不可连接就没有解释了
搭建单例Eureka Server服务注册中心 :
实现过程:
1:单实例Eureka Server—>访问管理界面
2:服务提供者(商品微服务注册到集群)
3:服务消费者(页面静态化微服务注册到Eureka/从Eureka Server获取服务信息)
4:完成调用
搭建Eureka Server服务lagou-cloud-eureka(是lagou-parent工程的子模块)
lagou-parent中引入Spring Cloud 依赖:
Spring Cloud 是一个综合的项目,下面有很多子项目,比如这里的eureka子项目
对应的依赖:
< dependencyManagement>
< dependencies>
< dependency>
< groupId> org.springframework.cloud groupId>
< artifactId> spring-cloud-dependencies artifactId>
< version> Greenwich.RELEASE version>
< type> pom type>
< scope> import scope>
dependency>
dependencies>
dependencyManagement>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
我们发现,他的版本是Greenwich.RELEASE,一般需要根据如下图来对应(spring的官网,以后会改变的):
即,你的Spring Boot是什么的版本,那么这个Spring Cloud 依赖版本就要对应
这是兼容性的问题,虽然可以不对应,但可能在某些方面会出现问题,如启动报错,所以最好还是对应
具体为什么,大概是里面包含的依赖冲突问题吧,只要版本对应,基本不会发生依赖冲突(会报错的冲突,因为可能因为冲突,导致版本不一致,使得依赖之间可能导致不支持,从而报错,在70章博客有具体说明)
其中可能并没有其他对应的依赖了,所以这里使用Greenwich
这时,我们需要将Spring Boot版本改成2.1.x版本,如2.1.6.RELEASE版本
否则的话,可能出现问题,如启动报错,因为兼容性的问题
注意:导入时,根据名称来的,我们可以看上面的首字母,对应D,E,F,G,H,I(i的大写,不是l的大写L),J
仔细观察,iIlL(有明显的不同)
lagou-cloud-eureka工程pom.xml中引入依赖:
< dependencies>
< dependency>
< groupId> org.springframework.cloud groupId>
< artifactId> spring-cloud-starter-netflix-eureka-server artifactId>
dependency>
dependencies>
注意:这里要在父工程的pom文件中手动引入jaxb的jar
因为Jdk9之后(像这样说明的通常包括本身,即这里包括Jdk9)默认没有加载该模块,或者不包括了
但Eureka Server却使用到了,所以需要手动导入
否则EurekaServer服务可能无法启动(通常启动不了,且使用@EnableEurekaServer注解的情况下)
所以若你的Jdk版本是Jdk9之前的,一般就不需要引入(但是之前的可能也没有,所以最好也进行引入,因为随着时间的推移,可能会有所改动,虽然基本不会,测试一下就知道了,如果仍然启动不了,再加上就可以了)
但之所以说是这里,是因为该版本没用jaxb依赖,如果是其他版本可能会自带
比如Eureka Server的3.0.0版本就自带了,那么他就可以不用写(对应于cloud使用2020.0.0版本,在97章博客里操作的就是这个)
这里需要注意:通过测试,实际上是@EnableEurekaServer注解需要使用jaxb,所以我们也认为需要导入Eureka Server
即只要不是操作该@EnableEurekaServer注解,那么就不会可能无法启动
所以我们也认为Eureka Server需要使用jaxb,而Eureka Client不需要(因为没有@EnableEurekaServer注解)
父工程lagou-parent:
< dependency>
< groupId> com.sun.xml.bind groupId>
< artifactId> jaxb-core artifactId>
< version> 2.2.11 version>
dependency> < dependency>
< groupId> javax.xml.bind groupId>
< artifactId> jaxb-api artifactId>
dependency> < dependency>
< groupId> com.sun.xml.bind groupId>
< artifactId> jaxb-impl artifactId>
< version> 2.2.11 version>
dependency>
< dependency>
< groupId> org.glassfish.jaxb groupId>
< artifactId> jaxb-runtime artifactId>
< version> 2.2.10-b140310.1920 version>
dependency> < dependency>
< groupId> javax.activation groupId>
< artifactId> activation artifactId>
< version> 1.1.1 version>
dependency>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
回到lagou-cloud-eureka工程,在资源文件下
创建application.yml文件(一般是创建配置文件的,那么说明是在resources资源文件里,而不是java资源文件里)
server :
port : 9200
spring :
application :
name : lagou- cloud- eureka
eureka :
instance :
hostname : localhost
client :
service-url :
defaultZone : http: //localhost: 9200/eureka/
register-with-eureka : false
fetch-registry : false
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
编写启动类com.lagou.eureka.EurekaApplication,声明当前服务为Eureka注册中心
package com. lagou. eureka ;
import org. springframework. boot. SpringApplication ;
import org. springframework. boot. autoconfigure. SpringBootApplication ;
import org. springframework. cloud. netflix. eureka. server. EnableEurekaServer ;
@SpringBootApplication
@EnableEurekaServer
public class EurekaApplication {
public static void main ( String [ ] args) {
SpringApplication . run ( EurekaApplication . class , args) ;
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
启动(运行)启动类,访问http://localhost:9200
如果看到如下页面(Eureka注册中心后台),则表明EurekaServer发布成功
上面的192.168.194.1代表当前实例主机地址(这里是局域网ip),即这里是本机地址
我们可以看到,与普通的Spring Boot项目相比,就是一个@EnableEurekaServer注解的添加,我们试着将该注解删除
继续启动,发现,启动不会报错,且访问,没有页面,即对应的依赖,主要是为了操作@EnableEurekaServer注解的
很明显,该注解操作了一系列的初始化操作,而出现对应的页面,即@EnableEurekaServer注解是核心
每一次的启动,页面的显示一般是不同的
我们来解释如下(给出部分显示):
我们将对应的false设置为true,再次查看,可以看到
Instances currently registered with Eureka(当前注册到这里的实例,虽然是本身注册本身)下有对应的实例了
在启动日志里面也可以看到对应的注册信息,我们可以看到对应的实例状态信息是localhost:lagou-cloud-eureka-server:9200
那么如何改变这样的格式呢,修改部分application.yml文件:
eureka :
instance :
prefer-ip-address : true
instance-id : ${ spring.cloud.client.ip- address} : ${ spring.application.name} : ${ server.port} : @project.version@
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
至此操作完毕
商品微服务和页面静态化微服务注册到Eureka:
商品微服务lagou-service-product:
添加对应的依赖:
< dependency>
< groupId> org.springframework.cloud groupId>
< artifactId> spring-cloud-starter-netflix-eureka-client artifactId>
dependency>
对应的application.yml里添加如下信息:
eureka :
client :
serviceUrl :
defaultZone : http: //localhost: 9200/eureka/
instance :
prefer-ip-address : true
instance-id : ${ spring.cloud.client.ip- address} : ${ spring.application.name} : ${ server.port} : @project.version@
我们发现,与前面的注册server一样的配置,但依赖不同,而正是依赖不同,导致对应的角色不同,使得这里是注册者
其中也指定了注册路径,自然也是注册进去的,但我们发现,并没有写对应的register-with-eurek属性和fetch-registry属性
虽然这些属性是操作注册中心的(Eureka server我称为注册中心),使得他们进行同步的主要操作,但客户端并不需要这些配置
因为可以说是默认都是true,所以我们并不需要修改
当然也是可以修改的
如使得不进行注册和获取,或者只注册,而不操作获取(大概获取的操作不可进行),或者只获取,而不注册
但注册中心之间的同步,与他们并没有关系,只与defaultZone列表有关
接下来我们修改启动类:
package com. lagou. product ;
import org. mybatis. spring. annotation. MapperScan ;
import org. springframework. boot. SpringApplication ;
import org. springframework. boot. autoconfigure. SpringBootApplication ;
import org. springframework. cloud. netflix. eureka. EnableEurekaClient ;
@SpringBootApplication
@MapperScan ( "com.lagou.product.mapper" )
@EnableDiscoveryClient
public class ProductApplication {
public static void main ( String [ ] args) {
SpringApplication . run ( ProductApplication . class , args) ;
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
页面静态化微服务lagou-service-page:
与上面一样的操作即可,就不做说明了
那么有个疑问,为什么不将对应的依赖放入父工程里呢:
这是为了具体显示(给需要的),且为了依赖的空间(少传递)
接下来我们启动对应的工程,回到页面,发现出现了对应的数据了
搭建Eureka Server 高可用集群:
在互联网应用中,服务实例很少有单个的
因为如果EurekaServer只有一个实例,该实例挂掉,正好微服务消费者本地缓存列表中的服务实例也不可用
那么这个时候整个系统都受影响
在生产环境中,我们会配置Eureka Server集群实现高可用
Eureka Server集群之中的节点通过点对点(P2P)通信的方式共享服务注册表,我们开启两台 Eureka Server 以搭建集群
由于是在个人计算机中进行测试很难模拟多主机的情况,所以Eureka配置server集群时需要执行操作hosts文件里的地址
所以需要修改个人电脑中hosts地址:
一般在C:\Windows\System32\drivers\etc路径下修改hosts文件来改变本机的域名
添加如下:
127.0 .0.1 LagouCloudEurekaServerA
127.0 .0.1 LagouCloudEurekaServerB
由于是单机,所以我们假设域名是不同的机子,虽然是同一个
将lagou-cloud-eureka复制一份为lagou-cloud-eureka-9201
记得将对应的配置文件信息由原来的lagou-cloud-eureka修改成lagou-cloud-eureka-9201
比如pom.xml里面的,application.yml里面的(这个我们不进行修改,即让对应名称相同,即应用名称相同)
虽然也可以都不用修改(pom.xml最好修改不同的id,否则可能被覆盖的是会报错的,即两个同样的类出现)
其中父类需要添加子模块标签
即在父工程里加上< module>lagou-cloud-eureka-9201 module>来导入子工程,否则不会看成项目,而是文件夹,注意即可
到那时你就知道了,除非你自己重新搞,如自己创建子工程,或者删除对应文件操作或直接创建
但删除和直接创建的是单独项目,而不是子工程
当然,为了更好的区别,最好将启动类也进行修改,加上后缀9201即可,一般会出现不同的启动名称(根据类名来区别)
若是相同的,那么就在右上角进行修改启动名称来手动区别或者全部删除再次启动(这时又会根据类名来区别)
主要看你如何操作,当然你也可以不修改
修改9200和9201的application.yml文件,让他们之间进行注册:
9200的application.yml文件:
defaultZone : http: //LagouCloudEurekaServerB: 9201/eureka/
9201的application.yml文件:
defaultZone : http: //LagouCloudEurekaServerA: 9200/eureka/
页面中的DS Replicas下面显示的是defaultZone对应注册地址的域名,即操作的注册地址的域名
如上面的LagouCloudEurekaServerB或者LagouCloudEurekaServerA,不是其他服务器注册到这里的地址
而是defaultZone的域名,无论是否存在,都会显示
所以若是上面的配置,那么只是一个,若是下面的配置,则是两个
而正是因为同步,即虽然注册到对方,但还是会复制没有的信息给对方,使得相当于操作下面的配置
注意:上面操作时,如果是集群的话,则基本不会写自己的地址,而是其他服务器的地址,若是多个地址,那么用","逗号隔开
当然,他们并不需要注册自己的,因为复制自己的数据到自己,并不需要这样,这不是多此一举吗
当然你也可以写上自己的地址,也并没有问题,即9200和9201都可以写上如下:
defaultZone : http: //LagouCloudEurekaServerA: 9200/eureka/, http: //LagouCloudEurekaServerB: 9201/eureka/
那么对应的商品微服务(lagou-service-product)和页面静态化微服务(lagou-service-page)需要修改吗,答:这里不需要
因为只要他们defaultZone的server操作的复制同步中有其他集群,就会同步给他们
那么我们只需要写上该server即可,否则同步不过去的(同步不会同步注册者,这里要注意)
同步与两个true是无关的,这里要注意,只与defaultZone有关
很明显,这里指定了9201,所以会同步到9201去,所以这里不需要
当然,自己的指不指定并无所谓,反正自己是有,也没有必要复制自己给自己
至此,我们访问http://localhost:9200/和http://localhost:9201/,发现,都有对应的商品微服务和页面静态化微服务
即的确同步了,很明显,对应的地址那里,就是同步的操作,为了验证之前的两个true是否与同步有关系
我们将对应的true修改成false,看看是否可以同步,发现,双方都有同步,即的确,对应的两个设置与同步没有关系
只是自己的注册信息没有了,这是自然的,因为他们操作客户端的
操作完毕后,我们先再次访问"localhost:9100/page/getProduct/1",先确定没有出错
但是我们看代码时,很明显是操作了商品微服务的地址,在前面我们说过,注册一般会有对应的信息
所以我们需要得到对应的注册信息,即该商品微服务的地址
所以我们修改对应的页面静态化微服务(lagou-service-page)的PageController:
package com. lagou. page. controller ;
import com. lagou. common. pojo. Products ;
import org. springframework. beans. factory. annotation. Autowired ;
import org. springframework. cloud. client. ServiceInstance ;
import org. springframework. cloud. client. discovery. DiscoveryClient ;
import org. springframework. web. bind. annotation. GetMapping ;
import org. springframework. web. bind. annotation. PathVariable ;
import org. springframework. web. bind. annotation. RequestMapping ;
import org. springframework. web. bind. annotation. RestController ;
import org. springframework. web. client. RestTemplate ;
import java. util. List ;
@RestController
@RequestMapping ( "/page" )
public class PageController {
@Autowired
private RestTemplate restTemplate;
@Autowired
private DiscoveryClient discoveryClient;
@GetMapping ( "/getProduct/{id}" )
public Products getProduct ( @PathVariable Integer id) {
List < ServiceInstance > instances = discoveryClient. getInstances ( "lagou-service-product" ) ;
ServiceInstance serviceInstance = instances. get ( 0 ) ;
String host = serviceInstance. getHost ( ) ;
System . out. println ( host) ;
int port = serviceInstance. getPort ( ) ;
System . out. println ( port) ;
String url = "http://" + host+ ":" + port+ "/product/query/" + id;
Products forObject = restTemplate. getForObject ( url, Products . class ) ;
return forObject;
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
给出对应的页面图片,看看对应的UP (1) - 192.168.194.1:lagou-service-product:9000:1.0-SNAPSHOT
其中的UP后面的数字就是参数的名称对应的集合的大小,注意即可,当然,你可以进行测试,让参数是lagou-cloud-eureka
如:
List < ServiceInstance > instancess = discoveryClient. getInstances ( "lagou-cloud-eureka" ) ;
System . out. println ( instancess. size ( ) ) ;
然后修改其中一个名称,再次测试,我经过测试,第一次是返回2,第二次是返回1,即的确是集合的大小,即是对应的数据
那么对应的get(0),是得到其中2中的那一个呢,即顺序是排列的呢
答:顺序是时刻变化的,即更新信息(30秒更新一次)时,这个顺序就会变化,所以没有固定的顺序
那么页面的顺序是怎么顺序的呢,答:根据某个计算来排列,但却是固定,而不是变化,当然这些并不需要死磕,虽然我死磕了
我们重启,继续测试,发现,也得到了数据,这就使得硬编码问题解决
以后,我们改变商品微服务地址(地址:包括主机地址,IP端口等等)时,只需要注册一下即可(名称不要改变)
而不是去代码里进行改变
接下来我们操作页面静态化微服务(lagou-service-page)的fetch-registry属性,设置为false,代表他不能去获取信息
再次启动,会发现,在get(0)这里报错,即获取报错,不能获取
当然,与其他的服务器是否获取是没有关系的,因为这里是自己的配置,而不是操作其他服务器的配置
即其他服务器设置的false(如注册中心),他若是设置true,还是可以获取,只有他是false,才不能获取
即,的确是不能操作对应的列表的,即不能获取信息,至此说明,注册(对应的false代表不注册)和获取的确是互相作用的
那么这里与dubbo的操作的区别是什么,差不多类似,都是远程获取返回信息,都是操作地址获取
只是这里写出来需要自己调用而已,而dubbo他去调用
且他们保存的信息,在启动时,就保存了,都是先注册,然后可以得到信息
主要的不同是访问注册中心信息得到信息后,访问对应提供者得到信息的协议不同,一个是tcp协议(dubbo)
这里好像是http协议,所以dubbo效率高点
但前面也说过了,只是Dubbo体系的组 件不全,操作注册时,一般需要其他的注册组件来辅助,如zookeeper,所以也是有缺点了
至此操作完毕
Eureka细节详解:
Eureka元数据详解:
Eureka的元数据有两种:标准元数据(默认的)和自定义元数据
标准元数据:主机名、 IP地址、端口号等信息,这些信息都会被发布在服务注册表中,用于服务之 间的调用,如前面的获取信息
自定义元数据:可以使用eureka.instance.metadata-map配置,符合KEY/VALUE的存储格式,这 些元数据可以在远程客户端中访问
类似于:
instance :
prefer-ip-address : true
instance-id : ${ spring.cloud.client.ip- address} : ${ spring.application.name} : ${ server.port} : @project.version@
metadata-map :
ip : 192.168.200.128
port : 10000
user : YuanJing
pwd : 123456
我们在商品微服务lagou-service-product里面加上面的新增的属性
再次回到对应的lagou-service-page里面的PageController进行打上断点,看看获取的信息是否在注册中心里面
如图所示:
在List< ServiceInstance> instances里面或者ServiceInstance serviceInstance里面找,自己查找即可),其他的可以说是标准的元数据
如果不加,那么只有对应的management.port这个属性了,这是默认的,与标准的元数据的服务器端口对应
那么如何获取自定义的元数据呢,看如下部分的PageController类:
ServiceInstance serviceInstance = instances. get ( 0 ) ;
Map < String , String > metadata = serviceInstance. getMetadata ( ) ;
Set < String > strings = metadata. keySet ( ) ;
Object [ ] objects = strings. toArray ( ) ;
Collection < String > values = metadata. values ( ) ;
Object [ ] objects1 = values. toArray ( ) ;
for ( int a = 0 ; a< strings. size ( ) ; a++ ) {
System . out. println ( objects[ a] + ":" + objects1[ a] ) ;
}
重新启动,访问后,看看打印结果即可
Eureka客户端详解:
服务提供者(也是Eureka客户端)要向EurekaServer注册服务,并完成服务续约等工作
服务注册详解(服务提供者)
1:当我们导入了eureka-client依赖坐标,配置Eureka服务注册中心地址
2:服务在启动时会向注册中心发起注册请求,携带服务元数据信息
3:Eureka注册中心会把服务的信息主要保存在Map(总列表是map,而不是单独的list,因为名称对应list)中
服务续约详解(服务提供者):
服务每隔30秒会向注册中心续约(心跳)一次(也称为报活),如果没有续约,租约在90秒后到期,然后服务会被失效
每隔30秒的续约操作我们称之为心跳检测
Eureka Client(提供者和消费者在这里都称为客户端,虽然他们在调用中,又可以称为对应的客户端和服务端):
30S续约一次,在Eureka Server更新自己的状态 (Client端进行配置)
Eureka Server:若有客户端90S还没有进行续约的(续约一般是会操作服务没有下线的,后面会说明)
则将该微服务实例从服务注册表(Map)剔除
在Client端进行配置,通知Eureka Server删除对应的服务信息,因为是在Client端进行配置的
Eureka Client: 30S拉取服务最新的注册表并缓存到本地 (Client端进行配置),一般续约成功后,再操作,即更新时进行缓存
当然,可能是分开的,即续约(回应)和拉取的各操作各的,实际上也是如此,而不是每次的回应都会续约
那么如何修改这些默认的呢,看如下:
eureka :
instance :
lease-renewal-interval-in-seconds : 30
lease-expiration-duration-in-seconds : 90
获取服务列表(服务注册表)详解(服务消费者):
每隔30秒服务会从注册中心中拉取一份服务列表,这个时间可以通过配置修改,往往不需要我们调整
eureka :
client :
registry-fetch-interval-seconds : 30
1:服务消费者启动时,从 EurekaServer服务列表获取只读备份,缓存到本地
2:每隔30秒,会重新获取并更新数据
3:每隔30秒的时间可以通过配置eureka.client.registry-fetch-interval-seconds修改
Eureka服务端详解:
服务下线:
1:当服务正常关闭操作时(如我们手动的关闭),会发送服务下线的REST请求给EurekaServer
2:服务中心接受到请求后,将该服务置为下线状态,那么一般也就会看不到了(保存信息使得操作心跳)
那么下线的也就不会更新和获取了
失效剔除:
Eureka Server会定时(间隔值是eureka.server.eviction-interval-timer-in-ms,默认60s)进行检查
如果发现实例在一定时间(此值由客户端设置的eureka.instance.lease-expiration-duration-in-seconds定义,默认值为90s)内
没有收到心跳(一般不是下线的情况,因为下线他们基本就没有了),则会注销此实例
所以说,并不是一定过了90秒就会被剔除,还需要这个60秒的检查到才会剔除
自我保护机制:
自我保护模式正是一种针对网络异常波动的安全保护措施,使用自我保护模式能使Eureka集群更加的健壮、稳定的运行
自我保护机制的工作机制是:如果在15分钟内超过85%的客户端节点都没有正常的心跳(无论是下线还是网络问题),那么
Eureka就认为客户端与注册中心出现了网络故障,Eureka Server自动进入自我保护机制
我们可以将除了9200的其他项目都停止,虽然下线了,但我们等待一会,刷新9200页面,那么就会出现保护机制起作用的提示
此时会出现以下几种情况:
1:Eureka Server不再从注册列表中移除因为长时间没收到大多数的心跳而应该过期的服务
2:Eureka Server仍然能够接受新服务的注册和查询请求,但是不会被同步到其它节点上,保证当前节点依然可用,节省网络带宽
使得不会拥挤,导致缓慢,因为需要保证自己的网络通信,直到好了为止
3:当网络稳定时,当前Eureka Server新的注册信息会被同步到其它节点中
这时前面两步,基本都会回到原来的操作,即会移除会同步(的操作)
因此Eureka Server可以很好的应对因网络故障导致部分节点失联的情况
而不会像ZK(zookeeper)那样如果有一半不可用的情况会导致整个集群不可用而变成瘫痪(半数机制)
为什么会有自我保护机制?
默认情况下,如果Eureka Server在一定时间内(默认90秒)没有接收到某个微服务实例的心跳,Eureka Server将会移除该实例
但是当网络分区故障发生时,微服务与Eureka Server之间无法正常通信或者微服务与客户端无法正常通信
而微服务本身是正常运行的,此时不应该移除这个微服务,所以引入了自我保护机制
这时服务中心页面会显示如下提示信息:
我们在单机测试的时候很容易满足心跳失败比例在 15 分钟之内低于 85%,这个时候就会触发 Eureka 的保护机制
一旦开启了保护机制(默认情况下是开启的,当你触发了保护条件,就会出现对应的作用)
则服务注册中心维护的服务实例就不是那么准确了
此时我们通过修改Eureka Server的配置文件来关闭保护机制,这样可以确保注册中心中不可用的实例被及时的剔除(不推荐)
如下,这里需要在服务端进行关闭,客户端没有该属性,虽然写上不会报错,但并不会起作用(若可以识别,也会起作用)
即只会识别可以操作的,如后面的负载均衡策略的定义,我们点击时,他并没有对应的属性,但也操作了,因为识别了
而识别,也就是读取时,正好找对应的属性,而进行得对应值的操作
yml,properties,yaml基本都是如此,都可以自定义值,虽然识别时,只操作可以操作的:
eureka :
server :
enable-self-preservation : false
建议生产环境打开自我保护机制(因为需要部署的,肯定需要开启,防止因为网络问题,但服务没有问题,而造成的移除影响)
开发环境可以不打开
打开后,只要满足了保护条件,就会提示并进行相关操作,没有满足保护条件,就会没有提示且没有相关操作
如果关闭的话,无论是否满足保护条件,都没有提示且没有相关操作
至此,注册中心操作完毕
Ribbon负载均衡:
关于负载均衡:
负载均衡一般分为服务器端负载均衡和客户端负载均衡
所谓服务器端负载均衡,比如Nginx、F5这些
请求到达服务器之后由这些负载均衡器根据一定的算法将请求路由到目标服务器处理
所谓客户端负载均衡,比如我们要说的Ribbon,服务消费者客户端会有一个服务器地址列表
调用方在请求前通过一定的负载均衡算法选择一个服务器进行访问
因为负载均衡算法的执行是在请求客户端进行而不是服务端,所以称为客户端负载均衡
Ribbon是Netflix发布的负载均衡器,Eureka一般配合Ribbon进行使用
Ribbon利用从Eureka中读取到服务信息,在调用服务提供者提供的服务时,会根据一定的算法进行负载
Ribbon高级应用:
需求:
将商品微服务9000复制一份为9001,在9000和9001编写Controller,返回服务实例端口
Page微服务中通过负载均衡策略调用lagou-service-product的controller
复制的操作与前面的复制注册中心一样的就可
但在复制之前,我们首先先操作lagou-service-productd的controller包下创建ServiceInfoController类:
package com. lagou. product. controller ;
import com. netflix. discovery. converters. Auto ;
import org. springframework. beans. factory. annotation. Autowired ;
import org. springframework. beans. factory. annotation. Value ;
import org. springframework. web. bind. annotation. GetMapping ;
import org. springframework. web. bind. annotation. RequestMapping ;
import org. springframework. web. bind. annotation. RestController ;
@RestController
@RequestMapping ( "/service" )
public class ServiceInfoController {
@Value ( "${server.port}" )
private String port;
@GetMapping ( "port" )
public String getPort ( ) {
return port;
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
这时,我们再进行复制一份即可,名称是lagou-service-product-9001
yml的名称,我们不变,虽然也可以修改,但为了得到注册信息是,在一个集合里,所以就不修改了,其他自行修改
在微服务中使用Ribbon不需要额外导入依赖坐标
因为若微服务中引入过eureka-client或者eureka-server相关依赖,会自动引入Ribbon相关依赖坐标
而由于对应的Ribbon是操作消费方的,所以我们在页面静态化服务使用该负载均衡
接下来在lagou-service-page修改对应的启动类部分来操作负载均衡 :
@Bean
@LoadBalanced
public RestTemplate restTemplate ( ) {
return new RestTemplate ( ) ;
}
到PageController类里面添加部分代码:
@GetMapping ( "loadProductServicePort" )
public String getProductServerPort ( ) {
List < ServiceInstance > instances = discoveryClient. getInstances ( "lagou-service-product" ) ;
ServiceInstance serviceInstance = instances. get ( 0 ) ;
String host = serviceInstance. getHost ( ) ;
int port = serviceInstance. getPort ( ) ;
String url = "http://" + host+ ":" + port+ "/service/port/" ;
String forObject = restTemplate. getForObject ( url, String . class ) ;
return forObject;
}
@GetMapping ( "loadProductServicePort" )
public String getProductServerPort ( ) {
String url = "http://lagou-service-product/service/port/" ;
String forObject = restTemplate. getForObject ( url, String . class ) ;
return forObject;
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
这时,我们去访问,多次的访问,会发现,循环的访问,根据get得到的顺序来(前面说的,是没有顺序的,因为会更新)
至此,我们的负载均衡操作完毕,实际上就是操作对应的集合来操作,我们直接传递项目名称
那么负载均衡就会根据这个名称按照负载均衡的操作进行访问
这里默认是类似于轮询算法的(就是后面说明的区域权衡策略),但好像并不是轮询的算法,具体看后面的解释
从get(0)开始,即第一个开始,然后解析成对应的地址和端口,然后访问
当再次访问时,就会解析另外一个,然后访问,所以主要是名称对应的注册信息的多少
那么很明显,从原来的,指定服务器的地址,变成了选择服务器的地址访问
即这里的确是客户端的负载均衡,而不是服务端的负载均衡
Ribbon负载均衡策略:
Ribbon内置了多种负载均衡策略,内部负责复杂均衡的顶级接口为 com.netflix.loadbalancer.IRule
接口简介:
package com. netflix. loadbalancer ;
public interface IRule {
Server choose ( Object var1) ;
void setLoadBalancer ( ILoadBalancer var1) ;
ILoadBalancer getLoadBalancer ( ) ;
}
实际上上面这个图可以手动的生成,我们双击IRule,然后按右键,点击如下:
其中最右边的两个选择,上面一个是新的窗口,下面一个是小窗口(一般不会出现默认的显示),你可以自己测试一下
所以我们点击上面一个,然后若出现如下:
点击第一个即可,然后到这里:
我们点击他,然后使用ctrl+alt+b,出现如下:
然后使用ctrl+a,鼠标不要动,动了需要重新选择,然后按回车,出现如下:
我们进行对比,虽然位置可能不同,但是连接基本相同,至此操作完毕
之所以我们之前操作轮询,是因为过滤器的操作,并没有过滤掉
即我们没有超时,连接数也不多,且在一个区域,那么自然也是轮询,提一个小知识,如果是输出(1),那么结果是1,吗
答:结果是1,()可以看成运算的符号,只是提高优先运算而已,只是单纯的他并不会操作值
那么如何修改负载均衡策略,操作如下:
修改负载均衡策略:
lagou-service-product :
ribbon :
NFLoadBalancerRuleClassName : com.netflix.loadbalancer.RandomRule
lagou-service-product :
ribbon :
NFLoadBalancerRuleClassName : com.netflix.loadbalancer.RoundRobinRule
Ribbon核心源码剖析:
Ribbon工作原理:
SpringCloud充分利用了SpringBoot的自动装配特点,我们Spring Boot启动时,由于有Spring Cloud
那么除了操作自己的对应的配置文件外,也会操作Spring Cloud的配置文件,所以我们找Spring Cloud对应的spring.factories配置文件
点击,找到如下(LoadBalancerAutoConfiguration):
使用ctrl+n,找LoadBalancerAutoConfiguration类(部分代码):
@Configuration
@ConditionalOnClass ( { RestTemplate . class } )
@ConditionalOnBean ( { LoadBalancerClient . class } )
@EnableConfigurationProperties ( { LoadBalancerRetryProperties . class } )
public class LoadBalancerAutoConfiguration {
@LoadBalanced
@Autowired (
required = false
)
private List < RestTemplate > restTemplates = Collections . emptyList ( ) ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
再看LoadBalancerAutoConfiguration类的如下代码:
@Bean
@ConditionalOnMissingBean
public RestTemplateCustomizer restTemplateCustomizer ( final LoadBalancerInterceptor
loadBalancerInterceptor) {
return ( restTemplate) -> {
List < ClientHttpRequestInterceptor > list = new
ArrayList ( restTemplate. getInterceptors ( ) ) ;
list. add ( loadBalancerInterceptor) ;
restTemplate. setInterceptors ( list) ;
} ;
}
我们看看拦截器LoadBalancerInterceptor类,找到如下:
public ClientHttpResponse intercept ( final HttpRequest request, final byte [ ] body, final ClientHttpRequestExecution execution) throws IOException {
URI originalUri = request. getURI ( ) ;
String serviceName = originalUri. getHost ( ) ;
Assert . state ( serviceName != null , "Request URI does not contain a valid hostname: " +
originalUri) ;
return ( ClientHttpResponse ) this . loadBalancer. execute (
serviceName, this . requestFactory. createRequest ( request, body, execution) ) ;
}
我们看看execute方法,发现是个接口,我们看看实现类,发现,只有一个实现类
即RibbonLoadBalancerClient类(很明显就是对应的负载均衡操作的类,即Ribbon的操作):
该方法如下:
public < T > T execute ( String serviceId, LoadBalancerRequest < T > request) throws IOException {
return this . execute ( serviceId, ( LoadBalancerRequest ) request, ( Object ) null ) ;
}
我们进入execute方法:
public < T > T execute ( String serviceId, LoadBalancerRequest < T > request, Object hint) throws
IOException {
ILoadBalancer loadBalancer = this . getLoadBalancer ( serviceId) ;
Server server = this . getServer ( loadBalancer, hint) ;
if ( server == null ) {
throw new IllegalStateException ( "No instances available for " + serviceId) ;
} else {
RibbonLoadBalancerClient. RibbonServer ribbonServer = new
RibbonLoadBalancerClient. RibbonServer ( serviceId, server, this . isSecure ( server, serviceId) , this . serverIntrospector ( serviceId) . getMetadata ( server) ) ;
return this . execute ( serviceId, ( ServiceInstance ) ribbonServer,
( LoadBalancerRequest ) request) ;
}
}
至此大致了解完毕
实际上微服务也就是一个调用者或者提供者,只是更加的细一点,而称为微服务
Hystrix熔断器:
他属于一种容错机制
微服务中的雪崩效应:
当山坡积雪内部的内聚力抗拒不了它所受到的重力拉引时,便向下滑动,引起大量雪体崩塌,人们把这种自然现象称作雪崩
微服务中,一个请求可能需要多个微服务接口才能实现(他们互相调用),会形成复杂的调用链路
服务雪崩效应:是一种因"服务提供者的不可用"(原因)导致"服务调用者不可用"(结果),并将不可用逐渐放大的现象
下面的红色代表不可用或者出现问题
扇入:代表着该微服务被调用的次数,扇入大,说明该模块复用性好
扇出:该微服务调用其他微服务的个数,扇出大,说明业务逻辑复杂
扇入大是一个好事,扇出大不一定是好事
在微服务架构中,一个应用可能会有多个微服务组成,微服务之间的数据交互通过远程过程调用完成
这就带来一个问题,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的"扇出"
如果扇出的链路上某个微服务的调用响应时间过长或者不可用
对微服务A的调用就会占用越来越多的系统资源(如持续的等待,或者一直的执行中),进而引起系统崩溃,所谓的"雪崩效应"
如上面的图中所示,最下游商品微服务响应时间过长,大量请求阻塞,大量线程不会释放,会导致服务器资源耗尽
最终导致上游服务甚至整个系统瘫痪
形成原因:
服务雪崩的过程可以分为三个阶段:
1:服务提供者不可用
2:重试加大请求流量
3:服务调用者不可用
我们先出现1,然后再出现2,最后出现3,就形成了提供者不可用,导致调用者不可用的原因
服务雪崩的每个阶段都可能由不同的原因造成:
当然有些具体的原因是可以解决的,如用户大量请求(可以采用限流)
上面的用户重试是对于提供者说的消费者(调用者),即一般是这个的服务调用者,用户大量请求一般也是如此
当然,也可以说用户就是用户,而不是调用者,虽然也没有问题
雪崩效应解决方案:
从可用性可靠性着想,为防止系统的整体缓慢甚至崩溃,采用的技术手段
下面,我们介绍三种技术手段应对微服务中的雪崩效应
这三种手段都是从系统可用性、可靠性角度出发,尽量防止系统整体缓慢甚至瘫痪
服务熔断:
熔断机制是应对雪崩效应的一种微服务链路保护机制,我们在各种场景下都会接触到熔断这两个字
如高压电路中,如果某个地方的电压过高,熔断器就会熔断,对电路进行保护
如股票交易中,如果股票指数过高,也会采用熔断机制,暂停股票的交易
同样,在微服务架构中,熔断机制也是起着类似的作用
当扇出链路的某个微服务不可用或者响应时间太长时,熔断该节点微服务的调用,进行服务的降级,快速返回错误的响应信息
当检测到该节点微服务调用响应正常后,恢复调用链路
注意:
1:服务熔断重点在"断",切断对下游服务的调用
这个断与普通的访问失败不同,他是由一定的显示失败的,并与降级连接操作,所以通常与降级一起使用
或者说降级加熔断,就是一个整体熔断,简单来说,熔断是具体说明或者断的操作(实现了断),而降级是实现断后面的操作
2:服务熔断和服务降级往往是一起使用的,Hystrix就是这样
服务降级:
通俗讲就是整体资源不够用了,先将一些不关紧的服务停掉(调用我的时候,给你返回一个预留的值,也叫做兜底数据)
待渡过难关高峰过去,再把那些服务打开
服务降级一般是从整体考虑,就是当某个服务熔断之后,服务器将不再被调用
此刻客户端可以自己准备一个本地的fallback回调,返回一个缺省值(即默认值)
这样做,虽然服务水平下降,但好歹可用,比直接挂掉要强
服务限流:
服务降级是当服务出问题或者影响到核心流程的性能时,暂时将服务屏蔽掉,待高峰或者问题解决后再打开
但是有些场景并不能用服务降级来解决,比如秒杀业务这样的核心功能,这个时候可以结合服务限流来限制这些场景的并发/请求量
限流措施也很多,比如:
限制总并发数(比如数据库连接池、线程池)
限制瞬时并发数(如nginx限制瞬时并发连接数)
限制时间窗口内的平均速率(如Guava的RateLimiter、nginx的limit_req模块,限制每秒的平均速率)
限制远程接口调用速率、限制MQ的消费速率等
至于更加的底层限制,可以理解为,我只给出部分窗口,该窗口可以是底层的TCP或者UDP的数量
Hystrix简介:
Hystrix(豪猪),宣言"defend your application(保护你的应用)"是由Netflix开源的一个延迟和容错库
用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性
Hystrix主要通过以下几点实现延迟和容错:
包裹请求:使用HystrixCommand包裹对依赖的调用逻辑,页面静态化微服务方法(@HystrixCommand 添加Hystrix控制)
跳闸机制:当某服务的错误率超过一定的阈值时,Hystrix可以跳闸,停止请求该服务一段时间
资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(舱壁模式)
如果该线程池已满, 发往该依赖的请求就被立即拒绝,而不是排队等待,从而加速失败判定
监控:Hystrix可以近乎实时地监控运行指标和配置的变化,例如成功、失败、超时、以及被拒绝 的请求等
回退机制:当请求失败、超时、被拒绝,或当断路器(即熔断)打开时
执行回退逻辑,回退逻辑由开发人员自行提供,例如返回一个缺省值(兜底数据,即默认值)
自我修复:断路器打开一段时间后,会自动进入"半开"状态(探测服务是否可用,如还是不可用,再次退回打开状态)
Hystrix应用:
熔断处理:
需求目的:商品微服务长时间没有响应,服务消费者(用户调用) -> 页面静态化微服务快速失败给用户提示
引入依赖:服务消费者工程(静态化微服务)中引入Hystrix依赖坐标(也可以添加在父工程中)
< dependency>
< groupId> org.springframework.cloud groupId>
< artifactId> spring-cloud-starter-netflix-hystrix artifactId>
dependency>
开启熔断:服务消费者工程(静态化微服务)的启动类中添加熔断器开启注解,部分启动类代码:
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
public class PageApplication {
也可以这样:
@SpringCloudApplication
public class PageApplication {
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
我们回到商品微服务的ServiceInfoController类(9000和9001都进行操作):
修改部分代码如下:
@GetMapping ( "port" )
public String getPort ( ) {
try {
Thread . sleep ( 5000 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
return port;
}
再回到静态化微服务项目里的PageController类,加上如下方法:
@HystrixCommand (
threadPoolKey = "getProductServerPort2" ,
threadPoolProperties = {
@HystrixProperty ( name= "coreSize" , value = "1" ) ,
@HystrixProperty ( name= "maxQueueSize" , value = "20" ) ,
} ,
commandProperties= {
@HystrixProperty ( name= "execution.isolation.thread.timeoutInMilliseconds" , value = "2000" )
}
)
@GetMapping ( "loadProductServicePort2" )
public String getProductServerPort2 ( ) {
String url = "http://lagou-service-product/service/port/" ;
System . out. println ( 1 ) ;
String forObject = restTemplate. getForObject ( url, String . class ) ;
System . out. println ( forObject) ;
return forObject;
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
接下来,我们重启对应的两台商品微服务,和这里的静态化微服务
对localhost:9100/page/loadProductServicePort2和localhost:9100/page/loadProductServicePort进行访问,可以发现
localhost:9100/page/loadProductServicePort2访问失败,localhost:9100/page/loadProductServicePort访问成功
失败原因:超时,即:
"message" : "getProductServerPort2 timed-out and fallback failed." ,
上面打印了1,那么后面的会不会打印,答:会的,虽然超时报错
但程序还是在执行(因为是超时,而不是程序错误,程序错误自然不会打印后面的了)的
只是首先给前端数据了,即虽然前端有显示数据,但我们回到服务器等待,就会有对应的打印
即先返回默认数据,之后打印出来信息,只是这时,不会在返回了
即只是对应的商品微服务需要执行完才可,所以等待一会,对应的System.out.println(forObject)就打印信息了
但也正是由于报错,所以导致页面返回的数据不会是对应的return forObject;
因为他返回之前,可以说是重新赋值返回数据,再给前端
然后他的返回不会给前端了
我们现在将两个商品微服务的5000修改成1000,继续测试,发现,得到了数据,即返回了
当然,调用的时间未必是设置的时间,即就算你设置了1999,但可能也会报错
因为调用也需要时间,那么总体来说可能超过了2秒
所以说,对于程序来说,有些时候,最好要考虑更加长远一些,来消除调用时间,而不是刚刚好的设置
所以上面就将5000修改成1000来测试成功的案例,而不是1999,更加长远的设置了
现在,我们去除一个商品微服务的休眠,如9000,进行测试
可以发现,有时报错,有时出现数据,这是正常的,那么现在有个问题
如果直接的返回错误数据,那么对用户体验肯定是不行的,那么如何操作返回的数据呢
即这里就是在前面熔断后的操作服务降级的处理了,具体处理如下:
回到静态化微服务项目里的PageController类,加上如下方法:
@HystrixCommand (
commandProperties= {
@HystrixProperty ( name= "execution.isolation.thread.timeoutInMilliseconds" , value = "2000" )
} ,
fallbackMethod = "fallBack"
)
@GetMapping ( "loadProductServicePort3" )
public String getProductServerPort3 ( ) {
String url = "http://lagou-service-product/service/port/" ;
System . out. println ( 1 ) ;
String forObject = restTemplate. getForObject ( url, String . class ) ;
System . out. println ( 5 ) ;
System . out. println ( forObject) ;
return forObject;
}
public String fallBack ( ) {
return "-1" ;
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
为了证明当我们的name没有对应正确时,是否使用默认的,比如:
@HystrixProperty ( name= "execution.isolation.thread.timeoutInMilliseconds" , value = "2000" )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
至此,我们的对应熔断和降级操作完毕
Hystrix舱壁模式:
即前面的如下:
资源隔离:Hystrix为每个依赖都维护了一个小型的线程池(舱壁模式 )
如果该线程池已满, 发往该依赖的请求就被立即拒绝,而不是排队等待,从而加速失败判定
即:线程池隔离策略
如果不进行任何设置,所有熔断方法使用一个Hystrix线程池(10个线程)
那么这样的话会导致问题,这个问题并不是扇出链路微服务不可用导致的,而是我们的线程机制导致的
如果方法A的请求把10个线程都用了,方法2请求处理的时候压根都没法去访问B,因为没有线程可用,并不是B服务不可用
为了避免问题服务请求过多导致正常服务无法访问,Hystrix 不是采用增加线程数
而是单独的为每一个控制方法创建一个线程池的方式,这种模式叫做“舱壁模式",也是线程隔离的手段
即只要我们操作了如下:
threadPoolKey = "getProductServerPort2" ,
Hystrix工作流程与高级应用:
这里是可以自定义设置的一个熔断机制,如果是超时的,那么按照超时的处理,而不是这里的处理
1:当调用出现问题时,开启一个时间窗(10s)
2:在这个时间窗内,统计调用次数是否达到最小请求数:
如果没有达到,则重置统计信息,回到第1步
如果达到了,则统计失败的请求数占所有请求数的百分比,这时查看是否达到阈值:
如果达到,则熔断,即则跳闸(请求对应服务基本失败)
如果没有达到,则重置统计信息,回到第1步
一般来说,这里就是熔断中的处理,即熔断处理,那么对应的调用基本是调用不了的
虽然可能也可以调用(如超时造成的熔断,而正是因为这样,所以有自我修复机制)
虽然熔断的意思是操作不可调用(超时算作一种特殊的不可调用,因为最终会调用)
3:如果跳闸(到这里代表即熔断,因为前面的阈值,也可称他为熔断,后面会说明为什么称他为熔断)
则会开启一个活动窗口(默认5s),每隔5s,Hystrix会让一个请求通过到达那个问题服务,看是否调用成功
如果成功,重置断路器(重置跳闸处理,即关闭跳闸,也就没有活动窗口了)回到第1步(即上面的1),如果失败,回到第3步
通常处理超时外,基本都是访问失败,即后面的不会执行
上面的三个步骤,整体就是熔断中的工作流程,简称为熔断(虽然与前面介绍的熔断的意思并不是非常符合)
因为并没有完全的不能访问,如延时,实际上介绍的熔断也是如此,但针对于显示来说的,自然是全部断掉
但针对于程序并不是,如延时
而跳闸可以说明是真正的熔断,针对于这个熔断来说
当然,无论是说明跳闸还是熔断,上面的整体都是对访问失效的一种操作而已
所以说,你认为跳闸不是熔断也是可以的,而我之所以说跳闸是真正的熔断而不是处理的原因是
到了跳闸这里,代表访问基本(并不是全部)是失效的(虽然受自己的影响)
如何设置对应的值:
commandProperties= {
@HystrixProperty ( name= "execution.isolation.thread.timeoutInMilliseconds" , value = "2000" ) ,
@HystrixProperty ( name = "metrics.rollingStats.timeInMilliseconds" , value = "8000" ) ,
@HystrixProperty ( name = "circuitBreaker.requestVolumeThreshold" , value = "2" ) ,
@HystrixProperty ( name = "circuitBreaker.errorThresholdPercentage" , value = "50" ) ,
@HystrixProperty ( name = "circuitBreaker.sleepWindowInMilliseconds" , value = "3000" )
} ,
那么我们如何查看对应的设置是否成功呢,首先根据需求,自然是先要操作熔断,再次发送一个成功的请求
那么当对应的自我修复机制拿到该请求时,就会重置熔断,在发送之前
可以先访问下面的健康机制,来看看对应的的状态,比如:
"hystrix" : {
"status" : "UP"
}
这里我们使用Postman来进行测试,首先,需要在8秒的窗口中,只要调用的问题数是正常的一半,那么就会开启熔断(跳闸)
那么如何进行多次的访问呢,在Postman里面看如下:
该熔断测试里面只有这localhost:9100/page/loadProductServicePort3一个请求,至于为什么后面会说明
点击右边的Run,到如下:
其中这个5代表执行次数,0代表间隔,即执行完,立即执行另外一个请求
从这里可以看出,他们是逐步的,而不是异步的,即操作同步,且是执行熔断测试里面的所有请求
即执行5次熔断测试的所有请求(所有请求执行完,才算一次,且是从上到下的执行,即这个5,可以理解为5次循环的执行)
这就是为什么最好只有一个请求的原因,因为很方便,我们点击Run熔断测试
然后访问http://localhost:9100/actuator/health,如果出现如下(因为是随机的策略,所以可能不会是开启跳闸):
"hystrix" : {
"status" : "CIRCUIT_OPEN" ,
"details" : {
"openCircuitBreakers" : [
"PageController::getProductServerPort3"
]
}
}
代表开启了跳闸(也可以说是熔断),即进行跳闸中的操作了
接下来我们手动的访问localhost:9100/page/loadProductServicePort3(虽然他并不知道使用了负载均衡)
而正是因为负载均衡,所以可以成功,即没有操作熔断处理的成功会出现如下(虽然对应并没有修复)
那么访问http://localhost:9100/actuator/health,就会出现如下:
"hystrix" : {
"status" : "UP"
}
代表没有使用熔断,至此测试完毕,当然了,因为是负载均衡,所以手动的访问是可以成功的,否则一般不会访问成功
我们前面通过注解进行的配置也可以配置在配置文件中(静态化微服务的配置文件,因为他操作了Hystrix):
hystrix :
command :
default :
circuitBreaker :
forceOpen : false
errorThresholdPercentage : 50
sleepWindowInMilliseconds : 3000
requestVolumeThreshold : 2
execution :
isolation :
thread :
timeoutInMilliseconds : 2000
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
基于springboot的健康检查观察跳闸状态(自动投递微服务暴露健康检查细节):
management :
endpoints :
web :
exposure :
include : "*"
endpoint :
health :
show-details : always
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
访问健康检查接口:http://localhost:9100/actuator/health,不能是post,否则访问失败,基本只能是get
post请求的部分错误信息:
{
"timestamp" : "2022-09-04T03:10:43.083+0000" ,
"status" : 405 ,
"error" : "Method Not Allowed" ,
"message" : "Request method 'POST' not supported" ,
"trace" : "org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported\ r\ n\ tat org.springframewo
Hystrix 线程池队列配置案例:
有一次在生产环境,突然出现了很多笔还款单被挂起,后来排查原因,发现是内部系统调用时出现了Hystrix调用异常
在开发过程中,因为核心线程数设置的比较大,没有出现这种异常,放到了测试环境,偶尔有出现这种情况
后来调整maxQueueSize属性,确实有所改善,可没想到在生产环境跑了一段时间后却又出现这种了情况
此时我第一想法就是去查看maxQueueSize属性,可是maxQueueSize属性是设置值了
当时就比较纳闷了,为什么maxQueueSize属性不起作用
后来通过查看官方文档发现Hystrix还有一个queueSizeRejectionThreshold属性,这个属性是控制队列最大阈值的
而Hystrix默认只配置了5个,因此就算我们把maxQueueSize的值设置再大,也是不起作用的。两个属性必须同时配置
hystrix :
threadpool :
default :
coreSize : 10
maxQueueSize : 1000
queueSizeRejectionThreshold : 800
正确的配置案例:
将核心线程数调低,最大队列数和队列拒绝阈值的值都设置大一点:
hystrix :
threadpool :
default :
coreSize : 10
maxQueueSize : 1500
queueSizeRejectionThreshold : 1000
至此,熔断操作完毕,最后说明,在显示时,熔断代表断开,但在程序里,并不是的,因为有延时的存在
虽然是断开,但其中还是有一系列的操作的,针对于调用的维护的操作,如自我修复机制
Feign远程调用组件,一般我们指定提供者的信息,然后调用
在之前的案例中,服务消费者调用服务提供者的时候使用RestTemplate技术
如:
@GetMapping ( "loadProductServicePort3" )
public String getProductServerPort3 ( ) {
List < String > services = discoveryClient. getServices ( ) ;
System . out. println ( services) ;
String url = "http://lagou-service-product/service/port/" ;
System . out. println ( 1 ) ;
String forObject = restTemplate. getForObject ( url, String . class ) ;
System . out. println ( 5 ) ;
System . out. println ( forObject) ;
return forObject;
}
在操作Feign之前,首先先介绍一下他
Feign简介:
Feign是Netflix开发的一个轻量级RESTful的HTTP服务客户端(用它来发起请求,远程调用的)
是以Java接口注解的方式调用Http请求,而不用像Java中通过封装HTTP请求报文的方式直接调用
Feign被广泛应用在Spring Cloud 的解决方案中
也类似于Dubbo,服务消费者拿到服务提供者的接口,然后像调用本地接口方法一样去调用,实际发出的是远程的请求
对于Feign的作用:
Feign可帮助我们更加便捷,优雅的调用HTTP API:
不需要我们去拼接url然后呢调用restTemplate的api,在SpringCloud中,使用Feign非常简单
创建一个接口(在消费者,即服务调用方这一端),并在接口上添加一些注解,代码就完成了
SpringCloud对Feign进行了增强,使Feign支持了SpringMVC注解(OpenFeign)
本质:封装了Http调用流程,更符合面向接口化的编程习惯,类似于Dubbo的服务调用
Feign配置应用:
在服务调用者工程(消费)创建接口(添加注解)
(效果)Feign = RestTemplate+Ribbon+Hystrix,他可以操作对应的三个功能,前提是需要有对应的依赖
在服务消费者工程(页面静态化微服务)中引入Feign依赖(或者父类工程):
< dependency>
< groupId> org.springframework.cloud groupId>
< artifactId> spring-cloud-starter-openfeign artifactId>
dependency>
要想使用,首先需要启用,回到静态化微服务的启动类:
@SpringCloudApplication
@EnableFeignClients
public class PageApplication {
在消费者微服务中创建Feign接口:
在page包下,创建feign.ProductFeign接口:
package com. lagou. page. feign ;
import com. lagou. common. pojo. Products ;
import org. springframework. cloud. openfeign. FeignClient ;
import org. springframework. web. bind. annotation. GetMapping ;
import org. springframework. web. bind. annotation. PathVariable ;
@FeignClient ( name= "lagou-service-product" )
public interface ProductFeign {
@GetMapping ( "/product/query/{id}" )
public Products queryById ( @PathVariable Integer id) ;
@GetMapping ( "/service/port" )
public String getPort ( ) ;
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
注意:
1:@FeignClient注解的name属性用于指定要调用的服务提供者名称,和服务提供者yml文件中spring.application.name保持一致
2:接口中的接口方法,就好比是远程服务提供者Controller中的Hander方法(只不过如同本地调用了)
那么在进行参数绑定的时,可以使用@PathVariable、@RequestParam、@RequestHeader等
这也是OpenFeign对SpringMVC注解的支持
但是需要注意value必须设置,否则会抛出异常(包括这样的说明:但是上面的value必须设置,即{id}
否则访问会抛出异常,这是基本的知识,68章博客有说明)
3:@FeignClient(name = “lagou-service-product”),name在消费者微服务中只能出现一次
升级Spring Boot 2.1.0 Spring Cloud Greenwich.M1 版本后,在2个Feign接口类内定义相同的名字
即@FeignClient的name若是相同的名字 就会出现报错,在之前的版本不会提示报错,大概运行时报错,也有可能是覆盖了
所以最好将调用一个微服务的信息都定义在一个Feign接口中
改造PageController类中原有的调用方式(部分):
@Autowired
private ProductFeign productFeign;
@GetMapping ( "loadProductServicePort3" )
public String getProductServerPort3 ( ) {
String port = productFeign. getPort ( ) ;
return port;
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
启动继续访问,发现可以访问,至此操作成功
Feign对负载均衡的支持:
Feign 本身已经集成了Ribbon依赖和自动配置,即他的访问是操作负载均衡的
因此我们不需要额外引入依赖或者使用@LoadBalanced注解,之前我们说过Feign = RestTemplate+Ribbon+Hystrix
可以理解为他操作RestTemplate并给他加上@LoadBalanced注解,在操作部分熔断处理(之所以是部分,因为没有回退操作)
拼接的操作给接口即可,所以简化了我们的编写
可以通过 ribbon.xx 来进行全局配置,也可以通过 “服务名.ribbon.xx” 来对指定服务进行细节配置(参考之前的策略设置, 此处略)
Feign默认的请求处理超时时长是1 s,有时候我们的业务确实执行的需要一定时间
那么这个时候,我们就 需要调整请求处理超时时长, Feign自己有超时设置,如果配置Ribbon的超时,则会以Ribbon的为准
lagou-service-product :
ribbon :
ConnectTimeout : 2000
ReadTimeout : 5000
OkToRetryOnAllOperations : true
MaxAutoRetries : 0
MaxAutoRetriesNextServer : 0
NFLoadBalancerRuleClassName : com.netflix.loadbalancer.RoundRobinRule
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
注意:在我们使用熔断启用时,如果出现超时错误,一般是对应的自己的错误(返回对应的错误信息)
但若有回退,那么都操作回退
但是,若不使用熔断启用,即使用Feign的熔断处理,那么只考虑请求超时,对应的访问超时无论怎么设置
都只有请求的错误或者成功
所以说,若单独的操作超时,那么只有该超时的操作,而没有其他的熔断超时
你可以试着将@EnableCircuitBreaker(启用熔断服务),去除,将熔断超时设置为1000
来测试即可,你会发现,只有请求的超时问题,他并不会操作,即因为并不是同一个熔断
他是Feign的熔断,即只操作Feign的熔断了,但是若启用的话,则都会操作,且熔断的超时优先,只要满足就会返回
所以说,通常情况下,@EnableCircuitBreaker最好加上,但是若只针对提供者,那么可以不加,实际上我们也只针对提供者
所以有些时候是不加的,而实际上Feign操作了真正的熔断,因为超时的错误信息,使得不会让后面的程序执行
而不像@EnableCircuitBreaker一样,可以执行,所以他们的共同操作,可以使得完全真正的熔断
虽然要考虑很多细节,但只要跨大步即可
Feign对熔断器的支持:
在Feign客户端工程配置文件(application.yml)中开启Feign对熔断器的支持
feign :
hystrix :
enabled : true
Feign的超时时长设置那其实就上面Ribbon的超时时长设置
Hystrix超时设置(就按照之前Hystrix设置的方式就OK了)
最后要注意,对应的超时是针对调用,而不是访问,与我们手动的熔断器的对象不同,所以会有如下:
1:开启Hystrix之后,Feign中的方法都会被进行一个管理了,一旦出现问题就进入对应的回退逻辑处理
2:针对超时这一点,当前有两个超时时间设置(Feign/hystrix),熔断的时候是根据这两个时间的最小值来进行的
即处理时长超过最短的那个超时时间了就熔断进入回退降级逻辑
但由于不是同一个熔断器,即原来的回退不会操作,因为不是同一个熔断器
好像只操作自己的默认的了,并不会操作设置的(不是同一个),所以即1秒(最小的)
且负载均衡的配置可能并不会使用(使用他自己的),所以他的默认设置为false,即最好不要使用这个,因为时间太短
需要自己去配置他的信息,具体设置可以去百度(好像并没有),大概是因为是提供者吧
那么这里说明一下,对应的回退逻辑方法操作如下:
首先在feign包下,创建ProductFeignFallBack类:
package com. lagou. page. feign ;
import com. lagou. common. pojo. Products ;
import org. springframework. stereotype. Component ;
@Component
public class ProductFeignFallBack implements ProductFeign {
@Override
public Products queryById ( Integer id) {
return null ;
}
@Override
public String getPort ( ) {
return "-1" ;
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
我们需要指定,指定操作如下:
回到ProductFeign接口,修改如下:
@FeignClient ( name= "lagou-service-product" , fallback = ProductFeignFallBack . class )
public interface ProductFeign {
至此,我们重新启动,进行测试,发现,对应的错误不是错误信息了,而是-1,至此他对提供的调用,实现了完整的熔断了
注意:当操作了回退时,延时的对应的程序会使得继续运行,而不会直接的停止,没有回退不会,与手动的熔断器,就这里不同
Feign对请求压缩和响应压缩的支持:
Feign 支持对请求和响应进行GZIP压缩,以减少通信过程中的性能损耗
通过下面的参数 即可开启请求与响应的压缩功能:
feign :
hystrix :
enabled : true
compression :
request :
enabled : true
mime-types : text/html, application/xml, application/json
min-request-size : 2048
response :
enabled : true
GateWay网关组件:
网关:微服务架构中的重要组成部分
局域网中就有网关这个概念,局域网接收或者发送数据出去通过这个网关
比如用Vmware虚拟机软件搭建虚拟机集群的时候,往往我们需要选择IP段中的一个IP作为网关地址
我们学习的GateWay–>Spring Cloud GateWay(它只是众多网关解决方案中的一种)
GateWay简介:
Spring Cloud GateWay是Spring Cloud的一个全新项目,目标是取代Netflix Zuul
它基于Spring5.0+SpringBoot2.0+WebFlux(基于高性能的Reactor模式响应式通信框架Netty,异步非阻塞模型)等技术开发
性能高于Zuul,官方测试,GateWay是Zuul的1.6倍,旨在为微服务架构提供一种简单有效的统一的API路由管理方式
Spring Cloud GateWay不仅提供统一的路由方式(反向代理)
并且基于 Filter(定义过滤器对请求过滤,完成一些功能)链的方式提供了网关基本的功能
例如:鉴权、流量控制、熔断、路径重写、日志监控等
网关在架构中的位置:
GateWay核心概念 :
Spring Cloud GateWay天生就是异步非阻塞的,基于Reactor模型(同步非阻塞的I/O多路复用机 制)
一个请求—>网关根据一定的条件匹配—匹配成功之后可以将请求转发到指定的服务地址
而在这 个过程中,我们可以进行一些比较具体的控制(限流、日志、黑白名单)
路由(route): 网关最基础的部分,也是网关比较基础的工作单元
路由由一个ID、一个目标URL(最终路由到的地址)、一系列的断言(匹配条件判断)和Filter过滤器(精细化控制)组成
如果断言为true,则匹配该路由
断言(predicates):参考了Java8中的断言java.util.function.Predicate
开发人员可以匹配Http请求中的所有内容(包括请求头、请求参数等,类似于nginx中的location匹配一样)
如果断言 与请求相匹配则路由
过滤器(fifilter):一个标准的Spring webFilter,使用过滤器,可以在请求之前或者之后执行业务 逻辑
GateWay如何工作:
Spring 官方介绍:
客户端向Spring Cloud GateWay发出请求,然后在GateWay Handler Mapping中找到与请求相匹配 的路由
将其发送到GateWay Web Handler,Handler再通过指定的过滤器链来将请求发送到我们实际 的服务执行业务逻辑,然后返回
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前 (pre)或者之后(post)执行业务逻辑
Filter在"pre"类型过滤器中可以做参数校验、权限校验、流量监控、日志输出、协议转换等
在"post"类型的过滤器中可以做响应内容、响应头的修改、日志的输出、流量监控等
GateWay应用:
使用网关对静态化微服务进行代理(添加在它的上游,相当于隐藏了具体微服务的信息,对外暴露的 是网关)
创建工程lagou-cloud-gateway并导入依赖
最好是独立的项目,而不是不是子项目,因为网关主要是独立的,虽然也可以是子项目
GateWay不需要使用web模块,它引入的是WebFlux(类似于SpringMVC)
< parent>
< groupId> org.springframework.boot groupId>
< artifactId> spring-boot-starter-parent artifactId>
< version> 2.1.6.RELEASE version>
< relativePath />
parent>
< dependencies>
< dependency>
< groupId> org.springframework.cloud groupId>
< artifactId> spring-cloud-commons artifactId>
dependency>
< dependency>
< groupId> org.springframework.cloud groupId>
< artifactId> spring-cloud-starter-netflix-eureka-client artifactId>
dependency>
< dependency>
< groupId> org.springframework.cloud groupId>
< artifactId> spring-cloud-starter-gateway artifactId>
dependency>
< dependency>
< groupId> org.springframework.boot groupId>
< artifactId> spring-boot-starter-webflux artifactId>
dependency>
< dependency>
< groupId> org.springframework.boot groupId>
< artifactId> spring-boot-starter-logging artifactId>
dependency>
< dependency>
< groupId> org.springframework.boot groupId>
< artifactId> spring-boot-starter-test artifactId>
< scope> test scope>
dependency>
< dependency>
< groupId> org.projectlombok groupId>
< artifactId> lombok artifactId>
< version> 1.18.4 version>
< scope> provided scope>
dependency>
< dependency>
< groupId> com.sun.xml.bind groupId>
< artifactId> jaxb-core artifactId>
< version> 2.2.11 version>
dependency>
< dependency>
< groupId> javax.xml.bind groupId>
< artifactId> jaxb-api artifactId>
dependency>
< dependency>
< groupId> com.sun.xml.bind groupId>
< artifactId> jaxb-impl artifactId>
< version> 2.2.11 version>
dependency>
< dependency>
< groupId> org.glassfish.jaxb groupId>
< artifactId> jaxb-runtime artifactId>
< version> 2.2.10-b140310.1920 version>
dependency>
< dependency>
< groupId> javax.activation groupId>
< artifactId> activation artifactId>
< version> 1.1.1 version>
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>
< optional> true optional>
dependency>
< dependency>
< groupId> org.springframework.cloud groupId>
< artifactId> spring-cloud-starter-sleuth artifactId>
dependency>
< dependency>
< groupId> org.springframework.cloud groupId>
< artifactId> spring-cloud-starter-zipkin artifactId>
dependency>
dependencies>
< dependencyManagement>
< dependencies>
< dependency>
< groupId> org.springframework.cloud groupId>
< artifactId> spring-cloud-dependencies artifactId>
< version> Greenwich.RELEASE version>
< type> pom type>
< scope> import scope>
dependency>
dependencies>
dependencyManagement>
< build>
< plugins>
< plugin>
< groupId> org.apache.maven.plugins groupId>
< artifactId> maven-compiler-plugin artifactId>
< configuration>
< source> 11 source>
< target> 11 target>
< encoding> utf-8 encoding>
configuration>
plugin>
< plugin>
< groupId> org.springframework.boot groupId>
< artifactId> spring-boot-maven-plugin artifactId>
plugin>
plugins>
build>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
到资源文件夹下,创建com.lagou.gateway.GateWayServerApplication类(作为启动类):
package com. lagou. gateway ;
import org. springframework. boot. SpringApplication ;
import org. springframework. boot. autoconfigure. SpringBootApplication ;
import org. springframework. cloud. client. discovery. EnableDiscoveryClient ;
@SpringBootApplication
@EnableDiscoveryClient
public class GateWayServerApplication {
public static void main ( String [ ] args) {
SpringApplication . run ( GateWayServerApplication . class , args) ;
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
创建配置文件application.yml:
server :
port : 9300
eureka :
client :
serviceUrl :
defaultZone : http: //LagouCloudEurekaServerA: 9200/eureka, http: //LagouCloudEurekaServerB: 9201/eureka
instance :
prefer-ip-address : true
instance-id : ${ spring.cloud.client.ip- address} : ${ spring.application.name} : ${ server.port} : @project.version@
spring :
application :
name : lagou- cloud- gateway
cloud :
gateway :
routes :
- id : service- page- router
uri : http: //127.0.0.1: 9100
predicates :
- Path=/page/**
- id : service- product- router
uri : http: //127.0.0.1: 9000
predicates :
- Path=/product/**
filters :
- StripPrefix=1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
这时,我们访问localhost:9300/product/query/1,他会找根据除了"ip(或者域名):端口"的整体地址
也就是product/query/1/进行匹配
很明显,可以发现,匹配到了/product/**,即将对应的地址变成http://127.0.0.1:9000/product/query/1进行转发操作
若是操作了过滤,则需要localhost:9300/product/product/query/1,因为地址变成之后
会去除第一个的部分(因为是过滤器,自然会进行操作,即也是在地址变成后操作),也就是/product
这里的/无论怎么加都是一个,除了第一部分的前面的/不能加\外,其他可以
当然这是程序的作用,也有时因为浏览器的作用(浏览器的作用是主要的,有时再第一部分前面只能是一个/)
当然,这些作用我们并不需要知道(一般都是省略变成一个"/",有些部分可能不会,如第一部分的前面)
但我们只要按照规范写即可,就能防止这些问题,即写上一个"/“即可,所以我们也最好操作一个”/"
这就是具体流程,那么我们也知道,上面进行了注册,如果不注册,可以操作吗
答:可以操作,因为他只是注册,且我们并没有使用注册中心的操作,他只是给我们操作转发而已,所以可以使用
上面的网关独有的404一般是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
GateWay路由规则详解:
Spring Cloud GateWay 帮我们内置了很多 Predicates功能
实现了各种路由匹配规则(通过 Header、请求参数等作为条件)匹配到对应的路由
时间点后匹配:
spring :
cloud :
gateway :
routes :
- id : after_route
uri : https: //example.org
predicates :
- After=2017- 01- 20T17: 42: 47.789- 07: 00[ America/Denver]
时间点前匹配:
spring :
cloud :
gateway :
routes :
- id : before_route
uri : https: //example.org
predicates :
- Before=2017- 01- 20T17: 42: 47.789- 07: 00[ America/Denver]
时间区间匹配:
spring :
cloud :
gateway :
routes :
- id : between_route
uri : https: //example.org
predicates :
- Between=2017- 01- 20T17: 42: 47.789- 07: 00[ America/Denver] , 2017- 01- 21T17: 42: 47.789- 07: 00[ America/Denver]
指定Cookie,且可以操作正则匹配指定值:
spring :
cloud :
gateway :
routes :
- id : cookie_route
uri : https: //example.org
predicates :
- Cookie=chocolate, ch.p
指定Header,且可以操作正则匹配指定值:
spring :
cloud :
gateway :
routes :
- id : header_route
uri : https: //example.org
predicates :
- Header=X- Request- Id, \d+
请求Host匹配指定值:
spring :
cloud :
gateway :
routes :
- id : host_route
uri : https: //example.org
predicates :
- Host=**.somehost.org , **.anotherhost.org
请求Method匹配指定请求方式:
spring :
cloud :
gateway :
routes :
- id : method_route
uri : https: //example.org
predicates :
- Method=GET, POST
请求路径,且可以操作正则匹配:
spring :
cloud :
gateway :
routes :
- id : path_route
uri : https: //example.org
predicates :
- Path=/red/{ segment} , /blue/{ segment}
请求包含某参数:
spring :
cloud :
gateway :
routes :
- id : query_route
uri : https: //example.org
predicates :
- Query=green
请求包含某参数,且可以操作参数值匹配正则表达式:
spring :
cloud :
gateway :
routes :
- id : query_route
uri : https: //example.org
predicates :
- Query=red, gree.
远程地址匹配:
spring :
cloud :
gateway :
routes :
- id : remoteaddr_route
uri : https: //example.org
predicates :
- RemoteAddr=192.168.1.1/24
上面大致了解即可,一般我们会操作时间的区间来操作规定时间内的活动,和路径的匹配等等,其他的我们只要了解即可
若出现了对应的业务环境,则可以去操作,但我们通常只会操作路径的匹配,以及特殊情况下的时间区间来匹配
GateWay动态路由详解:
GateWay支持自动从注册中心中获取服务列表并访问,即所谓的动态路由
前面因为我们是固定写死的,所以我们并没有操作注册信息,现在我们需要对应的信息了
实现步骤如下:
1:pom.xml中添加注册中心客户端依赖(因为要获取注册中心服务列表,eureka客户端已经引入)
2:动态路由配置
spring :
application :
name : lagou- cloud- gateway
cloud :
gateway :
routes :
- id : servicrrr
uri : lb: //lagou- service- page
predicates :
- Path=/page/**
- id : serviceee
uri : lb: //lagou- service- product
predicates :
- Path=/product/** ,
filters :
- StripPrefix=1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
注意:动态路由设置时,uri以 lb://开头(lb代表从注册中心获取服务),后面是需要转发到的服务名称
因为需要通过该服务获取信息,而操作地址和端口来访问,而正是因为如此,需要注册,这样才可以得到信息
因为只有注册了,我们才基本可以获取对应的列表信息
GateWay过滤器:
GateWay过滤器简介:
从过滤器生命周期(影响时机点)的角度来说,主要有两个pre和post:
他们简单的解释为,客户端发送请求信息到过滤器,过滤器到微服务,然后微服务将响应信息到过滤器,过滤器到客户端的意思
其中过滤器就可以称为网关(多个过滤器链的操作)
过滤器除了可以操作请求和响应外,也可以操作转发和重定向,即过滤器也可以看成一个servlet
即可以只有过滤器也可,而不操作资源的存放,单独的将服务器当成过滤器,一般网关就是这样
即request和response的走向,上面的去除就是request的走向
最后到访问微服务,但他们都是在网关上的过滤
从过滤器类型的角度, Spring Cloud GateWay的过滤器分为GateWayFilter和GlobalFilter两种:
如GatewayFilter可以去掉url中的占位后转发路由,比如:
predicates :
- Path=/product/**
filters :
- StripPrefix=1
filters :
- StripPrefix=1
predicates :
- Path=/product/**
注意:GlobalFilter全局过滤器是程序员使用比较多的过滤器,我们主要讲解这种类型
与GatewayFilter不同的是,需要代码操作,即自定义类来实现,而不是操作配置文件
自定义全局过滤器实现IP访问限制(黑白名单):
请求过来时,判断发送请求的客户端的ip,如果在黑名单中,拒绝访问
自定义GateWay全局过滤器时,我们实现Global Filter接口即可,通过全局过滤器可以实现黑白名单、限流等功能
接下来我们在lagou-cloud-gateway项目中的gateway包下,创建filter.BlackListFilter类:
package com. lagou. gateway. filter ;
import org. springframework. boot. web. servlet. filter. OrderedFilter ;
import org. springframework. cloud. gateway. filter. GatewayFilter ;
import org. springframework. cloud. gateway. filter. GatewayFilterChain ;
import org. springframework. cloud. gateway. filter. GlobalFilter ;
import org. springframework. core. Ordered ;
import org. springframework. core. io. buffer. DataBuffer ;
import org. springframework. http. HttpStatus ;
import org. springframework. http. server. reactive. ServerHttpRequest ;
import org. springframework. http. server. reactive. ServerHttpResponse ;
import org. springframework. stereotype. Component ;
import org. springframework. web. server. ServerWebExchange ;
import reactor. core. publisher. Mono ;
import java. net. InetSocketAddress ;
import java. util. ArrayList ;
import java. util. List ;
@Component
public class BlackListFilter implements GlobalFilter , Ordered {
private static List < String > blackList = new ArrayList < > ( ) ;
static {
blackList. add ( "127.0.0.1" ) ;
}
@Override
public Mono < Void > filter ( ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange. getRequest ( ) ;
ServerHttpResponse response = exchange. getResponse ( ) ;
String hostString = request. getRemoteAddress ( ) . getHostString ( ) ;
if ( blackList. contains ( hostString) ) {
response. setStatusCode ( HttpStatus . UNAUTHORIZED) ;
String data = "request be denied" ;
DataBuffer wrap = response. bufferFactory ( ) . wrap ( data. getBytes ( ) ) ;
return response. writeWith ( Mono . just ( wrap) ) ;
}
return chain. filter ( exchange) ;
}
@Override
public int getOrder ( ) {
return 0 ;
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
这时,我们去访问http:/127.0.0.1:9300/product/service/port和http://localhost:9300/product/service/port
前者返回对应的提示信息request be denied,后面的可以正常访问,因为我们获取的是对应的地址(该地址不会是DNS变成的ip)
所以是不同的,至此黑名单操作完毕
GateWay高可用:
网关作为非常核心的一个部件,如果挂掉,那么所有请求都可能无法路由处理,因此我们需要做GateWay的高可用
GateWay的高可用很简单:可以启动多个GateWay实例来实现高可用
在GateWay的上游使用Nginx等负载均衡设备进行负载转发以达到高可用的目的
启动多个GateWay实例(假如说两个,一个端口9002,一个端口9003),剩下的就是使用Nginx等完成负载代理即可,示例如下:
upstream gateway {
server 127.0 .0.1:9002;
server 127.0 .0.1:9003;
}
location / {
proxy_pass http://gateway;
}
Spring Cloud Config 分布式配置中心:
分布式配置中心应用场景:
往往,我们使用配置文件管理一些配置信息,比如application.yml
单体应用架构,配置信息的管理、维护并不会显得特别麻烦,手动操作就可以,因为就一个工程
微服务架构,因为我们的分布式集群环境中可能有很多个微服务
我们不可能一个一个去修改配置然后重启生效(通过前面的学习和测试,我可以很直观的感受到这样的痛苦)
在一定场景下我们还需要在运行期间动态调整配置信息
比如:根据各个微服务的负载情况,动态调整数据源连接池大小,我们希望配置内容发生变化的时候,微服务可以自动更新
大多数情况下,我们都需要如此,在之前的学习中,通常都是需要重新部署的,而不是自动更新,因为他们只会加载一次
有些是为了保存数据的就不需要加载,如sql语句,可以部署时进行修改
这里我考虑加载一次的(之前的学习中说明加载一次的地方也是这样的考虑,可能并没有说明),这里要注意
虽然之前对应的xml可以不用我们去修改java代码,有效的操作了硬编码
但我们虽然通常说解决硬编码使用xml,但实际上xml并没有解决
只是更好的修改而已,还是需要重新的操作部署的,但是,若有再次的读取配置文件的话,才算是解决硬编码
如自动的读取配置文件,或者每过一段时间读取一次配置文件,但这就要考虑整个系统的运行问题了
所以我们也常说,xml是解决硬编码的,但现在,我们一般需要去除xml了
因为太过于繁琐,且有些并不需要修改,也就使得,无效的使用xml,所以现在一般有Spring Boot的出现
具体的场景总结如下:
1:集中配置管理,一个微服务架构中可能有成百上千个微服务,所以集中配置管理是很重要的(一次修改、到处生效)
2:不同环境不同配置,比如数据源配置在不同环境(开发dev,测试test,生产prod)中是不同的
3:运行期间可动态调整,例如,可根据各个微服务的负载情况,动态调整数据源连接池大小等配置修改后可自动更新
4:如配置内容发生变化,微服务可以自动更新配置
那么,我们就需要对配置文件进行集中式管理,这也是分布式配置中心的作用
Spring Cloud Config:
Config简介:
Spring Cloud Config是一个分布式配置管理方案,包含了 Server端和 Client端两个部分
Server 端:提供配置文件的存储、以接口的形式将配置文件的内容提供出去
通过使用@EnableConfigServer注解在 Spring boot 应用中非常简单的嵌入
Client 端:通过接口获取配置数据并初始化自己的应用
Config分布式配置应用:
说明:Config Server是集中式的配置服务,用于集中管理应用程序各个环境下的配置,默认使用Git存储配置文件内容,也可以SVN
比如,我们要对"静态化微服务或者商品微服务"的application.yml进行管理
区分开发环境(dev)、测试环境(test)、生产环境(prod)
1:登录GitHub,创建项目lagou-config
输入网址https://github.com/,然后创建仓库lagou-config
2:上传yml配置文件,命名规则如下(之所以这样的命名,在后面的配置中就知道了):
{application}-{profile}.yml 或者 {application}-{profile}.properties
其中,application为应用名称,profile指的是环境(用于区分开发环境,测试环境、生产环境等)
示例:
lagou-service-page-dev.yml、lagou-service-page-test.yml、lagou-service-page-prod.yml
lagou-service-page-dev.properties 、lagou-service-page-test.properties 、lagou-service-page-prod.properties等等
比如我们上传一个lagou-service-page-dev.yml文件,具体内容如下:
mysal :
user : jetwu
person :
name : hahaha
内容最好是上面的格式,否则加载时,可能会出现问题,使得启动报错,正是因为可能,所以格式最好正确
具体如何上传,可以到73章博客,查找远程仓库的操作的内容,就知道怎么操作了
3:构建Config Server统一配置中心
新建SpringBoot工程lagou-cloud-config(是lagou-parent项目的子项目),引入依赖坐标(需要注册自己到Eureka)
< dependencies>
< dependency>
< groupId> org.springframework.cloud groupId>
< artifactId> spring-cloud-starter-netflix-eureka-client artifactId>
dependency>
< dependency>
< groupId> org.springframework.cloud groupId>
< artifactId> spring-cloud-config-server artifactId>
dependency>
dependencies>
然后再资源文件下,创建com.lagou.config.ConfigServerApplication类:
package com. lagou. config ;
import org. springframework. boot. SpringApplication ;
import org. springframework. boot. autoconfigure. SpringBootApplication ;
import org. springframework. cloud. client. discovery. EnableDiscoveryClient ;
import org. springframework. cloud. config. server. EnableConfigServer ;
@SpringBootApplication
@EnableDiscoveryClient
@EnableConfigServer
public class ConfigServerApplication {
public static void main ( String [ ] args) {
SpringApplication . run ( ConfigServerApplication . class , args) ;
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
创建 application.yml文件,且配置如下:
server :
port : 9400
eureka :
client :
service-url :
defaultZone : http: //LagouCloudEurekaServerA: 9200/eureka, http: //LagouCloudEurekaServerB: 9201/eureka
instance :
prefer-ip-address : true
instance-id : ${ spring.cloud.client.ip- address} : ${ spring.application.name} : ${ server.port} : @project.version@
spring :
application :
name : lagou- service- config
cloud :
config :
server :
git :
uri : https: //github.com/wobushigoudao/lagou- config.git
username : wobushigoudao
password : wurtr564
search-paths :
- lagou- config
label : master
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
测试:访问http://127.0.0.1:9400/master/lagou-service-page-dev.yml,他会去git(github)里面得到信息,放在对应的响应里面
这时可以发现得到了该文件的数据,页面显示如下:
mysal:
user: jetwu
person:
name: hahaha
代表去该master分支里进行读取lagou-service-page-dev.yml文件
首先我们在启动时,他会根据我们的地址以及用户名和密码进行连接
从而获取数据,但是该用户名和密码相当于直接的通过浏览器连接来获取的,不是git那样的不允许直接操作
构建Client客户端(在已有页面静态化微服务基础上):
案例实现:在lagou-service-page微服务中动态获取config server的配置信息
在该微服务里面加上如下依赖:
< dependency>
< groupId> org.springframework.cloud groupId>
< artifactId> spring-cloud-config-client artifactId>
dependency>
将该项目的application.yml修改为bootstrap.yml配置文件
bootstrap.yml是系统级别的,优先级比application.yml(当然也包括yaml和properties)高,应用启动时会检查这个配置文件
在这个配置文件中指定配置中心的服务地址,会自动拉取所有应用配置并且启用
主要是把与统一配置中心连接的配置信息放到bootstrap.yml,这样就可以实现,读取git上的配置了
相当于再外面读取application.yml之前,首先先得到文件
然后才能读取application.yml(当然是有三个文件的,properties,yaml,yml,这里我们就以yml为主)
这是后面的配置的作用,也是bootstrap.yml的作用(需要bootstrap.yml操作完毕)
一般情况下,bootstrap.yml是什么都没有的,所以也就相当于他application.yml直接的跳过了bootstrap.yml了
而这里因为有属性操作,所以需要等待他bootstrap.yml操作完,才可操作
这里提一下:一般来说,该文件是属于cloud操作的而不是boot操作的,他也有后缀,也符合boot的那三个后缀优先级
注意:需要统一读取的配置信息,从配置中心获取
bootstrap.yml(部分)
server :
port : 9100
Spring :
application :
name : lagou- service- page
datasource :
driver-class-name : com.mysql.jdbc.Driver
url : jdbc: mysql: //localhost: 3306/lagou?
useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username : root
password : 123456
cloud :
config :
name : lagou- service- page
profile : dev
label : master
uri : http: //localhost: 9400
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
我们回到lagou-service-page项目的(虽然前面说过了,这里提醒一下)controller包下,创建ConfigClientController类:
package com. lagou. page. controller ;
import org. springframework. beans. factory. annotation. Value ;
import org. springframework. web. bind. annotation. GetMapping ;
import org. springframework. web. bind. annotation. RequestMapping ;
import org. springframework. web. bind. annotation. RestController ;
@RestController
@RequestMapping ( "/config" )
public class ConfigClientController {
@Value ( "${mysal.user}" )
private String username;
@Value ( "${person.name}" )
private String name;
@GetMapping ( "/query" )
public String getConfiginfo ( ) {
return username+ ":" + name;
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
访问http://127.0.0.1:9100/config/query,发现得到了数据,即jetwu:hahaha
我们可以在启动日志里面看到如下:
发现,的确去读取信息了,至此操作读取配置文件信息操作完毕,即我们读取了配置中心的文件(虽然最终是github里面的)
但却是配置中心返回给我们的,而正是因为他们是读取github里面的信息
所以也基本可以操作实时的修改配置文件,而不用操作项目的重新部署,虽然主要操作自动加载的
因为只会加载一次的而没有自动加载的,主要是没有自动加载的,如Spring的一些配置
之前说明的只会加载一次的主要是这样没有自动加载的,且不是存放数据的配置(如sql)
那么怎么修改都是没有用的,因为不会加载
Config配置手动刷新:
我们需要进行某些操作,使得不用重启微服务,只需要手动的做一些其他的操作(访问一个地址/refresh)进行刷新
之后再访问即可
此时,客户端取到了配置中心的值,可是没有操作之前,当我们修改GitHub上面的值时
服务端(Config Server)能实时获取最新的值,但客户端(Config Client)读的是缓存,无法实时获取最新值
即他并没有再次的去访问加载,使得重新赋值(存放的地方赋值,也就是可以使得更新)
简单来说,他只是加载了启动时的信息,并没有再次的去加载,而第一次的加载的信息,就可以称为缓存信息,虽然也是在内存
Spring Cloud已经为我们解决了这个问题,那就是客户端使用post去触发refresh(中文意思:刷新),获取最新数据
而正是因为是我们使用,所以是手动的刷新,但也要注意:并不是所有的改变都会使用的,就如某些只会加载一次(比如端口)
基本上,只有可以重新赋值的,才能操作,加载一次的没有重新赋值的操作
那么如何操作呢,看如下:
到lagou-service-page项目里操作如下:
1:Client客户端添加依赖springboot-starter-actuator(记得添加)
2:Client客户端bootstrap.yml中添加配置(暴露通信端点)
management :
endpoints :
web :
exposure :
include : refresh
management :
endpoints :
web :
exposure :
include : "*"
回到ConfigClientController类修改部分如下:
@RestController
@RequestMapping ( "/config" )
@RefreshScope
public class ConfigClientController {
重启项目,接下来当我们修改配置文件时,先访问http://127.0.0.1:9100/config/query得到缓存数据进行验证
然后手动向Client客户端发起POST请求(注意:是post请求,之前的基本都是get请求,主要是因为@GetMapping)
即方法http://localhost:9100/actuator/refresh,使得刷新配置信息
即再次的加载一次,这时我们再次的访问http://127.0.0.1:9100/config/query,就不是缓存的数据(第一次的加载)了
如果不是post请求,那么错误信息如下,如操作get:
{
"timestamp" : "2022-09-04T03:12:37.044+0000" ,
"status" : 405 ,
"error" : "Method Not Allowed" ,
"message" : "Request method 'GET' not supported" ,
"trace" : "org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported\ r\ n\ tat org.springframework
很明显,若之前的查看信息的访问健康检查接口:http://localhost:9100/actuator/health也操作post请求
那么会有一样的类似错误,实际上是因为请求的规定
比如@GetMapping,如果是post请求他,那么就会出现上面的错误,这里了解即可
若是post请求(回答上面的如果不是post请求的后续),如果出现了如下:
[
"config.client.version" ,
"mysal.user"
]
代表已经更新了,这时,可以再次的访问,可以发现得到了更新的数据了
但是这有个问题,总不能所有的对应微服务都进行刷新吧,在成百上千个微服务中,这样的刷新太麻烦
当然,若写个脚本来进行刷新,也是可以的,但却并没有完全的解决
因为如果添加几个微服务或者删除几个微服务,那么脚本的刷新也需要修改,所以手动的刷新,不可避免的有一点麻烦问题
那么思考:可否使用广播机制,一次通知,处处生效,方便大范围配置自动刷新
答:可以,看如下
Config配置自动更新:
实现一次通知,处处生效
在微服务架构中,我们可以结合消息总线(Bus)实现分布式配置的自动更新(Spring Cloud Config + Spring Cloud Bus)
消息总线Bus:
所谓消息总线Bus,即我们经常会使用MQ消息代理构建一个共用的Topic
通过这个Topic连接各个微服务实例,MQ广播的消息会被所有在注册中心的微服务实例监听和消费
换言之就是通过一个主题连接各个微服务,打通脉络
Spring Cloud Bus(基于MQ的,支持RabbitMq/Kafka) 是Spring Cloud中的消息总线方案
Spring Cloud Config + Spring Cloud Bus 结合可以实现配置信息的自动更新
Spring Cloud Config + Spring Cloud Bus 实现自动刷新:
MQ消息代理,我们还选择使用RabbitMQ,ConfigServer和ConfigClient都添加都消息总线的支持以及与RabbitMq的连接信息
1:Config Server服务端和客户端都添加消息总线支持,这样他们就会操作同一个mq了
< dependency>
< groupId> org.springframework.cloud groupId>
< artifactId> spring-cloud-starter-bus-amqp artifactId>
dependency>
Config Server和客户端添加配置:
spring :
rabbitmq :
host : 192.168.164.128
port : 5672
username : laosun
password : 123123
Config Server微服务暴露端口(他没有这个配置,所以需要加上):
management :
endpoints :
web :
exposure :
include : bus- refresh
management :
endpoints :
web :
exposure :
include : "*"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
重启各个操作了上面的配置的服务,更改git的文件的配置之后,向配置中心服务端发送post请求
访问http://127.0.0.1:9400/actuator/bus-refresh,各个客户端配置即可自动刷新
至此,自动刷新操作完毕,实际上也算是手动的刷新,因为需要一次的手动的访问,而之所以说是自动
因为该手动的访问,使得自动的读取而已
那么如何操作只定向的更新,而不操作全部更新,访问如下:
http://localhost:9400/actuator/bus-refresh/lagou-service-page:9100,即后面指定对应的ip和端口
那么有个问题,后面的lagou-service-page:9100可以当成路径吗,答,可以,所以他能是过滤器,也可以是存在的路径
只要不是"/"分开,基本都可以访问,那么由于指定了lagou-service-page:9100这个
那么发送消息时,只会发送lagou-service-page:9100所对应的消息,而不会发现一系列的消息(名称加端口,自然是不同的信息的),从而使得,定向更新
自己测试即可
第二代 Spring Cloud 核心组件(Alibaba,SCA):
SpringCloud 是若干个框架的集合,包括 spring-cloud-config、spring-cloud-bus 等近 20 个子项目
提供了服务治理、服务网关、智能路由、负载均衡、断路器、监控跟踪、分布式消息队列、配置管理等领域的解决方案
Spring Cloud 通过 Spring Boot 风格的封装,屏蔽掉了复杂的配置和实现原理
最终给开发者留出了一套简单易懂、容易部署的分布式系统开发工具包
一般来说,Spring Cloud 包含很多组件,主要以 Netflix 开源为主
同 Spring Cloud 一样,Spring Cloud Alibaba 也是一套微服务解决方案(SCN虽然用的多)
包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务
依托 Spring Cloud Alibaba,您只需要添加一些注解和少量配置,就可以将 Spring Cloud 应用接入阿里微服务解决方案
通过阿里中间件来迅速搭建分布式应用系统
阿里开源组件:
Nacos:一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台
Sentinel:把流量作为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性
RocketMQ:开源的分布式消息系统,基于高可用分布式集群技术,提供低延时的、高可靠的消息发布与订阅服务
Dubbo:这个就不用多说了,在国内应用非常广泛的一款高性能 Java RPC 框架
Seata:阿里巴巴开源产品,一个易于使用的高性能微服务分布式事务解决方案
Arthas:开源的Java动态追踪工具,基于字节码增强技术,功能非常强大
我们再次看看前面的如下这个图(虽然并没有都说明出来):
阿里商业化组件:
作为一家商业公司,阿里巴巴推出 Spring Cloud Alibaba,很大程度上市希望通过抢占开发者生态,来帮助推广自家的云产品
所以在开源社区,夹带了不少私货,如前面图中的阿里云商业化组件(一般需要花钱才可使用)
其中整体易用性和稳定性还是很高的,一般比其他免费的性能更强大(因为收费,自然需要更强大呀)
对应的商业化组件如下:
Alibaba Cloud ACM:一款在分布式架构环境中对应用配置进行集中管理和推送的应用配置中心产品
Alibaba Cloud OSS:阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的云存储服务
Alibaba Cloud SchedulerX:阿里中间件团队开发的一款分布式任务调度产品
提供秒级、精准的定时(基于 Cron 表达式)任务调度服务
集成 Spring Cloud 组件:
Spring Cloud Alibaba 作为整套的微服务解决组件,只依靠目前阿里的开源组件是不够的
更多的是集成当前的社区组件,所以 Spring Cloud Alibaba 可以集成 Zuul,GateWay等网关组件
也可继承Ribbon、OpenFeign等组件
Nacos 服务注册和配置中心:
Nacos 介绍:
Nacos (Dynamic Naming and Configuration Service)是阿里巴巴开源的一个针对微服务架构中服务发现、配置管理和服务管理平台
Nacos就是注册中心+配置中心的组合(Nacos=Eureka + Config + Bus)
官网:https://nacos.io
下载地址:https://github.com/alibaba/Nacos
Nacos功能特性:
服务发现与健康检查
动态配置管理
动态DNS服务
服务和元数据管理,管理平台的角度,nacos也有一个ui页面,可以看到注册的服务及其实例信息(元数据信息)等
对动态的服务权重调整、动态服务优雅下线,都可以去做
Nacos 单例服务部署:
下载解压安装包,执行命令启动(这里使用最近比较稳定的版本 nacos-server-1.2.0.tar.gz):
下载地址:
链接:https://pan.baidu.com/s/1baEjvlXthlzDNLbZuDyl6w
提取码:alsk
对应的目录如下:
这里包括启动命令,一般不同的操作系统,执行不同的命令,如下:
linux/mac:sh
startup.sh -m standalone
windows:cmd
startup.cmd
我们双击startup.cmd运行(一般需要我们有JDK的环境变量),出现如下:
关闭窗口也就是关闭nacos,nacos也是一个项目(一般是java项目,即一般是Spring boot工程的项目,所以可以直接的访问)
访问nacos控制台:
http://127.0.0.1:8848/nacos/#/login 或者 http://127.0.0.1:8848/nacos/index.html 或者 http://127.0.0.1:8848/nacos
默认端口8848,账号和密码 nacos/nacos
我们可以发现,访问前面的一个地址,需要用户名和密码,后面的访问不需要,很明显
用户名和密码实际上也是多次一举(可能在某些时候是有作用的)
微服务注册到Nacos:
在父pom中引入SCA依赖:
< dependencyManagement>
< dependencies>
< 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>
dependencies>
dependencyManagement>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
由于他操作配置中心和服务注册,所以对应的注册中心和配置中心的服务器可以停掉了
在商品微服务和静态化微服务中引入nacos客户端依赖,必须删除eureka-client依赖(否则可能因为冲突造成启动失败):
< dependency>
< groupId> com.alibaba.cloud groupId>
< artifactId> spring-cloud-starter-alibaba-nacos-discovery artifactId>
dependency>
将原来的bootstrap.yml文件修改成application.yml文件
并将对应的需要bootstrap.yml文件的配置和与他相关的配置(如rabbitmq)
以及依赖和使用了依赖的类删除(当然可以直接注释也行)
如spring-cloud-starter-bus-amqp依赖和他所对应的ConfigClientController类,需要删除
实际上只需要删除ConfigClientController类即可,否则可能不会注册,访问失败的,因为没有对应的注入值
然后将商品微服务和静态化微服务的eureka的配置也删除(当然可以直接注释也行)
这样他们的原来的注册中心操作和配置中心操作就没有了
那么可以不删除对应的配置中心吗,答,由于eureka-client依赖冲突,所以基本不能操作对应的配置,所以基本也就不可用
再在商品微服务和静态化微服务的配置中添加如下信息:
spring :
cloud :
nacos :
discovery :
server-addr : 127.0.0.1: 8848
重新启动商品微服务和静态化微服务,就算先启动一般也会自动的连接上的nacos的
这时可以停掉对应的配置中心服务器和注册中心服务器和网关的服务器了
可能有对应的根据时间自动连接吧,虽然并没有日志出现
观察nacos控制台:
具体介绍:
我们可以创建服务(虽然是空的),然后点击隐藏空服务,打开即可看到空的服务了,空服务一般是用来被真的服务顶替了
即他也只是显示的作用
具体服务详情:
编辑服务用来修改这些详情的(下面的集群显示操作他的编辑进行修改)
权重一般操作负载均衡,权重大,一般访问他越多,但通常需要根据策略了
比如操作轮询时,权重并没有任何关系,但可能因为权重的存在使得策略自动变化,比如nginx中,设置后,就操作权重的策略了
一般默认的框架中,权重通常是1,且默认轮询的策略(虽然上面的ribbon不是)
但通常设置权重或者权重不是1,则会操作权重的策略,当然也并不是所以的框架都有操作权重的,比如ribbon(可能有)
保护阈值:可以设置为0-1之间的浮点数
在创建服务时,虽然也能设置为其他的数,负数也可,是浮点数,若是字母会创建不了,有个提示出现)
最好是0-1(不包括0和1),当然不能是-1-1,这样的计算表达式,也会创建不了,出现提示(一样的提示)
但现在的创建空服务并没有什么实际意义,所以这里我们就不创建或者删除
它其实是一个比例值(当前服务健康实例数/当前服务总实例数)
注意:若删除有服务的(不是空),那么一般需要重启对应的服务,使得再次注册了
上面的下线,并不会使得服务停止,只是不提供该服务的信息了,即操作对应的请求的,相当于他没有注册
这时只能访问其他的没有下线的了,但若都下线了,也就相当于删除服务了,那么可能会访问不了,这时可能就会操作熔断了
保护阈值的操作场景:
一般流程下,nacos是服务注册中心,服务消费者要从nacos获取某一个服务的可用实例信息
对于服务实例有健康/不健康状态(下线通常也可以看成不健康的状态)之分
nacos在返回给消费者实例信息的时候,会返回健康实例
这个时候在一些高并发、大流量场景下会存在一定的问题
如果服务A有100个实例,98个实例都不健康了,只有2个实例是健康的,如果nacos只返回这两个健康实例的信息的话
那么后续消费者的请求将全部被分配到这两个实例,流量洪峰到来
2个健康的实例也扛不住了,整个服务A 就扛不住,上游的微服务也会导致崩溃,产生雪崩效应
所以保护阈值的意义在于:
当服务A健康实例数/总实例数 < 保护阈值 的时候(有些时候可能等于也算,可能可以通过设置或者版本的原因)
说明健康实例真的不多了,这个时候保护阈值会被触发(状态true)
nacos将会把该服务所有的实例信息(健康的+不健康的)全部提供给消费者
下线的可能也会给(因为他通常也看成不健康的,从不提供变成了提供)
消费者可能访问到不健康的实例,请求失败,但这样也比造成雪崩要好,牺牲了一些请求,保证了整个系统的一个可用
总不能都没有可用吧,虽然不健康的可能更加不健康了,但总是可以有可用的,而不是基本没有
基本上保存阈值调整保存后,基本是立即生效,而正是因为这样,所以nacos也是非常方便的
注意:阿里内部在使用nacos的时候,也经常调整这个保护阈值参数
负载均衡:
Nacos客户端引入的时候,会关联引入Ribbon的依赖包,就如我们使用OpenFiegn的时候也会引入Ribbon的依赖,虽然熔断器也会
但不管是什么,只要有即可
所以Ribbon原来的配置可以不用变,而由于熔断器也在,所以包括Hystrix都按原来方式进行配置即可
这时我们直接访问http://127.0.0.1:9100/page/loadProductServicePort,我们通过后台也可以看出,的确负载均衡了
所以说,nacos基本只是替换了配置中心(后面有操作)和服务注册中心
其他的基本该是什么就是什么,差不多都是一样的整合的,甚至对应的代码都不需要改变,这里要注意
Nacos 数据模型(领域模型):
Namespace命名空间、Group分组、集群这些都是为了进行归类管理,把服务和配置文件进行归类
归类之后就可以实现一定的效果,比如隔离
比如,对于服务来说,不同命名空间中的服务不能够互相访问调用
Namespace在外层的(互相隔离),是一个命名空间,在一个命名空间中,可以定义多个Group
各个分组之间是相互隔离的,在一个group可以定义多个service/dataid(一般也是隔离的)
Namespace:命名空间,对不同的环境进行隔离,比如隔离开发环境、测试环境和生产环境
Group:分组,将若干个服务或者若干个配置集归为一组,通常习惯一个系统归为一个组(拉勾招聘、拉勾猎头、拉勾教育)
Service:某一个服务,比如商品微服务
DataId:配置集或者可以认为是一个配置文件(或者说配置文件的id)
通常情况下,一个Service就是对应一个DataId(Id中的"I"的小写的是i)
Namespace + Group + Service 如同 Maven 中的GAV坐标,GAV坐标是为了锁定Jar,而这里是为了锁定服务
Namespace + Group + DataId 如同 Maven 中的GAV坐标,GAV坐标是为了锁定Jar,而这里是为了锁定配置文件
最佳实践:
Nacos抽象出了Namespace、Group、Service、DataId等概念,具体代表什么取决于怎么用(非常灵活),推荐用法如下
Nacos 配置中心:
之前:Spring Cloud Config + Bus(配置的自动更新)
1:Github 上添加配置文件
2:创建Config Server 配置中心—>从Github上去下载配置信息
3:具体的微服务(最终使用配置信息的)中配置Config Client—> ConfigServer获取配置信息
有Nacos之后,分布式配置就简单很多
Github不需要了(配置信息直接配置在Nacos server中),Bus也不需要了(依然可以完成动态刷 新)
接下来:
1:去Nacos server中添加配置信息
2:改造具体的微服务,使其成为Nacos Config Client,能够从Nacos Server中获取到配置信息
Nacos Server添加配置:
首先点击命名空间,我们创建一个命名空间:
可以看到,出现了对应的命名空间,在public后面多出了三个
接下来我们回到配置列表,点击 " + " 加号(在public里面的,若要其他的命名空间的话,可以点击其他的命名空间然后创建即可
这里与之前的配置文件不同的是,可以指定后缀,但要注意,这只是创建文件,而创建文件自然是可以随便创建的
就如git的创建一样,只是对应的配置可能只会操作固定的后缀,就如前面的一样的示例(一般是yml和properties)
后面会介绍这里所要的后缀的
其中lagou前面的"-"不用写,他只是一个标识,写完后,会加上的,接下来我们点击发布,在弹出的框框中,点击确定
注意他并不会跳转,这是他的一个不足的点,我们返回即可
这时,出现如下:
接下来我们在微服务中开启 Nacos 配置管理
在静态化微服务里添加依赖:
< dependency>
< groupId> com.alibaba.cloud groupId>
< artifactId> spring-cloud-starter-alibaba-nacos-config artifactId>
dependency>
微服务中如何锁定 Nacos Server 中的配置文件(dataId)
通过 Namespace + Group + dataId 来锁定配置文件,Namespace不指定就默认public,Group不 指定就默认 DEFAULT_GROUP
dataId 的完整格式如下:
${prefix} -${spring.profile.active} . ${file-extension}
prefix 默认为 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix 来配置
spring.profile.active 即为当前环境对应的 profile
注意:当 spring.profile.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成:
${prefix} . ${file-extension}
file-exetension 为配置内容的数据格式,可以通过配置项
spring.cloud.nacos.config.file-extension 来配置,目前只支持 properties 和 yaml 类 型
所以就算前面的配置文件中创建的类型有多种,但是可能有些也操作不了
spring :
cloud :
nacos :
discovery :
server-addr : 127.0.0.1: 8848
config :
server-addr : 127.0.0.1: 8848
group : DEFAULT_GROUP
file-extension : yaml
接下来通过静态化微服务获取配置信息
首先将application.yml文件修改成bootstrap.yml文件,使得操作对应的配置
然后在controller包下,创建ConfigClientController类(之前没有删除的话,就需要创建):
package com. lagou. page. controller ;
import org. springframework. beans. factory. annotation. Value ;
import org. springframework. cloud. context. config. annotation. RefreshScope ;
import org. springframework. web. bind. annotation. GetMapping ;
import org. springframework. web. bind. annotation. RequestMapping ;
import org. springframework. web. bind. annotation. RestController ;
@RestController
@RequestMapping ( "/config" )
@RefreshScope
public class ConfigClientController {
@Value ( "${lagou.message}" )
private String message;
@GetMapping ( "/query" )
public String getRemoteConfig ( ) {
return message;
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
至此,我们访问http://localhost:9100/config/query后,若出现数据,则操作成功
接下来我们修改文件信息,然后发布,确认发布,继续访问,发现更新了,注意,他是直接的获取,所以没有缓存一说
虽然他也是客户端,但跟前面的操作一样,那么后面的refresh: false可以使得会操作缓存吗
答:还是不会,正如这里说的,是直接的获取,所以设置refresh: false与主获取没有关系,但与扩展有关
那么一个微服务希望从配置中心Nacos server中获取多个dataId的配置信息,这样可以操作吗
答:可以的,虽然我们的主指定,只能是一个,但可以操作扩展配置,扩展多个dataId
添加如下:
回到ConfigClientController类进行修改:
package com. lagou. page. controller ;
import org. springframework. beans. factory. annotation. Value ;
import org. springframework. cloud. context. config. annotation. RefreshScope ;
import org. springframework. web. bind. annotation. GetMapping ;
import org. springframework. web. bind. annotation. RequestMapping ;
import org. springframework. web. bind. annotation. RestController ;
@RestController
@RequestMapping ( "/config" )
@RefreshScope
public class ConfigClientController {
@Value ( "${lagou.message}" )
private String message;
@Value ( "${pagea}" )
private String pagea;
@Value ( "${pageb}" )
private String pageb;
@GetMapping ( "/query" )
public String getRemoteConfig ( ) {
return message+ "-" + pagea+ "-" + pageb;
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
对应的配置:
spring :
cloud :
nacos :
discovery :
server-addr : 127.0.0.1: 8848
config :
server-addr : 127.0.0.1: 8848
group : DEFAULT_GROUP
file-extension : yaml
ext- config[ 0 ] :
data-id : pagea.yaml
group : DEFAULT_GROUP
refresh : true
ext- config[ 1 ] :
data-id : pageb.yaml
group : DEFAULT_GROUP
refresh : true
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
这时我们重启,继续访问http://localhost:9100/config/query,发现有对应的数据了
SCA Sentinel 分布式系统的流量防卫兵 :
Sentinel 介绍:
Sentinel是一个面向云原生微服务的流量控制、熔断降级组件
替代Hystrix,针对问题:服务雪崩、服务降级、服务熔断、服务限流
Hystrix:
服务消费者(静态化微服务)—>调用服务提供者(商品微服务)
在调用方引入Hystrix
1:自己搭建监控平台 dashboard(虽然我们并没有搭建,但他也只是显示的作用,与程序无关,所以搭不搭建无所谓)
好像前面也没有具体说明过
2:没有提供UI界面进行服务熔断、服务降级等配置(使用的是@HystrixCommand参数进行设置)
有一定的代码入侵,即代码太多了
Sentinel:
1:独立可部署Dashboard/控制台组件(其实就是一个jar文件,直接运行即可)
一般是Spring boot工程的项目,所以可以直接的访问,一般可以直接访问的jar包,通常是Spring boot工程的项目
2:减少代码开发,通过UI界面配置即可完成细粒度控制
Sentinel 分为两个部分:
核心库:(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境
同时对 Dubbo / Spring Cloud 等框架也有较好的支持
控制台:(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等 应用容器
Sentinel 具有以下特征:
丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景
例如秒杀(即 突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用 应用等
完备的实时监控:Sentinel 同时提供实时的监控功能
您可以在控制台中看到接入应用的单台机器 秒级数据,甚至 500 台以下规模的集群的汇总运行情况
广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo的整合
您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel
完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口
您可以通过实现扩展接口来快 速地定制逻辑,例如定制规则管理、适配动态数据源等
Sentinel 的主要特性:
Sentinel 的开源生态:
Sentinel 部署:
下载地址:https://github.com/alibaba/Sentinel/releases,我们找到v1.7.1的版本,即1.7.1的版本,然后下载
若不想下载,可以直接取出我下载好的,地址如下:
链接:https://pan.baidu.com/s/18lfEh29-uaCCW_gwDwtAvg
提取码:alsk
我们可以到cmd命令里到该目录下访问,或者右键,点击独有的窗口(dos的加强版窗口,样式好看很多),然后访问
启动:java -jar sentinel-dashboard-1.7.1.jar & ,注意,加上&代表是守护的进程
即关闭窗口,他也不会关闭,但通常还是会关闭,可能现在不可用了,大概是为了防止出现多个守护进程造成资源持续占用吧
用户名/密码:sentinel/sentinel
访问http://localhost:8080/即可(注意:可能在一些浏览器里不会显示),一般使用谷歌浏览器可以显示
访问后出现如下:
输入用户名/密码:sentinel/sentinel
然后出现如下(点击首页):
服务改造:
在我们已有的业务场景中,“静态化微服务"调用了"商品微服务”
我们在静态化微服务进行的熔断降 级等控制,那么接下来我们改造静态化微服务,引入Sentinel核心包
为了不污染之前的代码,复制一个页面静态化微服务 ,名称为lagou-service-page-9101(前面操作了复制,这里就不多说了):
pom.xml引入依赖:
< dependency>
< groupId> com.alibaba.cloud groupId>
< artifactId> spring-cloud-starter-alibaba-sentinel artifactId>
dependency>
application.yml修改(配置sentinel dashboard,暴露断点依然要有,删除原有hystrix配置,删除 原有OpenFeign的降级配置)
虽然该application.yml文件变成了bootstrap.yml(不用修改回来):
server :
port : 9101
Spring :
main :
allow-bean-definition-overriding : true
application :
name : lagou- service- page
datasource :
driver-class-name : com.mysql.jdbc.Driver
url : jdbc: mysql: //localhost: 3306/lagou?
useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username : root
password : 123456
cloud :
nacos :
discovery :
server-addr : 127.0.0.1: 8848
config :
server-addr : 127.0.0.1: 8848
group : DEFAULT_GROUP
file-extension : yaml
sentinel :
transport :
dashboard : 127.0.0.1: 8080
port : 8719
Management :
endpoints :
web :
exposure :
include : "*"
endpoint :
health :
show-details : always
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
在这之前,我们要将RestTemplate类的实例注释掉,那么为什么要注释掉呢
答:因为我们是复制过来的(不是复制过来的基本不会出现这样的情况),一般RestTemplate类并不是当前项目的注入,他的注入会根据项目的具体原名注入
且会与maven打交道(因为已经打过一次,那么就以那一次为主,所以原项目不会出现这样的情况)
所以,这时候,若我们复制过来的项目中有RestTemplate类的实例,那么会在启动时,出现已被注入的信息
当然,在其他的组件或者实例中,可能也会出现这样的情况(基本对于复制的来说),所以这里提醒一下
那么如何解决这样的情况呢:
第一,就是注释掉对应的实例操作或者依赖操作
第二,看如下:
Spring :
main :
allow-bean-definition-overriding : true
application :
name : lagou- service- page
上述配置之后,启动静态化微服务,使用 Sentinel 监控静态化微服务
此时我们发现控制台没有任何变化,因为懒加载
懒加载:在满足一定条件时,才会加载资源,这里是发送一次请求,无论是否失败都可以
即我们只需要发起一次请求触发即可,我们访问http://127.0.0.1:9101/page/loadProductServicePort
上面就是访问过后,出现的(也有对应的信息)
只要现在只要不重启sentinel,那么他会一直存在,这时懒加载只针对于他的对应信息了
重启sentinel后,对应的项目需要重启,使得连接,否则就算访问也不会出现
因为不重启的话,以为原来的还在,实际上不在了,注意:懒加载可能会有延迟,多次访问即可,或者继续重启
当然,有些配置(该配置了解即可)可能会使得不会加载,比如(可能会加载,但我测试时不会加载,参考即可):
sentinel :
filter :
enabled : false
部分点击(实时监控和簇点链路):
一般需要访问才会出现,且过些时间可能会消失,这时需要继续访问
刷新(一般是自动的刷新,如时刷,即一定时间内的刷新)可能有点延迟,这是自然的,总不能一瞬间吧
注意:上面的QPS在这里代表用户发出请求到服务器做出响应成功的次数,通常是根据一秒种的时间来显示的次数
所以我们也称QPS为:每秒通过的请求数(该秒只针对于对应的一秒钟,这里若明白,那么就知道为什么上面图中,只有1.0了)
这里因为都有延迟,所以基本都是一次
假设我们发送十个请求(等待请求完毕):
根据每秒通过的请求数,可以看到,后面的图中,有4个1.0,代表有4次成功,我们继续等待,一般会刷新的
因为总不能全部将十次请求都显示完毕吧,因为窗口只有这么大
一般若操作时间少,表格一般可以看到上次的请求结果,这一点在后面的测试显示的次数可以自己看出来
为了测试是否是根据一秒种的时间来显示的次数,将原来的延迟代码注释,得到的结果如下:
发现的确是根据一秒种的时间来显示的次数的
不同的视图,上面是树状视图,下面是列表视图
Sentinel 关键概念 :
建议操作规则时,在簇点链路里点击,这样就会自动的传递资源名称了
而不用手写的(虽然不对也没关系,因为是大多数是匹配的意思)
Sentinel 流量规则(流控规则)模块:
系统并发能力有限,比如系统A的QPS支持1个,如果太多请求过来,那么A就应该进行流量控制 了,比如其他请求直接拒绝
现在开始,我们点击流控并打开高级选项:
出现如下:
上面的介绍:
新增:点击后,退出框框,然后自动到流控规则界面
新增并继续添加:点击后,退出框框,还是原来的页面,即只退出框框
假设添加了,如我们将单机阈值设置为1,流控规则界面显示如下:
虽然我们可以点击这里的新增,但是资源名需要自己写,所以为了方便或者对应,一般在簇点链路里点击
但无论是簇点链路里点击还是这里的点击,资源名都可以随便修改或者写上
接下来,说明那些属性值的作用:
资源名:默认请求路径
针对来源:Sentinel可以针对调用者进行限流,如填写微服务名称,默认default(不区分来源)
阈值类型/单机阈值:
QPS:(每秒钟请求数量,一般是成功的数量)当调用该资源的QPS达到阈值时进行限流
如单机阈值设置为100
那么每秒中(注意:这是中,而不是钟,虽然意思是一样的,写成钟也可,这里写成中)最大只能通过100个请求,包括100
即不能超过100个请求,如果多了,则操作流控模式
若流控模式是直接,那么直接失败,代表自己,然后操作流控效果,若流控效果是快速失败
则抛出异常,该异常使得准备要请求对应的方法时,直接被终止了,即没有我们设置的打印信息了
且该异常,通常不会显示在服务器,一般会将对应的信息返回给页面
实际上流控效果可能也会使得操作阈值,因为他们虽然是这样的顺序,但实际上也只是正常的顺序而已
因为对应的异常自然是在后面
即他们实际上只是设置关系,在初始化之前,会进行设置的,如后面的预热,所以他们实际上并没有顺序之分
我们可以测试,测试上面的哪个单机阈值设置为1且其余不变的,因为添加了,所以我们可以直接测试了
现在我们多次请求一下,争取在一秒中请求2次或者2次以上
由于这里我们去除了延迟代码,所以只要我们点击够快即可
如果出现如下:
就代表的确是出现了异常,即操作了限流,其中Blocked by Sentinel (flow limiting),可以理解为,让流量防卫兵的流控进行阻塞了
至此QPS操作成功
线程数:当调用该资源的线程数达到阈值的时候进行限流
主要是为了防止线程处理请求的时候,如果说业务逻辑 执行时间很长,流量洪峰来临时,会耗费很多线程资源
这些线程资源会堆积,最终可能造成服务不可用,进一步上游服务不可用,最终可能服务雪崩这样的问题
现在进行测试,首先,我们编辑之前的流控规则,将QPS修改成线程数(一般线程数不操作流控效果),保存后
我们将9101端口项目里面的loadProductServicePort路径方法里的调用方法之前的部分地方
加上休眠,设置为5000 ms,即5秒钟的休眠
现在重启项目,接下来在postman里面,打开两个可以请求的窗口
其中一个执行时,另外一个立即执行,若出现Blocked by Sentinel (flow limiting),代表操作成功
最后注意一点,一般若后台出现了什么问题
sentinel除了实时监控外(他只是记录数据,不会与项目有关联,所以只会由他的自己不显示)
其他的一般都不会显示,因为与项目有关,有非常小的延迟,相当于立即不显示
没有这样说明的延时,通常会延时一段时间,而不是立即
需要再次的访问才可显示,且对应的规则一般都会删除,因为并没有操作持久化
其中显示一般只要访问了,就会显示(虽然有延迟),无论是否停了多长时间和是否访问失败
但该次显示的访问不会计算到对应的流控规则(无论是什么规则)里面
但也只有该次,其他没有显示的是不会的,即只有第一次的访问不会,所以如果设置了50秒的停顿
那么访问显示后,第二次的访问不会出现异常抛出,虽然基本只有线程数可以测试到
是否集群:是否集群限流
流控模式:
直接:资源调用达到限流条件时,直接限流
关联:关联的资源调用达到阈值的时候限流自己的资源,而不是关联的资源
比如说如下:
点击关联会出现关联资源的属性,具体的理解为,只要B这个资源,触发了这里的流控,即每秒中的访问达到2次或者2次以上
那么就将A资源操作流控,即流控规则的操作,这里自然是快速失败,即抛出异常
与直接不同的是,直接代表自己的资源,而关联代表关联的资源,对应的触发流控效果的对象不同
所以流控模式也可以称为满足对应条件,而流控模式上面的属性配置就是条件的设置
如"直接"是满足自己触发流控,然后操作效果
而"关联"是关联触发流控,然后触发效果,但他们相同的是,效果的操作对象都是自己的资源,而不是关联的
我们可以将关联简称为,你出问题,我背锅,具体测试,在后面中的"流控模式之关联限流"中进行
链路:只记录指定链路上的流量,后面会说明他的测试的(虽然并没有实际操作说明),在"流控模式之链路限流"中进行
流控效果:
快速失败:直接失败,抛出异常,前面的多次测试,都是这个
Warm Up:根据冷加载因子(默认为3)的值,从阈值/冷加载因子,经过预热时长,才达到设置的QPS阈值
具体意思可以说明一下:
当你操作单机阈值为10时,该值对应的预热时长是11(代表秒,即11秒),那么在11秒内,对应的单机阈值是10/3
取整数,那么就是3,而10/3的这个3就是冷加载因子,所以在一开始的11秒内,我们的单机阈值是3,之后才是10
他相当于规定一个空间,具体测试在后面的"流控效果之Warm up"
主要针对于项目刚刚上线的时刻,因为一般情况下
第一次是没有缓存的,即操作数据库,后续才会操作缓存,我们可以先操作少的请求,先缓存再说
而不是然多的请求操作了数据库,这就是该预热的作用之一,当然,还有其他的作用
排队等待:匀速排队,让请求匀速通过,阈值类型必须设置为QPS,否则无效
因为线程数是不操作流控效果的,一般默认是快速失败,具体测试在"流控效果之排队等待"
具体的解释是:假设对应的QPS设置为1,原来是代表每秒中最大只能是1个请求,否则操作流控模式,而当我们设置了这个时
会根据时间来划分,即如果是1,那么一秒中最大执行一次,如果是2,那么一秒钟最大执行两次
如果是0.5(这里与没有设置的不同),那么每两秒最大执行一次,其中如果超过了最大,那么多余的会排队
而不是操作流控模式,且这些排队的,会在后面的秒中进行访问,如果设置的超时时间
在排队访问时,他还没有在超时的时间内访问到,那么就抛出异常(显示给页面,即前端)
流控模式之关联限流:
关联的资源调用达到阈值时候限流自己,比如用户注册接口,需要调用身份证校验接口(往往身份证校验接口)
如果身份证校验接口请求达到阈值,使用关联,可以对用户注册接口进行限流
简单说,就是第三方接口出现大的延时时,将我们进行限流,通常操作第三方接口时使用这个模式
现在我们进行测试,我们到9101端口项目里面的controller包下,创建UserController类:
package com. lagou. page. controller ;
import org. springframework. web. bind. annotation. GetMapping ;
import org. springframework. web. bind. annotation. RequestMapping ;
import org. springframework. web. bind. annotation. RestController ;
@RestController
@RequestMapping ( "/user" )
public class UserController {
@GetMapping ( "/register" )
public String register ( ) {
System . out. println ( "Register success!" ) ;
return "Register success!" ;
}
@GetMapping ( "/validateID" )
public String validateID ( ) {
System . out. println ( "validateID" ) ;
return "ValidateID success!" ;
}
}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
现在我们创建对应的流控规则:
在这之前,我们需要访问一次,才能够新增,由此看来,第一次的访问,是为了初始化的,也顺便操作了显示
接下来,我们操作访问http://127.0.0.1:9101/user/validateID设置访问100次,间隔100ms(前面有操作过多次的访问)
然后在他访问时,我们访问http://127.0.0.1:9101/user/register,可以发现,出现了Blocked by Sentinel (flow limiting),即操作成功
但是如果没有访问了,即100次访问完成,那么也就正常了
所以我们可以发现,只要对方进行了操作限流,访问就会出现Blocked by Sentinel (flow limiting)
那么对方的限流有效时间是多少,答,对应的1秒钟的时间如果出现限流,那么该一秒就是有效时间
否则不可能你出现一次限流,那么后面的所有的http://127.0.0.1:9101/user/register都不能访问吧,所以有有效时间
但实际上也可以说是满足一定条件,就会操作异常,就如前面说的,流控模式就是操作对应的满足条件,然后操作对应的流控效果
但如果不满足,自然不会操作限流,就如前面的100次访问完毕
但是这里需要提一点,该效果,只是准备出现(满足条件后,操作规则定义的而已)
而让他出现,自然需要对应的操作的限流资源进行访问才可出现
就如上面的访问http://127.0.0.1:9101/user/register,出现了Blocked by Sentinel (flow limiting)
流控模式之链路限流:
链路指的是请求链路(比如调用链:A–>B–>C,D–>E–>C),同一个资源被不同的调用链路调用,且并不是非要按照顺序
如D–>E–>C,这是肯定的,因为调用本来就部分先后,也只是我们来设置逻辑顺序而已
链路模式下会控制该资源所在的调用链路入口的流量,需要在规则中配置入口资源,即该调用链路 入口的上下文名称
一棵典型的调用树如下图所示:
上图中来自入口 Entrance1 和 Entrance2 的请求都调用到了资源 NodeA ,Sentinel 允许只根据某个 调用入口的统计信息对资源限流
比如链路模式下设置入口资源为 Entrance1 来表示只有从入口
Entrance1 的调用才会记录到 NodeA 的限流统计当中,而不关心经 Entrance2 到来的调用
通常情况下,点击链路会出现入口资源的属性,且当我们访问入口资源的值并触发条件时
就会报异常(如果没有,则去百度搜索解决方案即可)
该异常由入口资源触发,而不是自己的资源,与前面的操作不同,这里操作的不是自己,而正是因为这样
使得,不同的链路是互不影响的,因为我们只针对于入口操作限流
虽然不同的链路,入口基本不同,但可能会有相同的入口
如中间不同(比如根据静态变量的累加到一定程度,变成其他调用等等)
但由于我们只看入口,所以这种情况虽然特殊,但却也是同一个入口资源,因为入口相同
比如这里:https://blog.csdn.net/chyh741/article/details/123909064
但是在高版本中,可能失效了,具体的说明这里非常难说
我查看了一下对应的网上资料,这个资料的应该很详细:
https://github.com/alibaba/Sentinel/issues/1024
一般需要官方解决,所以就不说明了,可以使用低版本的
流控效果之Warm up:
当系统长期处于空闲的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮,比如电商网站的秒杀模块
通过 Warm Up 模式(预热模式),让通过的流量缓慢增加,经过设置的预热时间以后,到达系统 处理请求速率的设定值
Warm Up 模式默认会从设置的 QPS 阈值的 1/3 开始慢慢往上增加至 QPS 设置值
点击Warm Up,会出现预热时长的属性
由于这里会改变阈值(流控效果是准备的),所以他也使得快速失败和设置阈值一起,虽然有设置阈值的能力
这时,我们可以多次访问http://127.0.0.1:9101/user/register,一直访问,可以看到
在前期,有时候可以看到异常信息,在之后就没有了,注意:该预热时长,一般从第一次的访问开始计时的
预热时长过后,就会设置回来,即没有操作1/3的阈值了,也就回来了,
流控效果之排队等待:
排队等待模式下会严格控制请求通过的间隔时间,即请求会匀速通过,允许部分请求排队等待
通常用于消息队列削峰填谷等场景,需设置具体的超时时间,当计算的等待时间超过超时时间时请求就会 被拒绝
很多流量过来了,并不是直接拒绝请求,而是请求进行排队,一个一个匀速通过(处理)
请求能 等就等着被处理,不能等(等待时间>超时时间)就会被拒绝
例如,QPS 配置为 5,则代表请求每 200 ms 才能通过一个(设置这个就变成匀速了,否则我们想怎么访问就怎么访问)
多出的请求将排队等待通过
超时时间代表最大排队时间,超出最大排队时间的请求将会直接被拒绝
排队等待模式下,QPS 设置值不要超过 1000(请求间隔 1 ms),即最大是1000,大概是对应的匀速最小是1ms
如果超过了,一般会报错,那么为什么不能超过呢,主要是为了减少异步的处理
但实际上会报错的,这里了解即可
现在进行测试,回到9101项目的UserController类:
修改如下:
@GetMapping ( "/validateID" )
public String validateID ( ) {
String format = new SimpleDateFormat ( "yyyy/MM/dd mm:ss" ) . format ( new Date ( ) ) ;
System . out. println ( format) ;
System . out. println ( "validateID" ) ;
return "ValidateID success!" ;
}
对应的流控如下:
点击排队等待会出现超时时间(毫秒单位,即上面的是1秒)
接下来我们操作多次的访问,看看打印,发现,每秒只打印一次,至此操作大致成功
可能该1秒不会出现异常出来(可能超时时间会延迟一点点)
我们可以设置10毫秒,这时我们发现,多次访问时,一般会出现异常信息了
注意:该异常是说明页面的打印异常,而不是服务器的
注意:到这里,基本说明完了,但有个问题,如果单机阈值是小于1的,那么代表什么呢(前面的匀速说明过了,这里提一下):
这里需要分情况考虑,如果没有操作匀速,那么默认是一直是限流的,无论是否访问,因为我们的访问
最少也是1个请求,所以如果是小于1的,那么就相当于默认一直超过了,即操作限流了
就算是没有访问也是如此(具体结论在于操作关联时,设置为0.5,然后我们不执行关联资源,也出现了限流)
那么如果是匀速,那么对于匀速来说,该意思是变成的时间操作
即每两秒最大执行一次,超过的排队,自己通过测试发现的确如此
至此流控规则操作完毕
Sentinel 降级规则模块:
流控是对外部来的大流量进行控制访问,与熔断降级不同的是,他是直接操作外部问题的访问的
而熔断降级的视角是对内部问题进行处理
Sentinel 降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高)
对这 个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误
当资源被降级后, 在接下来的降级时间窗口之内,对该资源的调用都自动熔断,这里的降级其实是Hystrix中的熔断
策略:
Sentinel不会像Hystrix那样放过一个请求尝试自我修复,就是明明确确按照时间窗口来
熔断触发 后,时间窗口内拒绝请求,时间窗口后就恢复
当然,还是建议在簇点链路里进行点击,方便一些
这里的时间的窗口与前面说过的熔断的自我修复机制不同,前面的是每隔5秒操作一次,只要有对于的正确访问,那么不操作熔断
而这里只过5秒,即5秒后,就不操作熔断了,当然,这些都只是熔断的规则而已,规则有很多,自然不一定是相同的
其中熔断的规则一般是降级的规则,因为熔断也只是断的意思,他们的确都断了,那么后续一般是降级的规则
当然熔断一般也会加入该规则里面,比如说自我修复机制
当然,你也可以称为熔断的规则
RT(平均响应时间 ):
只要有5个或者5个以上的请求,我们就会操作一次平均响应时间,一般只要5个即可
操作之后,下次的访问操作熔断了,如果熔断了,那么下次的访问,也就是会出现异常信息,即Blocked by Sentinel (flow limiting)
但在高并发下,可能会出现5个以上
1s 内持续 进入 5 个或者5 个以上的请求(注意:最少是5个,否则不会操作熔断)
注意这里不是1秒中有五个或者5 个以上的请求,而是间隔
那么为什么这样说呢:即我们在1秒的间隔内,连续的访问5次或者5次以上,即每次访问的之间的间隔是1秒内的间隔
那么才会操作平均响应时间
所以这里我们用持续 表示,即"1s 内持续进入 5 个或者5 个以上的请求"
若平均响应时间超过阈值(以 ms 为单位),阈值设置为200 ms
即这5个请求或者5 个以上的请求响应给前端的时间加起来除以5或者5以上,若是>200
那么在接下的时间 窗口(以 s 为单位)之内
对这个方法的调用都会自动地熔断,即抛出 DegradeException,即前端显示Blocked by Sentinel (flow limiting)
而不是有效时间的1秒,是时间窗口的有效时间,该有效时间过去,那么就不会操作熔断,自然也就没有异常了
注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的设置都会算作 4900 ms
若需要变更此上限可 以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置
比如:java -jar -Dcsp.sentinel.statistic.max.rt=2000 sentinel-dashboard-1.7.1.jar这样来启动,设置上限为2000 ms(毫秒)
我们来测试一下,先操作降级规则
默认是点击RT,即有RT这个属性的设置(这里设置为200毫秒,即200 ms)
回到UserController类,修改如下:
@GetMapping ( "/register" )
public String register ( ) throws InterruptedException {
Thread . sleep ( 1000 ) ;
System . out. println ( "Register success!" ) ;
return "Register success!" ;
}
这里记得重启
上面我们设置了延迟1秒钟,那么假设我们请求5次,每次的间隔不超过1秒,那么很明显
由于每次的响应时间通常是1秒,即平均响应的时间也就是1秒,大于阈值
那么在第5次或者5次以上的访问后,后面的访问即会请求失败
返回Blocked by Sentinel (flow limiting),有效时间是对应的时间窗口
这里只需要我们持续的点击访问即可,不需要操作循环多次的访问
所以我们连续的点击访问localhost:9101/user/register,访问了5次或者5次以上之后
后面的访问就会出现Blocked by Sentinel (flow limiting)
由于这里并不是操作高并发,所以通常情况下,第5次访问后,再次的访问即第6次的访问就会出现Blocked by Sentinel (flow limiting)
异常比例:
当资源的每秒请求量 >= 5(注意:这里不是持续,而是真的一秒钟之内的访问次数大于等于5次)
并且每秒异常总数占通过量的比值超过阈值之后,资源进入降级状态
即在接下的时间窗口(以 s 为单位)之内,对这个方法的调用都会自动地返回对于的异常信息
也就是Blocked by Sentinel (flow limiting),异常比率的阈值范围是 [0.0, 1.0] ,代表 0% - 100%
现在进行测试,我们回到UserController类,修改如下:
@GetMapping ( "/register" )
public String register ( ) throws InterruptedException {
int i = 1 / 0 ;
System . out. println ( "Register success!" ) ;
return "Register success!" ;
}
对于的降级规则:
点击异常比例,那么就会出现异常比例的属性,这里设置为0.1
由于代码里面设置了int i = 1/0;,那么这样必定百分百的报错
那么我们只需要在一秒钟之内访问localhost:9101/user/register这个地址5次后,再次的访问
就会出现异常,即Blocked by Sentinel (flow limiting)信息(有效时间也是时间窗口的时间)
这里再次的说明一下,该熔断异常不会打印出来的,但会给前端异常信息
即Blocked by Sentinel (flow limiting)信息
至于多次的访问,看前面的操作即可,当然,记得重启
异常数:
当资源近 1 分钟内的异常数目超过阈值之后会进行熔断
注意由于统计时间窗口是分钟级别的,若 timeWindow (时间窗口)小于 60s
则结束熔断状态后仍可能再进入熔断状态,时间窗口 >= 60s
现在进行测试,对应的降级规则:
点击异常数,会出现异常数的属性,这里设置为2,代表最大只能是2个异常
对应的代码不用修改,其中2代表1分钟之内,最大只能是2个异常(该异常是程序异常,不是这里的熔断的)
之后,若出现异常,则返回熔断的异常信息,66代表66秒,前面说过,要大于等于60秒,比如59秒
但道理说,可能1分钟之内,若在第一秒钟直接熔断(返回信息),然后熔断后,可能在第60秒再次的熔断(返回信息)
即多次的熔断,但实际上,我们只要在1分钟之内熔断了,那么时间窗口就是1分钟,那么时间窗口的作用是什么
一般如果少于1分钟,那么默认1分钟,大于1分钟,则操作该时间窗口,而不是1分钟
这时,我们试着访问localhost:9101/user/register两次,前两次是对应的程序的报错
第3次,就是熔断的异常信息了,即Blocked by Sentinel (flow limiting)
最后针对于异常比例和异常数,那么有个问题,熔断会使得打印报错信息吗,答:不会
可以说在打印之前,就会取消的,或者说,被覆盖或者先操作熔断的异常了,即熔断的异常优先了
所以这里以及前面的异常比例都是如此,不会打印异常信息
SCA 小结:
SCA实际上发展了三条线
第一条线:开源出来一些组件
第二条线:阿里内部维护了一个分支,自己业务线使用(这里并没有大致说明)
第三条线:阿里云平台部署一套,付费使用 从战略上来说,SCA更是为了贴合阿里云
目前来看,开源出来的这些组件(这里是SCA),推广及普及率不高,社区活跃度不高
稳定性和体验度上仍需进 一步提升(如持久化好像并没有操作,但由于SCN好像并不怎么维护,可能在以后会广泛使用SCA的)
但现在所以一般都是使用SCN(因为相当于全家桶的模式,即基本上对应的操作都有,且各个组件相对来说还是比较成熟稳定的)
全家桶模式:包括各种的解决方案,基本能解决大多数问题,即比较全
最后,根据实际使用来看SCA中的Sentinel的稳定性和体验度要好于Nacos
最后说明:一般情况下,如果是操作地址的,那么如果地址不存在,一般是不会导致项目需要重新启动的
只是使用不了对应的功能而已,比如nacos和sentinel等等,但如果对应的地址又回来了,一般会自动的连接
但并不是全部,有些还是会导致重启的或者不连接的(非常少),但这里的Cloud基本都可以
但这些只是提一下而已,了解即可