什么是熔断器?
限流在前面的章节已经知道了,现在谈谈熔断器
熔断器起的作用就是在特定的场景下关掉当前的通路,从而起到保护整个系统的效果
这张图需要注意的是,绿色表示接通电路或者服务健康,红色表示熔断或者服务负载
一次服务请求访问可能需要涉及到 服务A 而 服务A 又需要 服务B,C,D 三个服务器提供支持(微服务链路),如果 服务D 负载,请求延迟过高导致该请求阻塞等待服务D的响应
又请求的线程是珍贵资源,有限的,如果不将服务D熔断掉,并立即反馈失败,线程将被全部占用。
这种情况国内较雪崩效应(去你丫的雪崩效应),国外叫 cascading failures(级联故障)
吐槽国内某些砖家别的不会,争KPI搞高大上的名字最牛 国内好像是2012年企鹅某高官首先提出的新名字 我当初学习的时候,就一直纠结微服务链路故障怎么就跟雪崩效应联系在一起了?雪崩效应不是加密算法的一种理想属性么?
而熔断器就是为了防止出现级联故障而存在的。。。
功能对比
市面上的断路器就三种: Sentinel、Hystrix、resilience4j
其中前文说过的,Hystrix已经进入维护,基本不考虑使用了
而resilience4j国外推荐的比较多,国内主要使用阿里的Sentinel
| Sentinel | Hystrix(维护状态) | Resilience4j(Spring推荐) | |
|---|---|---|---|
| 开发者 | alibaba | Netflix | 独立 |
| 隔离策略 | 信号量隔离(并发线程数限流) | 线程池隔离/信号量隔离 | 信号量隔离 |
| 熔断降级策略 | 基于响应时间、异常比率、异常数 基于异常比率 | 基于异常比率 | 基于异常比率、响应时间 |
| 实时统计实现 | 滑动窗口(LeapArray) | 滑动窗口(基于 RxJava) | Ring Bit Buffer |
| 动态规则配置 | 支持多种数据源 | 支持多种数据源 | 有限支持 |
| 扩展性 | 多个扩展点 | 插件的形式 | 接口的形式 |
| 基于注解的支持 | 支持 | 支持 | 支持 |
| 限流 | 基于 QPS,支持基于调用关系的限流 | 有限的支持 | Rate Limiter |
| 流量整形 | 支持预热模式、匀速器模式、预热排队模式 | 不支持 | 简单的 Rate Limiter 模式 |
| 系统自适应保护 | 支持 | 不支持 | 不支持 |
| 控制台 | 提供开箱即用的控制台,可配置规则、查看秒级监控、机器发现等 | 简单的监控查看 | 不提供控制台,可对接其它监控系统 |
我们主要学习 sentinel
从:
- Sentinel是什么?
- Sentinel有什么用?
- 怎么用sentinel?
- sentinel如何实现?(底层实现)
sentinel限流框架
sentinel是什么?
Sentinel 是面向分布式服务架构的流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。
Sentinel有功能?
Hystrix 主要是隔离和熔断,但是 Sentinel 侧重于
- 多样化的流量控制
- 熔断降级
- 系统负载保护
- 实时监控和控制台
记住,限流和降级都是以保护核心业务为目的的策略,除核心外的业务在一定阈值内保证可用。
什么是流量控制?
对随机到来的请求,sentinel根据系统处理能力对流量进行规整。
sentinel将java应用的任何内容认为是资源(比如:应用程序提供的服务、应用程序调用其他应用提供的服务、甚至是一段代码),然后在资源外边围绕一圈规则(流量控制规则、熔断降级规则或系统保护规则)以保护资源,并且所有规则都可以实时动态调整。
什么是sentinel的熔断降级?
sentinel的熔断降级实现方式和 Hystrix 的实现不同。
Hystrix通过线程隔离实现对资源的隔离,这样的好处在于比较彻底,缺点也很明显线程池线程过多导致线程上下文切换频繁,还需要预先设定线程池大小。
Sentinel的实现方式就有两种思路:
- 对每个资源设定一个阈值,如果该资源阻塞导致线程数量超过该阈值,资源就会拒绝新的请求,直到该资源的线程完成任务之后,才可以重新接受新的线程。这种方式的好处在于,不会有频繁的线程上下文切换,也不需要事先设定好线程池的大小,只要累计堆积线程的数量就行。
- 对资源进行定时。Sentinel 通过响应时间来快速降级不稳定的资源。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。
Hystrix的线程隔离:针对不同的资源分别创建不同的线程池,不同服务调用都发生在不同的线程池中,在线程池排队、超时等阻塞情况时可以快速失败,并可以提供 fallback 机制。线程池隔离的好处是隔离度比较高,可以针对某个资源的线程池去进行处理而不影响其它资源,但是代价就是线程上下文切换的 overhead 比较大,特别是对低延时的调用有比较大的影响。
系统自适应保护
sentinel会保持外部流量和内部系统处理能力之间的平衡。在分布式系统中,如果某个资源已经负载,分布式系统就会将该次请求转发给其他资源,但其他资源正好也在边缘状态,此时就会导致两个资源都会负载,导致集群不可用。针对这个情况,Sentinel 提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。
怎么安装sentinel控制台?
什么是Sentinel 控制台?
Sentinel 提供一个轻量级的开源控制台,它提供机器发现以及健康情况管理、监控(单机和集群),规则管理和推送的功能。
Sentinel 控制台包含如下功能:
查看机器列表以及健康情况:收集 Sentinel 客户端发送的心跳包,用于判断机器是否在线。
监控 (单机和集群聚合):通过 Sentinel 客户端暴露的监控 API,定期拉取并且聚合应用监控信息,最终可以实现秒级的实时监控。
规则管理和推送:统一管理推送规则。
鉴权:生产环境中鉴权非常重要。这里每个开发者需要根据自己的实际情况进行定制。
注意:Sentinel 控制台目前仅支持单机部署。Sentinel 控制台项目提供 Sentinel 功能全集示例,不作为开箱即用的生产环境控制台,不提供安全可靠保障。若希望在生产环境使用请根据文档自行进行定制和改造。
启动控制台
- 下载
sentineljar 包 sentinel笔者使用的是1.8.5版本 - 启动控制台
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar - 访问
[http://localhost:8080/](http://localhost:8080/)
其中
-Dserver.port=8080用于指定 Sentinel 控制台端口为8080。 从 Sentinel 1.6.0 起,Sentinel 控制台引入基本的登录功能,默认用户名和密码都是sentinel。 用户可以通过如下参数进行配置:-Dsentinel.dashboard.auth.username=sentinel用于指定控制台的登录用户名为sentinel;-Dsentinel.dashboard.auth.password=123456用于指定控制台的登录密码为123456;如果省略这两个参数,默认用户和密码均为sentinel;-Dserver.servlet.session.timeout=7200用于指定 Spring Boot 服务端 session 的过期时间,如7200表示7200 秒;60m表示60 分钟,默认为 30 分钟; 同样也可以直接在Spring properties文件中进行配置。 注意:部署多台控制台时,session默认不会在各实例之间共享,这一块需要自行改造。
创建项目
cloudalibaba-sentinel-service8401
<dependencies>
<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-sentinel</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--日常通用jar包配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.5</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
server-addr: localhost:8848
sentinel:
transport:
dashboard: localhost:8080
port: 8719
management:
endpoints:
web:
exposure:
include: '*'
package com.zhazha.alibaba;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@SpringBootApplication
public class SentinelMainApp8401 {
public static void main(String[] args) throws Exception {
SpringApplication.run(SentinelMainApp8401.class, args);
}
}
package com.zhazha.alibaba.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class FlowLimitController {
@GetMapping("/testA")
public String testA() {
return "------testA";
}
@GetMapping("/testB")
public String testB() {
return "------testB";
}
}
启动项目后,我们不会看到 cloudalibaba-sentinel-service这个项目出现在sentinel控制台左侧
sentinel控制台是懒加载的,如果不访问一次,控制台就不会主动去捕获信息
需要主动一次访问:http://localhost:8401/testA
sentinel控制台详情
流控规则
sentinel默认监控的是SpringMVC的Mapping也就是我们常说的 controller 层,sentinel将这视为资源。
簇点链路显示了那些被sentinel视为资源的地址,如下图:
注意: 簇点链路监控是内存态的信息,它仅展示启动后调用过的资源。所以如果打开
sentinel控制台后发现这里没有我们需要的资源,比如:testA,可以在浏览器上访问一次,就会出现了
我们可以为 testA 资源添加流控:
接着我们下载JMeter工具。该工具主要用来发送请求使用。
打开工具后会发现全是英文,如果阅读比较困难可以打开【option】——【choose language】选择简体中文即可
现在开始模拟多客户访问网站
- 创建【测试计划】。点击【文件】——【新建】,还可以修改下名称【sentinel测试计划】——
ctrl+s保存,选择存储路径即可。 - 右键【sentinel测试计划】——【添加】——【线程】——【线程组】——
- 创建http请求
- 添加路径:
- 添加结果树:
- 直接启动
- 打开结果树:
我们的中线程数为:20,两秒内执行完,qps计算可得:20/2=10,明显大于我们设定的qps=5
流控规则的模式
流控模式有三种:
- 直接:统计当前资源的请求,触发阈值时限流,比如上面我们访问
/testA资源一样 - 关联:统计关联资源(
B)的请求,触发阈值时限流A,简单点说就是统计B限流A - 链路:统计指定链路访问到本资源的请求,触发阈值,限流整个链路
关联的使用场景
它的单元是 接口,讲的是 接口与接口之间 的关联关系
用户有无数个请求访问 /textA 和 /textB,他们之间共同竞争
现在我们要保证 /textB能够得到尽量多的资源要怎么做?
非常简单。
- 我们假设
/textB更加重要(根据实际项目判定那个资源更加重要) - 之后再对
/textA限流 - 然后sentinel就会统计
/textB是否超出阈值,来限流/textA,从而让出更多的数据库某表给textB访问
超出表示
/textB现在需要更多的资源,此时就需要让testA让出更多的请求
关联的使用场景:
- 两个资源有竞争关系
- 一个优先级高,一个优先级低
- 需要保证其中 一个重要的请求获得更多的资源
添加代码
@GetMapping("query")
public String query() {
return "查看订单成功";
}
@GetMapping("update")
public String update() {
return "更新订单成功";
}
需要注意这里是
put请求不是get
访问:http://localhost:8401/query
可以看到 query请求被限流
链路的使用场景
链路的单元是一条调用链,讲的是调用链之间的关系
在上面的调用链路中,作为入口的 testA和testC 最终都会调用到goods资源
现在的问题是哪个调用链路的重要性较高,假设 testC 调用链路重要性更加重要
那么就需要在 sentinel 控制台中添加以 testA作为入口的调用链路
- 两个链路(或者多条)
- 一个链路优先级高,另一个链路优先级低
- 两个链路有相交的资源
还是支付和查询订单,这两个业务都需要在底层调用OrderService的查询订单信息方法。
但是我们知道 sentinel 默认监控 SpringMVC 的 Mapping
所以我们可以在OrderService方法上面添加SentinelResource注解,将方法标记为 资源
public interface OrderService {
void queryOrder();
}
@Service
public class OrderServiceImpl implements OrderService {
@SentinelResource(value = "goods")
@Override
public void queryOrder() {
System.out.println("查询订单");
}
}
@RestController
public class OrderController {
@Resource
private OrderService orderService;
@GetMapping("query")
public String query() {
orderService.queryOrder();
return "查看订单成功";
}
@GetMapping("update")
public String update() {
orderService.queryOrder();
return "更新订单成功";
}
}
接着我们访问下链接
但是我们发现还是只有一个链路:
这是有问题的,官网也说了,application.yml需要添加:
spring:
cloud:
sentinel:
web-context-unify: false
再次试试
这样我们就能看到两棵不同的树
接着我们给 goods 资源添加流控
注意这里的qps是2
添加jmeter
qps=4
然后添加两个 http 请求:
访问:http://localhost:8401/query
jmeter也报错:
流控效果
流控效果是请求达到流量阈值是所采取的策略是什么?
- 快速失败:达到阈值,新的请求将被拒绝并抛出FlowException异常(默认)
- 预热模式(warm up):对超出阈值的请求也会抛出异常,但是这种默认的阈值是动态的,会慢慢的将阈值调高直到最大阈值为止
- 排队等待:让所有的请求按照先后顺序排序,每个请求的间隔不能小于指定时间
预热模式
预热模式下,需要设置qps阈值和预热时长
这里我们设置 qps = 10 预热时长 = 5
这样 sentinel 将在不需要预热是 qps 保持为 10 / 3 = 3
这里的除数是 sentinel 内部默认值 3
预热请求超出qps = 3 则 sentinel 会逐步预热,直到5秒后的最大值,中间会拒绝掉部分请求
这就是预热模式
threshold / coldFactor = 初始qps coldFactor默认值为 3 threshold是最大qps
设置jmeter:
起步就是 3 qps
可以看到成功的请求逐渐变高
使用场景
预热模式的使用场景一般是秒杀活动
热点参数限流
热点:统计访问数量较大的节点
参数:针对参数
限流:使用的操作
说白了,就是分别统计参数值相同的请求,判断是否超出QPS阈值。
如果不懂,还可以看下下面这张图示
sentinel对热点参数限流为 QPS = 5,然后总共发了 18 个请求,其中10个请求是非热点接口的,所以可以不看
热点请求有8个
参数 axb 的请求在一秒钟发生了5次,大于等于qps阈值5,所以将被阻塞
参数 abc 的请求在一秒内发生了3次,没有超出阈值,请求通过。
还可以使用 sentinel控制台再次说明:
如上图所示,我们设置了资源hot
参数索引为 0 表示 hot接口的第一个参数
单机阈值为5,统计窗口时长为1,表示1秒内相同参数额请求数不能超过5个
比如 hot?id=1请求在一秒的时间内发起了10次,那么请求就会被阻塞
此时前面的那一秒时间可能还发起了别的请求,比如
hot?id=2发起了3次,此时并不会阻塞,虽然接口相同,但参数不同 热点参数限流针对的颗粒度是 接口+参数,接口相同,参数不同,意味着不同的请求,不会被 热点参数限流规则 统计
在springboot中试试
添加代码:
@GetMapping("hot")
public String hot(long id) {
return "热点参数: " + id;
}
然后设置 jmeter:
id=1配置的线程组是
id=2配置的线程组是
同时启动两个线程组,结果发现全部都是成功,没有限流效果?
限流不成功?
其原因很简单,SpringMVC的@Controller注解的资源不被热点流量限流发现
需要主动给他们添加注解@SentinelResource("hot")
注意了。热点参数限流默认对SpringMVC资源无效。
@GetMapping("hot")
@SentinelResource("hot")
public String hot(long id) {
return "热点参数: " + id;
}
对
添加【热点规则】
id=1结果,部分成功
id=2结果,全部成功
说明 id=1和 id=2同时启动,不会产生任何的相互影响
参数例外项
热点限流,对该接口的热点参数进行限流,但需要有例外情况。
也就是说这个参数需要进行特殊处理,即便它是热点参数,但不能也不需要限流。
比如:某款包款热卖利润极其高的商品,我们就不能进行热点参数限流。
上图我们重新设置了 id=10的这款热点商品的例外项,将其qps上调到每秒10个请求。
添加jmeter测试:
http://localhost:8401/hot?id=10
"实时监控"汇总资源信息(集群聚合)
同时,同一个服务下的所有机器的簇点信息会被汇总,并且秒级地展示在"实时监控"下。
注意: 实时监控仅存储 5 分钟以内的数据,如果需要持久化,需要通过调用实时监控接口来定制。
注意:请确保 Sentinel 控制台所在的机器时间与自己应用的机器时间保持一致,否则会导致拉不到实时的监控数据。
规则管理及推送
本页面相关文档在 :
sentinel默认使用的是原始模式,这种模式的配置只保存在内存中,在测试环境可以用用,但是在生产环境就不能这么玩了
Sentinel 控制台同时提供简单的规则管理以及推送的功能。规则推送分为 3 种模式,包括 "原始模式"、"Pull 模式" 和"Push 模式"。
这里先简单的介绍"原始模式"。
sentinel默认是原始模式,该模式API 将规则推送至客户端并直接更新到内存中,简单,无任何依赖,但不保证一致性;规则保存在内存中,重启即消失。严重不建议用于生产环境
DataSource 接口给我们提供了对接任意配置源的能力。相比直接通过 API 修改规则,实现 DataSource 接口是更加可靠的做法。
我们推荐通过控制台设置规则后将规则推送到统一的规则中心,客户端实现 ReadableDataSource 接口端监听规则中心实时获取变更,流程如下:
DataSource 扩展常见的实现方式有:
- 拉模式:客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件,甚至是 VCS 等。这样做的方式是简单,缺点是无法及时获取变更;
- 推模式:规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。
Sentinel 目前支持以下数据源扩展:
- Pull-based: 动态文件数据源、Consul, Eureka
- Push-based: ZooKeeper, Redis, Nacos, Apollo, etcd
拉模式
pull 模式的数据源(如本地文件、RDBMS 等)一般是可写入的。使用时需要在客户端注册数据源:将对应的读数据源注册至对应的 RuleManager,将写数据源注册至 transport 的 WritableDataSourceRegistry 中。
首先 Sentinel 控制台通过 API 将规则推送至客户端并更新到内存中,接着注册的写数据源会将新的规则保存到本地的文件中。使用 pull 模式的数据源时一般不需要对 Sentinel 控制台进行改造。这种实现方法好处是简单,坏处是无法保证监控数据的一致性。
实现拉模式的数据源最简单的方式是继承 AutoRefreshDataSource 抽象类,然后实现 readSource() 方法,在该方法里从指定数据源读取字符串格式的配置数据。比如 基于文件的数据源。
隔离和降级
服务之间是相互依赖的,虽然限流可以保证服务不会因高并发产生异常,但无法保证服务不会因为其他原因而产生故障。所以需要其他手段将这些故障控制台一定的范围内,防止服务级联故障(服务雪崩)。
而这些手段大体上包括这几种:
- 线程隔离:给每个服务专门提供一个线程池,线程池线程数量不能太高。这样出现问题 ,也仅仅只会有部分线程被阻塞。不会导致服务级联故障。
- 熔断:判断服务的故障率,根据故障情况,对该服务进行熔断。下次请求再次访问到该服务时,就会被立即返回。
- 限流:通过对并发访问进行限速,保证其他功能拿到更多的系统资源。
需要注意:降级是限流的一种行为(方案)
限流的方案:
- 拒绝服务:最简单的方式,把多余的请求直接拒绝掉。做的高大上一些,可以根据一定的用户规则进行拒绝策略。
- 服务降级:降级甚至关掉后台的某些服务。在服务必定出现高并发的情况下,比如双11。可以预先停止掉部分不重要的服务,并将部分强一致性的服务修改为最终一致性。(需要对业务有一定的了解)【弃卒保帅】
- 特权请求: 在多租户或者对用户进行分级时,可以考虑让一些特殊的用户有限处理,其他的可以考虑干掉
- 延迟处理:可以利用队列把请求缓存住。削峰填谷。
从上面提供的方案我们可以发现,都是对服务的调用方进行某种操作以保证服务高可用。服务的调用方也就是客户端在springcloud中一般使用feign进行调用,所以对于SpringCloud整合Sentinel,就需要对feign进行操作