Spring Cloud Alibaba
微服务架构
单体架构
将项⽬所有模块(功能)打成jar或者war,然后部署⼀个进程
优点:
1:部署简单: 由于是完整的结构体,可以直接部署在⼀个服务器上即可。
2:技术单⼀: 项⽬不需要复杂的技术栈,往往⼀套熟悉的技术栈就可以完成开发。
3:⽤⼈成本低: 单个程序员可以完成业务接⼝到数据库的整个流程。
缺点:
1:系统启动慢, ⼀个进程包含了所有的业务逻辑,涉及到的启动模块过多,导致系统的启动、重启时间周期过⻓;
2:系统错误隔离性差、可⽤性差,任何⼀个模块的错误均可能造成整个系统的宕机;
3:可伸缩性差:系统的扩容只能只对这个应⽤进⾏扩容,⽆法结合业务模块的特点进⾏伸缩。
4:线上问题修复周期⻓:任何⼀个线上问题修复需要对整个应⽤系统进⾏全⾯升级。
5. 跨语⾔程度差
6. 不利于安全管理,所有开发⼈员都拥有全量代码
微服务架构
1:微服务是⼀种项⽬架构思想(⻛格)
2:微服务架构是⼀系列⼩服务的组合(组件化与多服务)
3:任何⼀个微服务,都是⼀个独⽴的进程(独⽴开发、独⽴维护、独⽴部署)
4:轻量级通信http协议(跨语⾔,跨平台)
5:服务粒度(围绕业务功能拆分)
6:去中⼼化管理(去中⼼化”地治理技术、去中⼼化地管理数据)
Spring Cloud Alibaba
Spring Cloud Alibaba 致力于提供微服务开发的一站式解决方案。此项目包含开发分布式应用微服务的必需组件,方便开发者通过 Spring Cloud 编程模型轻松使用这些组件来开发分布式应用服务
spring cloud alibaba 特点
- 服务降级和流量控制 sentinel 替换 hystrix
- 服务注册与发现 nacos 替换 eureka consul
- 分布式配置& 事件驱动消息总线 nacos 替换 config & bus
- 分布式事务&dubbo seta
springcloud 组件
- 服务注册与发现组件 eureka consul nacos
- 服务间通信组件 restTemplate+ribbon,Openfeign restTemplate+ribbon,Openfeign
- 服务降级和熔断 hystrix hystrix dashboard sentinel
- 服务网关组件 gateway zuul
- 统一配置中心组件 消息总线组件 config bus nacos
环境搭建
新建一个空的项目
创建一个普通的父maven工程
依赖
springcloud 依赖
<!--定义springcloud版本-->
<properties>
<spring.cloud.alibaba.version>2.2.1.RELEASE</spring.cloud.alibaba.version>
</properties>
<!--全局引入springcloudalibaba下载依赖地址,并不会引入依赖-->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
完整版
<!-- 继承springboot父项目-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
</parent>
<!-- 定义版本号-->
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<spring-cloud.version>Hoxton.SR6</spring-cloud.version>
<!--定义springcloud版本-->
<spring.cloud.alibaba.version>2.2.1.RELEASE</spring.cloud.alibaba.version>
</properties>
<!-- 维护依赖-->
<dependencyManagement>
<dependencies>
<!--引入springcloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--引入springcloud alibaba-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Nacos
Nacos Name Service(服务注册与发现) & Configurations Services(统一配置中心)
Nacos 致力于帮助您发现、配置和管理微服务。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据及流量管理
总结:Nacos就是微服务架构中服务注册中心以及统一配置中心,用来替换原来的(eureka,consul)以及config组件
官网地址
下载地址
在Nacos的GitHub页面,提供有下载链接,可以下载编译好的Nacos服务端或者源代码:
GitHub主页:https://github.com/alibaba/nacos
GitHub的Release下载页:https://github.com/alibaba/nacos/releases
安装Nacos
linux版安装
必须含有jdk 最好是8以上
上传到服务器
解压
tar -zxvf nacos-server-1.4.1.tar.gz
Nacos目录
- bin 启动nacos服务的脚本目录
- conf nacos的配置文件目录
- target nacos的启动依赖存放目录
- data nacos启动成功后保存数据的目录
启动服务
打开终端进入nacos的bin目录执行如下命令
./startup.sh -m standalone
注意:默认nacos启动是以集群模式启动,必须满足多个节点
查看日志
vim logs/access_log.2021-08-19.log
访问nacos的web服务管理界面
http://localhost:8848/nacos/
用户名 和 密码都是nacos
Windows安装
windows版本使用nacos-server-1.4.1.zip
包即可,注意路径不要带中文
目录说明:
- bin:启动脚本
- conf:配置文件
端口配置
Nacos的默认端口是8848,如果你电脑上的其它进程占用了8848端口,请先尝试关闭该进程。
如果无法关闭占用8848端口的进程,也可以进入nacos的conf目录,修改配置文件中的端口:
启动
启动非常简单,进入bin目录,结构如下:
打开cmd然后执行命令即可:
- windows命令
startup.cmd -m standalone
访问
在浏览器输入地址:http://127.0.0.1:8848/nacos即可
默认的账号和密码都是nacos,进入后:
开发服务注册到nacos
创建项目并引入依赖
<!--引入nacos client的依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
配置文件
server:
port: 7001
spring:
application:
name: NACOSCLIENT
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址
management:
endpoints:
web:
exposure:
include: '*'
server.port=8789 #指定当前服务端口
spring.application.name=nacosclient #指定服务名称
spring.cloud.nacos.server-addr=localhost:8848 #指定nacos服务 总地址
spring.cloud.nacos.discovery.server-addr=${spring.cloud.nacos.server-addr} #指定注册中心地址
management.endpoints.web.exposure.include=* #暴露所有web端点
加入启动服务注册注解
[注意:][新版本之后这步可以省略不写]
@SpringBootApplication
@EnableDiscoveryClient
public class NacosClientApplication {
public static void main(String[] args) {
SpringApplication.run(NacosClientApplication.class,args);
}
}
查看nacos的服务列表
nacos领域模型
nacos的服务由三元组唯⼀确定 (namespace、group、servicename)
nacos的配置由三元组唯⼀确定 (namespace、group、dataId)
不同的namespace是相互隔离的,相同namespace但是不同的group也是相互隔离的
默认的namespace是public ,不能删除
默认的group是DEFAULT-GROUP
服务分级存储模型
一个服务可以有多个实例,例如我们的user-service,可以有:
- 127.0.0.1:8081
- 127.0.0.1:8082
- 127.0.0.1:8083
假如这些实例分布于全国各地的不同机房,例如:
- 127.0.0.1:8081,在上海机房
- 127.0.0.1:8082,在上海机房
- 127.0.0.1:8083,在杭州机房
Nacos就将同一机房内的实例 划分为一个集群。
也就是说,user-service是服务,一个服务可以包含多个集群,如杭州、上海,每个集群下可以有多个实例,形成分级模型,如图:
微服务互相访问时,应该尽可能访问同集群实例,因为本地访问速度更快。当本集群内不可用时,才访问其它集群。例如:
Nacos服务分级存储模型
一级是服务,例如userservice
二级是集群,例如杭州或上海
三级是实例,例如杭州机房的某台部署了userservice的服务器
spring:
cloud:
nacos:
server-addr: localhost:8848
discovery:
cluster-name: HZ # 集群名称
同集群优先的负载均衡
认的ZoneAvoidanceRule
并不能实现根据同集群优先来实现负载均衡。
因此Nacos中提供了一个NacosRule
的实现,可以优先从同集群中挑选实例。
NacosRule负载均衡策略
- 优先选择同集群服务实例列表
- 本地集群找不到提供者,才去其它集群寻找,并且会报警告
- 确定了可用实例列表后,再采用随机负载均衡挑选实例
修改负载均衡规则
userservice:
ribbon:
NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
权重配置
实际部署中会出现这样的场景:
服务器设备性能有差异,部分实例所在机器性能较好,另一些较差,我们希望性能好的机器承担更多的用户请求。
但默认情况下NacosRule是同集群内随机挑选,不会考虑机器的性能问题。
因此,Nacos提供了权重配置来控制访问频率,权重越大则访问频率越高。
在nacos控制台,找到user-service的实例列表,点击编辑,即可修改权重:
修改权重
实例的权重控制
- Nacos控制台可以设置实例的权重值,0~1之间
- 同集群内的多个实例,权重越高被访问的频率越高
- 权重设置为0则完全不会被访问
Nacos注册中心原理
Nacos的服务实例分为两种l类型:
-
临时实例:如果实例宕机超过一定时间,会从服务列表剔除,默认的类型。
-
非临时实例:如果实例宕机,不会从服务列表剔除,也可以叫永久实例
配置一个服务实例为永久实例:
spring:
cloud:
nacos:
discovery:
ephemeral: false # 设置为非临时实例
-
Nacos与eureka的共同点
- 都支持服务注册和服务拉取
- 都支持服务提供者心跳方式做健康检测
-
Nacos与Eureka的区别
- Nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式
- 临时实例心跳不正常会被剔除,非临时实例则不会被剔除
- Nacos支持服务列表变更的消息推送模式,服务列表更新更及时
- Nacos集群默认采用AP方式,当集群中存在非临时实例时,采用CP模式;Eureka采用AP方式
nacos作为配置中心
上传当前配置文件
先把当前配置文件上传到nacos
从微服务拉取配置
注意:项目的核心配置,需要热更新的配置才有放到nacos管理的必要。基本不会变更的一些配置还是保存在微服务本地比较好。
微服务要拉取nacos中管理的配置,并且与本地的application.yml配置合并,才能完成项目启动
因此spring引入了一种新的配置文件:bootstrap.yaml文件,会在application.yml之前被读取,流程如下
创建项目并引入nacons配置中心依赖
<!--引入nacos client 依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--引入配置中心依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
配置配置中心地址
application.yml直接启动会报错,要把配置文件命名为 bootstrap.yml
bootstrap.yml
方式1
直接配置名称
spring:
cloud:
nacos:
#拉取文件的地址
server-addr: localhost:8848
config:
#拉取文件的组
group: DEFAULT_GROUP
#拉取配置文件的文件名
name: nacosconfig-prod
#拉取配置文件的后缀名
file-extension: yml
spring.cloud.nacos.server-addr=localhost:8848 # 远程配置中心的地址
spring.cloud.nacos.config.server-addr=${spring.cloud.nacos.server-addr} # 去指定nacos地址读取配置
spring.cloud.nacos.config.group=DEFAULT_GROUP # 读取配置的分组
spring.cloud.nacos.config.file-extension=properties # 指定读取文件后缀
spring.application.name=nacosconfig-prod # 指定读取文件的前缀
spring.profiles.active=prod # 指定读取文件的具体环境
方式2
⽂件名是通过公式来拼接$ {prefix}-$ {spring.profiles.active}.${file-extension}
配置前缀
spring:
cloud:
nacos:
#拉取文件的地址
server-addr: localhost:8848
config:
#拉取文件的组
group: DEFAULT_GROUP
#拉取配置文件的前缀
prefix: nacosconfig
#拉取配置文件的后缀名
file-extension: yml
profiles:
active: prod
编写控制器测试配置读取情况
@RestController
public class TestController {
@Value("${student.name}")
private String studentname;
@RequestMapping("/test")
public String test(){
return studentname;
}
}
启动项目方式测试配置读取
实现自动配置刷新
- 默认情况下nacos已经实现了自动配置刷新功能,如果需要刷新配置直接在控制器中加入@RefreshScope注解即可
方式一
在@Value注入的变量所在类上添加注解@RefreshScope:
@RestController
@RefreshScope //允许远端配置修改自动刷新
public class TestController {
@Value("${student.name}")
private String studentname;
@RequestMapping("/test")
public String test(){
return studentname;
}
}
再次访问页面
方式二
使用@ConfigurationProperties注解代替@Value注解
配置中心细节
DataId
- 用来读取远程配置中心的中具体配置文件其完整格式如下:
- $ {prefix}-$ {spring.profile.active}.$ {file-extension}
-
prefix 默认为 spring.application.name 的值,也可以通过配置项 spring.cloud.nacos.config.prefix来配置。
-
spring.profile.active 即为当前环境对应的 profile,详情可以参考 Spring Boot文档。 注意:当 spring.profile.active 为空时,对应的连接符 - 也将不存在,dataId 的拼接格式变成 $ {prefix}.$ {file-extension}
-
file-exetension 为配置内容的数据格式,可以通过配置项 spring.cloud.nacos.config.file-extension 来配置。目前只支持 properties 和 yaml 类型。
-
命名空间
namespace命名空间是nacos针对于企业级开发设计用来针对于不同环境的区分,比如正在企业开发时有测试环境,生产环境,等其他环境,因此为了保证不同环境配置实现隔离,提出了namespace的概念,默认在nacos中存在一个public命名空间所有配置在没有指定命名空间时都在这个命名空间中获取配置,在实际开发时可以针对于不能环境创建不同的namespace空间。默认空间不能删除!
创建其他命名空间
每个命名空间都有一个唯一id,这个id是读取配置时指定空间的唯一标识
在配置列表查看空间,并在指定空间下载创建配置文件
项目中使用命名空间指定配置
不知道为什么我使用上面第二种方式配置加命名空间一直拉取不到,只有用文件名才行
spring:
cloud:
nacos:
#拉取文件的地址
server-addr: localhost:8848
config:
#拉取文件的组
group: DEFAULT_GROUP
#拉取配置文件的文件名
name: nacosconfig-prod
#拉取配置文件的后缀名
file-extension: yml
namespace: 1b1034de-42a0-4c5c-8d5a-35bf35accea0
分组
配置分组是对配置集进行分组,通过一个有意义的字符串(如 Buy 或 Trade )来表示,不同的配置分组下可以有相同的配置集(Data ID)。当您在 Nacos 上创建一个配置时,如果未填写配置分组的名称,则配置分组的名称默认采用 DEFAULT_GROUP 。配置分组的常见场景:可用于区分不同的项目或应用,例如:学生管理系统的配置集可以定义一个group为:STUDENT_GROUP
spring:
cloud:
nacos:
#拉取文件的地址
server-addr: localhost:8848
config:
#拉取文件的组
group: mygroup
#拉取配置文件的文件名
name: nacosconfig-prod
#拉取配置文件的后缀名
file-extension: yml
namespace: 1b1034de-42a0-4c5c-8d5a-35bf35accea0
访问
配置共享
其实微服务启动时,会去nacos读取多个配置文件,例如:
-
[spring.application.name]-[spring.profiles.active].yaml
,例如:userservice-dev.yaml -
[spring.application.name].yaml
,例如:userservice.yaml
而[spring.application.name].yaml
不包含环境,因此可以被多个环境共享。
在远端配置 userservice.yaml,userservice-dev.yaml,userservice-prod.yaml
发现不管是dev还是prod都可以读到userservice.yaml里的值
并且赋上不同的值,编写controller注入访问查看结果
配置共享的优先级
当nacos、服务本地同时出现相同属性时,优先级有高低之分:
nacos持久化(mysql)
在0.7版本之前,在单机模式时nacos使用嵌入式数据库(derby)实现数据的存储,不方便观察数据存储的基本情况。
0.7版本增加了支持mysql数据源能力,具体的操作步骤:
1.安装数据库,版本要求:5.6.5+
2.初始化mysql数据库,数据库初始化文件:nacos-mysql.sql
3.修改conf/application.properties文件,增加支持mysql数据源配置(目前只支持mysql),添加mysql数据源的url、用户名和密码。
创建数据库
在mysql中创建一个数据库 格式要求一定是utf8编码的
执行sql脚本
sql在安装目录下的conf里面
修改配置文件
只需要将别人的注释解开即可
spring.datasource.platform=mysql
### Count of DB:
db.num=1
### Connect URL of DB:
db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC
db.user.0=root
db.password.0=123456
注意:修改完配置文件后一定要重启nacos
测试
可以看到切换为mysql持久化保存后原来的配置都没了
重新添加一个配置文件
测试
nacos集群高可用
集群搭建注意事项
3个或3个以上Nacos节点才能构成集群。
要求虚拟机内存分配必须大于2G以上
集群规划
7001 nacos01
7002 nacos02
7003 nacos03
7006 nginx
3306 mysql
修改config配置
先复制3个nacos,并把端口分别改成7001,7002,7003,并把mysql数据库配置打开
开启nacos mysql持久化
注意:数据库中不能存在原始数据,所有最好重新执行一下SQL脚本
nacos conf目录中新建cluster.conf文件
在 nacos01,nacos02,nacos03 新建cluster.conf里面添加如下配置
123.57.252.81:7001
123.57.252.81:7002
123.57.252.81:7003
启动nacos
./startup.sh
这里做个说明我本来想用我服务器跑集群的,结果跑第二个nacos的时候,服务器就死机了,我的服务器是一核两g的,可以把启动文件里的参数改小一点
原设置:
JAVA_OPT="${JAVA_OPT} -server -Xms2g -Xmx2g -Xmn1g -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=320m"
修改后:
JAVA_OPT="${JAVA_OPT} -server -Xms256m -Xmx256m -Xmn128m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=160m"
配置nginx conf配置文件
在http块中添加
upstream nacos-servers {
server 123.57.252.81:7001;
server 123.57.252.81:7002;
server 123.57.252.81:7003;
}
location / {
proxy_pass http://nacos-servers/;
}
把监听端口改为7006
注意这是 配置文件里的spring.cloud.nacos.server-addr=123.57.252.81:7006
就应该填成nginx地址,以后可以改成域名
-
实际部署时,需要给做反向代理的nginx服务器设置一个域名,这样后续如果有服务器迁移nacos的客户端也无需更改配置.
-
Nacos的各个节点应该部署到多个不同服务器,做好容灾和隔离
sentinel
Sentinel以“流量”为突破口,在流量控制、断路、负载保护等多个领域进行工作,保障服务可靠性。
通俗:用来在微服务系统中保护微服务对的作用 如何 服务雪崩 服务熔断 服务降级 就是用来替换hystrix
特性
-
丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
-
完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
-
广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
sentinel使用
sentinel提供了两个服务组件
- sentinel 用来实现微服务系统中服务熔断、降级等功能
- sentinel dashboard 用来监控微服务系统中流量调用等情况 流控 熔断 降级 配置
sentinel dashboard的安装
https://github.com/alibaba/Sentinel/releases
启动
仪表盘是个jar包可以直接通过java命令启动 如: java -jar 方式运行 默认端口为 8080
java -jar -Dserver.port=10086 sentinel-dashboard-1.8.2.jar
访问
http://123.57.252.81:10086/#/login
登录
用户名&密码: sentinel
sentinel 实时监控服务
引入依赖
<!--引入nacos client依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--引入sentinel依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
配置
server:
port: 7003
spring:
application:
name: SENTINEL
cloud:
nacos:
server-addr: localhost:8848
sentinel:
enabled: true # 开启sentinel 默认开启
transport:
dashboard: localhost:10086 #指定dashboard地址
port: 8719 # 与dashboard通信的端口
server.port=8789
spring.application.name=nacosclient
spring.cloud.nacos.server-addr=localhost:8848
spring.cloud.nacos.discovery.server-addr=${spring.cloud.nacos.server-addr}
spring.cloud.sentinel.enabled=true # 开启sentinel 默认开启
spring.cloud.sentinel.transport.dashboard=localhost:9191 # 连接dashboard
spring.cloud.sentinel.transport.port=8719 # 与dashboard通信的端口
访问dashboard界面查看服务监控
默认情况下sentiel为延迟加载,不会在启动之后立即创建服务监控,需要对服务进行调用时才会初始化
controller服务
@RestController
public class DemoController {
@RequestMapping("/test")
public String test(){
return "test ok";
}
}
一开始项目是在本地跑的dashboard在服务器跑的发现一直报错后来全部换成本地了,大家可以试试我没成功
QPS
Query-Per-Second QPS 称之为系统每秒的请求数
RT
Response Time RT 每个请求响应时间
sentinel 流量控制
流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
- QPS 每秒的请求数 每秒请求数超过阈值之后对当前请求进行限流
- 并发线程数 当服务器中创建线程数超过指定阈值之后对当前请求进行限流
高级选项
- 流控模式
- 直接 :当配置资源在运行过程超过当前规则配置的阈值之后对该资源请求做的处理是什么
- 关联模式 :当配置资源在运行过程中超过当前规则配置的阈值之后对它所关联的资源进行请求做什么样的助理
- 链路模式 : 当配置资源在运行过程超过当前配置规则阈值之后对它链路中资源请求做什么样的处理
- 流控效果(只适用于qps限流)
- 快速失败: 直接拒绝请求,并抛出响应异常
- warm up :在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮
- 排队等待 :方式会严格控制请求通过的间隔时间也即是让请求以均匀的速度通过
配置QPS流量控制
每秒只能最大接收2个请求,超过2个报错
流控模式
标识流量控制规则到达阈值直接触发流量控制
- 直接:标识流量控制规则到达阈值直接触发流量控制
- 关联: 当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操
- 作和写操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则
- 争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,
- read_db 和 write_db 这两个资源分别代表数据库读写,我们可以给 read_db 设置限流规则来达到写优先的目的:设
- 置 strategy 为 RuleConstant.STRATEGY_RELATE 同时设置 refResource 为 write_db。这样当写库操作过于频繁时,
- 读数据的请求会被限流。
其实关联就是反向关联
链路其实就是正向关联
流控效果
注意 流控效果 只适用于QPS限流
- 直接拒绝:(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)方式是默认的流量控制方式,当QPS超过任意规则的阈值后,
- 新的请求就会被立即拒绝,拒绝方式为抛出FlowException。
- Warm Up:(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,
- 当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时
- 间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
匀速排队:(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。 只能对请求进行排队等待
sentinel熔断降级
Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException
)
- 熔断:用来避免微服务架构中雪崩现象
- 原理:当监控到调用链路中某一个服务,出现异常(阈值以上)自动触发熔断,在触发之后对于该服务调用不可用
- 熔断:达到某个阈值条件之后自动触发熔断
熔断策略
- RT 根据请求响应时间熔断
- 异常比例 根据请求调用过程中出现异常百分比进行熔断
- 异常数 根据请求调用过程中异常数进行熔断
@RestController
public class DemoController {
@RequestMapping("/test")
public String test(Integer id){
if(id<=0){
throw new RuntimeException("id错误");
}
return "test ok";
}
}
平均响应时间 (DEGRADE_GRADE_RT):当 1s 内持续进入 N 个请求,对应时刻的平均响应时间(秒级)均超过阈值(count,以 ms 为单位),那么在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地熔断(抛出 DegradeException)。注意 Sentinel 默认统计的 RT 上限是 4900 ms,超出此阈值的都会算作 4900 ms,若需要变更此上限可以通过启动配置项 -Dcsp.sentinel.statistic.max.rt=xxx 来配置
异常比例 (DEGRADE_GRADE_EXCEPTION_RATIO):当资源的每秒请求量 >= N(可配置),并且每秒异常总数占通过量的比值超过阈值(DegradeRule 中的 count)之后,资源进入降级状态,即在接下的时间窗口(DegradeRule 中的 timeWindow,以 s 为单位)之内,对这个方法的调用都会自动地返回。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%
断路器的⼯作流程:
⼀旦熔断,断路器的状态是Open(所有的请求都不能进来)
当熔断时⻓结束,断路器的状态是half-Open(可以允许⼀个请求进来)
如果接下来的请求正常,断路器的状态是close(资源就⾃恢复)
如果接下来的请求不正常,断路器的状态是open
异常数 (DEGRADE_GRADE_EXCEPTION_COUNT):当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态
热点参数限流
热点限流,也称之为热点参数限流,日后访问资源中携带了指定参数进行限流
如何使用
注意:使用热点参数限流时,不能使用资源路径,必须使用资源别名
Sentinel提供资源别名注解 @SentinelResource(value=“ ”)
@SentinelResource 注解
- value:资源名称,必需项(不能为空)
- entryType:entry 类型,可选项(默认为 EntryType.OUT)
- blockHandler / blockHandlerClass: blockHandler 对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
- fallback / fallbackClass:fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求:
- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
- fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
- defaultFallback(since 1.6.0):默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求:
- 返回值类型必须与原函数返回值类型一致;
- 方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。
- defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。
exceptionsToIgnore(since 1.6.0):用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。
特别地,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。若未配置 blockHandler、fallback 和 defaultFallback,则被限流降级时会将 BlockException 直接抛出(若方法本身未定义 throws BlockException 则会被 JVM 包装一层 UndeclaredThrowableException)
package com.blb.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController {
@RequestMapping("/test")
@SentinelResource(value = "dyk",blockHandler = "fallBack",fallback = "fall")//作用代表这是一个sentinel资源
public String test(Integer id){
if(id<=0){
throw new RuntimeException("id错误");
}
return "test ok";
}
//降级异常处理
public String fallBack(Integer id, BlockException e){
if(e instanceof FlowException){
return "当前服务已被流控! "+e.getClass().getCanonicalName();
}
return "当前服务已被降级处理! "+e.getClass().getCanonicalName();
}
//异常处理
public String fall(Integer id){
return "当前服务已不可用!";
}
}
@RequestMapping("/test1")
@SentinelResource("hostkey-test1")
public String test1(String name,int age){
return "姓名"+name+"年龄"+age;
}
注意使用热点key时,必须指定资源路径 即@SentinelResource(“hostkey-test1”)否则无效
当qps大于1
原来的是sentinel帮我们处理了异常,而现在是我们自己定义的资源,所以异常要自己处理
Sentinel-权限规则
package com.blb.sentinel;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
@Component
public class CustomerRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
String source = httpServletRequest.getHeader("source");
if (source != null) {
return source;
}
return null;
}
}
openfeign拦截器统⼀设置请求头
package com.blb.sentinel;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.stereotype.Component;
@Component
public class CustomerRequestInterceptor implements RequestInterceptor {
public void apply(RequestTemplate requestTemplate) {
//统⼀设置请求头
requestTemplate.header("source","cloud-order");
}
}
现在服务id为cloud-order的微服务就进入了黑名单就无法直接访问了
Sentinel-针对来源
但是别忘记加上
package com.blb.sentinel;
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
@Component
public class CustomerRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
String source = httpServletRequest.getHeader("source");
if (source != null) {
return source;
}
return null;
}
}
系统规则
- Load ⾃适应(仅对 Linux/Unix-like 机器⽣效):系统的 load1 作为启发指标,进⾏⾃适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统容量由系统的 maxQps *minRt 估算得出。设定参考值⼀般是 CPU cores * 2.5 。
- CPU usage(1.5.0+ 版本):当系统 CPU 使⽤率超过阈值即触发系统保护(取值范围 0.0-1.0),⽐较灵敏。
- 平均 RT:当单台机器上所有⼊⼝流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
- 并发线程数:当单台机器上所有⼊⼝流量的并发线程数达到阈值即触发系统保护。⼊⼝ QPS:当单台机器上所有⼊⼝流量的 QPS 达到阈值即触发系统保护。
全局异常处理
package com.blb.sentinel;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import java.util.HashMap;
import java.util.Map;
@ControllerAdvice
public class CustomerExceptionHandler {
//流控规则,全局异常处理
@ExceptionHandler(FlowException.class)
@ResponseBody
public Map handlerFlowExcetoption() {
return new HashMap() {{
put("code", "5xx");
put("msg", "系統繁忙,稍后重试!!");
}};
}
//熔断降级规则,全局异常处理
@ExceptionHandler(DegradeException.class)
@ResponseBody
public Map handlerDegradeException() {
return new HashMap() {{
put("code", "5xx");
put("msg", "系统开⼩差,稍后重试!!");
}};
}
//权限规则,全局异常处理
@ExceptionHandler(AuthorityException.class)
@ResponseBody
public Map handlerAuthorityException() {
return new HashMap() {{
put("code", "5xx");
put("msg", ",没有权限访问!!");
}};
}
}
Sentinel规则持久化
原始模式
如果不做任何修改,Dashboard 的推送规则⽅式是通过 API 将规则推送⾄客户端并直接更新到内存中:
这种做法的好处是简单,⽆依赖;坏处是应⽤重启规则就会消失,仅⽤于简单测试,不能⽤于⽣产环境。
Pull模式
FileRefreshableDataSource 定时从指定⽂件中读取规则JSON⽂件【图中的本地⽂件】,如果发现⽂件发⽣变化,就更新规则缓存。
FileWritableDataSource 接收控制台规则推送,并根据配置,修改规则JSON⽂件【图中的本地⽂件】
添加依赖
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-extension</artifactId>
</dependency>
核⼼代码
package com.blb.sentinel;
import com.alibaba.csp.sentinel.command.handler.ModifyParamFlowRulesCommandHandler;
import com.alibaba.csp.sentinel.datasource.*;
import com.alibaba.csp.sentinel.init.InitFunc;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRuleManager;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRuleManager;
import com.alibaba.csp.sentinel.slots.system.SystemRule;
import com.alibaba.csp.sentinel.slots.system.SystemRuleManager;
import com.alibaba.csp.sentinel.transport.util.WritableDataSourceRegistry;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import java.io.File;
import java.io.IOException;
import java.util.List;
/**
* 拉模式规则持久化
*/
public class FileDataSourceInit implements InitFunc {
@Override
public void init() throws Exception {
// TIPS: 如果你对这个路径不喜欢,可修改为你喜欢的路径
String ruleDir = System.getProperty("user.dir") +
"/sentinel/sentinel/rules";//第一个sentinel是我的模块名,第二个是文件名
String flowRulePath = ruleDir + "/flow-rule.json";
String degradeRulePath = ruleDir + "/degrade-rule.json";
String systemRulePath = ruleDir + "/system-rule.json";
String authorityRulePath = ruleDir + "/authority-rule.json";
String paramFlowRulePath = ruleDir + "/param-flow-rule.json";
this.mkdirIfNotExits(ruleDir);
this.createFileIfNotExits(flowRulePath);
this.createFileIfNotExits(degradeRulePath);
this.createFileIfNotExits(systemRulePath);
this.createFileIfNotExits(authorityRulePath);
this.createFileIfNotExits(paramFlowRulePath);
// 流控规则
ReadableDataSource<String, List<FlowRule>> flowRuleRDS = new
FileRefreshableDataSource<>(
flowRulePath,
flowRuleListParser
);
// 将可读数据源注册⾄FlowRuleManager
// 这样当规则⽂件发⽣变化时,就会更新规则到内存
FlowRuleManager.register2Property(flowRuleRDS.getProperty());
WritableDataSource<List<FlowRule>> flowRuleWDS = new
FileWritableDataSource<>(
flowRulePath,
this::encodeJson
);
// 将可写数据源注册⾄transport模块的WritableDataSourceRegistry中
// 这样收到控制台推送的规则时,Sentinel会先更新到内存,然后将规则写⼊到⽂件中
WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);
// 降级规则
ReadableDataSource<String, List<DegradeRule>> degradeRuleRDS =
new FileRefreshableDataSource<>(
degradeRulePath,
degradeRuleListParser
);
DegradeRuleManager.register2Property(degradeRuleRDS.getProperty());
WritableDataSource<List<DegradeRule>> degradeRuleWDS = new
FileWritableDataSource<>(
degradeRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS);
// 系统规则
ReadableDataSource<String, List<SystemRule>> systemRuleRDS =
new FileRefreshableDataSource<>(
systemRulePath,
systemRuleListParser
);
SystemRuleManager.register2Property(systemRuleRDS.getProperty());
WritableDataSource<List<SystemRule>> systemRuleWDS = new
FileWritableDataSource<>(
systemRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS);
// 授权规则
ReadableDataSource<String, List<AuthorityRule>>
authorityRuleRDS = new FileRefreshableDataSource<>(
authorityRulePath,
authorityRuleListParser
);
AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty()
);
WritableDataSource<List<AuthorityRule>> authorityRuleWDS = new
FileWritableDataSource<>(
authorityRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS);
// 热点参数规则
ReadableDataSource<String, List<ParamFlowRule>>
paramFlowRuleRDS = new FileRefreshableDataSource<>(
paramFlowRulePath,
paramFlowRuleListParser
);
ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty()
);
WritableDataSource<List<ParamFlowRule>> paramFlowRuleWDS = new
FileWritableDataSource<>(
paramFlowRulePath,
this::encodeJson
);
ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS);
}
private Converter<String, List<FlowRule>> flowRuleListParser =
source -> JSON.parseObject(
source,
new TypeReference<List<FlowRule>>() {
}
);
private Converter<String, List<DegradeRule>> degradeRuleListParser
= source -> JSON.parseObject(
source,
new TypeReference<List<DegradeRule>>() {
}
);
private Converter<String, List<SystemRule>> systemRuleListParser =
source -> JSON.parseObject(
source,
new TypeReference<List<SystemRule>>() {
}
);
private Converter<String, List<AuthorityRule>>
authorityRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<AuthorityRule>>() {
}
);
private Converter<String, List<ParamFlowRule>>
paramFlowRuleListParser = source -> JSON.parseObject(
source,
new TypeReference<List<ParamFlowRule>>() {
}
);
private void mkdirIfNotExits(String filePath) throws IOException {
File file = new File(filePath);
if (!file.exists()) {
file.mkdirs();
}
}
private void createFileIfNotExits(String filePath) throws
IOException {
File file = new File(filePath);
if (!file.exists()) {
file.createNewFile();
}
}
private <T> String encodeJson(T t) {
return JSON.toJSONString(t);
}
}
配置
在项⽬的 resources/META-INF/services ⽬录下创建⽂件,
⽂件名: com.alibaba.csp.sentinel.init.InitFunc
内容为如下:
改成上⾯FileDataSourceInit的包名类名全路径即可。
整合环境公共依赖
spring boot 2.2+
springcloud Hoxton+
springcloud alibaba 2.2.1+
- springcloud eureka consul ribbon openfeign hystix config+bus gateway
- springcloudalibaba nacos服务注册中&统一配置中心 sentinel 服务熔断 限流 …
整合思路:
1.服务注册中心 nacos
2.统一配置中心 nacos
3.服务熔断组件 sentinel
4.服务间通信组件 openfeign + ribbon
5.服务网关组件 gateway
<!--继承springboot父项目-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.SR6</spring-cloud.version>
<spring.cloud.alibaba.version>2.2.1.RELEASE</spring.cloud.alibaba.version>
</properties>
<dependencyManagement>
<dependencies>
<!--引入springcloud alibaba-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring.cloud.alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!--引入springcloud-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>