Nacos: 注册中心,解决服务注册与发现
Ribbon: 客户端的负载均衡器,解决服务集群的负载均衡
OpenFeign:声明式的HTTP客户端,服务远程调用
Nacos:配置中心,中心化管理配置文件
Sentinel:微服务流量卫兵,以流量为入口,保护微服务,防止出现服务雪崩
Gateway: 微服务网关,服务集群的入口,路由转发以及负载均衡(结合Sentinel)
Sleuth: 链路追踪,链路快速梳理、故障定位等
Seata: 分布式事务解决方案
目录
- 一、概述
- 二、安装部署Dashboard
- 三、快速开始
- 四、Sentinel规则操作
- 五、@SentinelResource及异常捕获
- 六、规则持久化
一、概述
1.1、高并发带来的问题
在微服务架构中,我们将业务拆分成一个个的服务,服务与服务之间可以相互调用,但是由于网络原因或者自身的原因,服务并不能保证服务的100%可用,如果单个服务出现问题,调用这个服务就会出现网络延迟,此时若有大量的网络涌入,会形成任务堆积,最终导致服务瘫痪。
1.2、服务雪崩
1.2.1、服务雪崩现象
【现象】
一个服务不可用导致一些列的服务不可用
1.3、常见容错方案
要防止雪崩的扩散,我们就要做好服务的容错,容错说白了就是保护自己不被猪队友拖垮的一些措施。
常见的容错思路有隔离、超时、限流、熔断、降级这几种。
1.3.1、隔离机制
比如服务A内总共有100个线程, 现在服务A可能会调用服务B,服务C,服务D.我们在服务A进行远程调用的时候,给不同的服务分配固定的线程,不会把所有线程都分配给某个微服务. 比如调用服务B分配30个线程,调用服务C分配30个线程,调用服务D分配40个线程. 这样进行资源的隔离,保证即使下游某个服务挂了,也不至于把服务A的线程消耗完。比如服务B挂了,这时候最多只会占用服务A的30个线程,服务A还有70个线程可以调用服务C和服务D。
1.3.2、超时机制
在上游服务调用下游服务的时候,设置一个最大响应时间,如果超过这个时间,下游未作出反应,就断开请求,释放掉线程。
1.3.3、限流机制
限流就是限制系统的输入和输出流量已达到保护系统的目的。为了保证系统的稳固运行,一旦达到的需要限制的阈值,就需要限制流量并采取少量措施以完成限制流量的目的。
1.3.4、熔断机制
在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用。这种牺牲局部,保全整体的措施就叫做熔断。
服务熔断一般有三种状态:
- 熔断关闭状态(Closed):服务没有故障时,熔断器所处的状态,对调用方的调用不做任何限制。
- 熔断开启状态(Open):后续对该服务接口的调用不再经过网络,直接执行本地的fallback方法。
- 半熔断状态(Half-Open):尝试恢复服务调用,允许有限的流量调用该服务,并监控调用成功率。如果成功率达到预期,则说明服务已恢复,进入熔断关闭状态;如果成功率仍旧很低,则重新进入熔断关闭状态。
1.3.5、降级机制
降级其实就是为服务提供一个兜底方案,一旦服务无法正常调用,就使用兜底方案。
1.4、常见容错组件
Hystrix | Resilience4J | Sentinel |
---|---|---|
Hystrix是由Netflflix开源的一个延迟和容错库,用于隔离访问远程系统、服务或者第三方库,防止级联失败,从而提升系统的可用性与容错性。 | Resilicence4J一款非常轻量、简单,并且文档非常清晰、丰富的熔断工具,这也是Hystrix官方推荐的替代产品。不仅如此,Resilicence4j还原生支持Spring Boot 1.x/2.x,而且监控也支持和prometheus等多款主流产品进行整合。 | Sentinel 是阿里巴巴开源的一款断路器实现,本身在阿里内部已经被大规模采用,非常稳定。 |
1.5、Sentinel
1.5.1、什么是Sentinel
Sentinel (分布式系统的流量防卫兵) 是阿里开源的一套用于服务容错的综合性解决方案。它以流量为切入点, 从流量控、熔断降级、系统负载保护等多个维度来保护服务的稳定性。
1.5.2、Sentinel特点
- 丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景, 例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
- 完备的实时监控:Sentinel 提供了实时的监控功能。通过控制台可以看到接入应用的单台机器秒级数据, 甚至 500 台以下规模的集群的汇总运行情况。
- 广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块, 例如与 SpringCloud、Dubbo、gRPC 的整合。只需要引入相应的依赖并进行简单的配置即可快速地接入Sentinel。
- 完善的SPI扩展点:Sentinel提供了简单易用、完善的SPI扩展接口。您可以通过实现扩展接口来快速的定制逻辑。例如定制规则管理、适配动态数据源等。
1.5.3、Sentinel组成部分
Sentinel分为两部分:
- 核心库(Java 客户端)不依赖任何框架/库,能够运行于所有 Java 运行时环境,同时对 Dubbo /Spring Cloud 等框架也有较好的支持。
- 控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器。
1.5.4、Sentinel架构
【API调用】常见api
# 接口概览
http://localhost:8720/api
# 获取资源的metrics信息
http://localhost:8720/cnode?id=接口路径
# 获取流控规则接口
http://localhost:8720/getRules?type=flow
# 设置规则接口
http://localhost:8720/setRules
二、安装部署Dashboard
下载jar包
https://github.com/alibaba/Sentinel/releases/tag/v1.8.0
启动控制台
# 直接到jar包目录下,使用jar命令启动项目(控制台本身是一个SpringBoot项目)
java -Dserver.port=8088 -Dcsp.sentinel.dashboard.server=localhost:8088 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.0.jar
页面访问
# http://localhost:8088/#/login
# 默认用户名密码是 sentinel/sentinel
三、快速开始
-
添加依赖
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency>
-
配置文件
spring: application: name: cloud-order # 服务名 cloud: nacos: discovery: server-addr: localhost:8848 # 指定nacos-server的地址 username: nacos password: nacos namespace: dev sentinel: transport: port: 8719 # 默认8719(当前服务,对sentinel提供的,调用端口号,用于监控服务访问数据) dashboard: localhost:8888 # dashboard地址 eager: true # 启动项目是自动注入到sentinel中 server: port: 9003
启动项目,访问http://localhost:8088即可看到,已经注册进去了
四、Sentinel规则操作
【流控】:当满足被流控规则,控制流量 — api层
【熔断降级】:当满足规则,走备用方案 — api层
【热点】:细化到接口的参数,当携带指定参数时,对其做QPS限制 — api的参数层
【系统规则】: 对整个服务做流控— 整个服务层
【授权】:配置当前服务,那些服务能调用(白名单)、哪些服务不能调用(黑名单)。
4.1、流控
【流控】:当满足被流控规则,控制流量 — api层
4.1.1、针对来源
针对某个服务做流控
default代表对所有服务的调用做流控
4.1.2、流控规则
-
QPS
每秒请求数
-
线程数
同时请求的线程数量
4.1.3、流控模式
-
直接(默认)
接口达到限流条件时,开启限流
-
关联
当关联的资源达到限流条件时,开启限流 [适合做应用让步]
-
链路
当从某个接口,进入当前资源达到限流条件时,开启限流
4.1.4、流控效果
-
快速失败(默认)
直接失败,抛出异常,不做任何额外的处理,是最简单的效果
-
Warm Up
它从开始阈值到最大QPS阈值会有一个
缓冲
阶段,一开始的阈值是最大QPS阈值的1/3,然后慢慢增长,直到最大阈值,适用于将突然增大的流量转换为缓步增长的场景。eg:预热时长填10,QPS填900,那么如果一下进来900,会立即完成300,剩下的在10s内完成
-
排队等待
让请求以
均匀
的速度通过,单机阈值为每秒通过数量,其余的排队等待; 它还会让设置一个超时时间,当请求超过超时间时间还未处理,则会被丢弃。
4.2、熔断降级
【熔断降级】:当满足规则,走备用方案 — api层
4.2.1、慢调用比例
上面配置表示
在【统计时长】内,请求数超过【最小请求数】,并且请求中响应时间大于【最大RT】的比例超过【比例阈值】,就会触发熔断,在【熔断时长内】直接走降级方法。
4.2.2、异常比例
上面配置表示
在【统计时长】内,请求数超过【最小请求数】,并且异常比例超过【比例阈值】,就会触发熔断,在【熔断时长内】直接走降级方法。
4.2.3、异常数
上面配置表示
在【统计时长】内,请求数超过【最小请求数】,并且异常数超过【异常数】,就会触发熔断,在【熔断时长内】直接走降级方法。
4.3、热点
【热点】:细化到接口的参数,当携带指定参数时,对其做QPS限制 — api的参数层
上面配置表示
请求该资源时,携带了第【参数索引】个参数,且在【统计窗口时长】内,QPS达到【单机阈值】则被流控
📢:资源需要使用注解@SentinelResource自定义才能生效
并且,由于使用了该注解,抛出的异常ParamFlowException,需要自己捕获
4.4、系统规则
【系统规则】: 对整个服务做流控— 整个服务层
系统规则支持以下的模式:
- 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 达到阈值即触发系统保护。
4.5、授权
【授权】:配置当前服务,那些服务能调用(白名单)、哪些服务不能调用(黑名单)。
上面配置表示
/auth/login接口,不允许cloud-order和cloud-jifen这两个服务调用
五、@SentinelResource及异常捕获
5.1、@SentinelResource使用
5.1、使用规则
【作用】
1、用在方法上,用于自定义资源名
2、在设置热点时,必须使用该注解自定义的资源名,不然不生效
@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。
属性 | 作用 |
---|---|
value | 资源名称,必需项(不能为空) |
entryType | entry 类型,可选项(默认为 EntryType.OUT ) |
blockHandler/ blockHandlerClass |
blockHandler 对应处理 BlockException 的函数名称,可选项。blockHandler 函数访问范围需要是 public ,返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为 BlockException 。blockHandler 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 blockHandlerClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。 |
fallback/ fallbackClass |
fallback 函数名称,可选项,用于在抛出异常的时候提供 fallback 处理逻辑。fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。fallback 函数签名和位置要求: 1. 返回值类型必须与原函数返回值类型一致; 2.方法参数列表需要和原函数一致,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。 3.fallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。 |
defaultFallback |
默认的 fallback 函数名称,可选项,通常用于通用的 fallback 逻辑(即可以用于很多服务或方法)。默认 fallback 函数可以针对所有类型的异常(除了 exceptionsToIgnore 里面排除掉的异常类型)进行处理。若同时配置了 fallback 和 defaultFallback,则只有 fallback 会生效。defaultFallback 函数签名要求: 1. 返回值类型必须与原函数返回值类型一致; 2. 方法参数列表需要为空,或者可以额外多一个 Throwable 类型的参数用于接收对应的异常。 3. defaultFallback 函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定 fallbackClass 为对应的类的 Class 对象,注意对应的函数必需为 static 函数,否则无法解析。 |
exceptionsToIgnore |
用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入 fallback 逻辑中,而是会原样抛出。 |
5.2、使用示例
使用@SentinelResource对流控、降级做自定义处理
-
方式一:写在同一个类里
@RestController @Slf4j public class AnnoController { @RequestMapping("/anno1") /** * 当访问资源出现【流控】,会进入 blockHandler 指定的方法去进行处理 * 当访问资源出现 异常/【熔断降级】,会进入到 fallback 去进行处理 */ @SentinelResource(value = "anno1", blockHandler="anno1BlockHandler", fallback = "anno1Fallback") public String anno1(String name){ if("wolfcode".equals(name)){ throw new RuntimeException(); } return "anno1"; } /** * 限流备用方法 */ public String anno1BlockHandler(String name, BlockException ex){ log.error("{}", ex); return "接口被限流了"; } /** * 异常或降级备用方法 */ public String anno1Fallback(String name, Throwable throwable) { log.error("{}", throwable); // 区分熔断降级和其他异常 if (throwable instanceof DegradeException) { return "熔断降级"; } return "非sentinel异常"; } }
-
写到别的类中
@RestController @Slf4j public class AnnoController { @RequestMapping("/anno1") /** * 当访问资源出现【限流】,会进入 BlockExceptionHandler类的anno1BlockHandler方法 */ @SentinelResource(value = "anno1", blockHandler="anno1BlockHandler", blockHandlerClass="BlockExceptionHandler", fallback = "anno1Fallback", fallbackClass = "DegradeExceptionHandler" ) public String anno1(String name){ if("wolfcode".equals(name)){ throw new RuntimeException(); } return "anno1"; } }
-
限流备用方法
public class BlockExceptionHandler { /** * 静态方法 */ public static String anno1BlockHandler(String name, BlockException ex){ log.error("{}", ex); return "接口被限流了"; } public static String anno1Fallback(String name, Throwable throwable) { log.error("{}", throwable); // 区分熔断降级和其他异常 if (throwable instanceof DegradeException) { return "熔断降级"; } return "非sentinel异常"; } }
-
异常/降级备用方法
public class DegradeExceptionHandler { /** * 静态方法 */ public static String anno1Fallback(String name, Throwable throwable) { log.error("{}", throwable); // 区分熔断降级和其他异常 if (throwable instanceof DegradeException) { return "熔断降级"; } return "非sentinel异常"; } }
-
5.2、全局异常捕获
给每个方法都写一个对应的兜底方法,效率太低了
可以使用全局异常捕获对所有方法做兜底
5.2.1、Sentinel异常
5.2.2、全局异常处理
@Slf4j
@ControllerAdvice
public class MyResponseBodyAdvice implements ResponseBodyAdvice<Object> {
/**
* 流控处理
*/
@ExceptionHandler(FlowException.class)
@ResponseStatus(HttpStatus.OK)
@ResponseBody // 该注解用于把对象转为json
public OperationInfo handlerFlowException(HttpServletRequest request, HttpServletResponse response, Exception ex) {
log.info("{}, 流控, {}", request.getRequestURI(), LogUtil.getStack(ex));
return OperationInfo.failure("流控");
}
/**
* 熔断降级处理
*/
@ExceptionHandler(DegradeException.class)
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public OperationInfo handlerDegradeException(HttpServletRequest request, HttpServletResponse response, Exception ex) {
log.info("{}, 熔断降级, {}", request.getRequestURI(), LogUtil.getStack(ex));
return OperationInfo.failure("熔断降级");
}
// 其他略。。。。。。
}
六、规则持久化
6.1、默认模式
【规则】:如果不做任何修改,Dashboard的推送规则方式是通过API将规则推送至客户端,并直接更新到内存中
【好处】:简单、无依赖
【坏处】:应用重启规则会消失,仅用于测试,不能用于生产环境
6.2、Pull模式
【规则】
FileRefreshableDataSource定时从指定文件中读取规则JSON文件【本地文件】,如果发现文件发生变化,就更新规则缓存
FileRefreshableDataSource接收控制台规则推送,并根据配置,修改规则JSON【本地文件】
【优点】
能持久化配置的规则
【缺点】
服务部署在多台服务器上,无法共享本地文件
6.3、Push模式
【规则】
将规则持久化在配置中心中,Sentinel把规则发送到配置中心,各个服务从配置中心中拿对应的规则
6.3.1、Sentinel Dashboard代码改造
-
Sentinel Dashboard默认是往Sentinel客户端发送,需要修改为往 配置中心发送
修改源码之后,重新打成jar包使用
6.3.2、微服务端
在要做Sentinel控制的服务里面配置
-
添加依赖
<!-- sentinel --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <!--sentinel的nacos持久化--> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>spring-datasource-nacos</artifactId> </dependency>
-
添加配置
spring: cloud: nacos: config: server-addr: localhost:8848 namespace: dev group: DEFAULT_GROUP prefix: cloud-order file-extension: yml username: nacos password: nacos sentinel: transport: port: 8730 # 默认8719(当前服务对sentinel提供访问的端口号) dashboard: localhost:8088 # dashboard地址 eager: true # 启动项目是自动注入到sentinel中 web-content-unify: false datasource: flow: nacos: server-addr: {nacos.server-addr} username: {nacos.username} password: {nacos.password} namespace: {nacos.namespace} groupId: DEFAULT_GROUP dataId: ${spring.application.name}-flow-rules data‐type: json rule‐type: flow degrade: nacos: server-addr: {nacos.server-addr} username: {nacos.username} password: {nacos.password} namespace: {nacos.namespace} groupId: DEFAULT_GROUP dataId: ${spring.application.name}-degrade-rules data‐type: json rule‐type: degrade param-flow: nacos: server-addr: {nacos.server-addr} username: {nacos.username} password: {nacos.password} namespace: {nacos.namespace} groupId: DEFAULT_GROUP dataId: ${spring.application.name}-param-flow-rules data‐type: json rule‐type: param-flow system: nacos: server-addr: {nacos.server-addr} username: {nacos.username} password: {nacos.password} namespace: {nacos.namespace} groupId: DEFAULT_GROUP dataId: ${spring.application.name}-system-rules data‐type: json rule‐type: system authority: nacos: server-addr: {nacos.server-addr} username: {nacos.username} password: {nacos.password} namespace: {nacos.namespace} groupId: DEFAULT_GROUP dataId: ${spring.application.name}-authority-rules data‐type: json rule‐type: authority nacos: server-addr: localhost:8848 username: nacos password: nacos namespace: sentinel