目录
1.0. Sentinel核心库
1.1.Sentinel介绍
1.2.Sentinel核心功能
1.2.1.流量控制
1.2.2.熔断降级
1.3.Sentinel熔断限流
1.3.1.1.@SentinelResource注解
1.3.1.2.blockHandler
1.3.1.4.defaultFallback
1.3.2.限流降级规则
1.3.2.1.流量控制
1.3.2.2.熔断降级
1.3.2.3.系统自我保护
1.3.3.1.fallback
2.0.Gateway集成
2.1.Sentinel网关支持
2.2.集成Gateway
2.3.配置/API定义
3.0.Sentinel控制台
3.1.安装
3.2.接入控制台
3.3.可视化管理
3.3.1.实时监控
3.3.2.熔断规则
3.3.3.热点数据
3.3.3.1.流控规则-关联
3.3.3.2.流控规则-链路
3.3.3.3.流控规则-Warm up
3.3.3.4.流控规则-授权
1.0. Sentinel核心库
Sentinel主页 https://github.com/alibaba/Sentinel/wiki/%E4%B8%BB%E9%A1%B5
1.1.Sentinel介绍
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel是面向分布式服务架构的流量控制组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。
1)Sentinel核心组件
1:核心库(Java 客户端):不依赖任何框架/库,能够运行于 Java 7 及以上的版本的运行时环境,同时对 Dubbo / Spring Cloud 等框架也有较好的支持。 2:控制台(Dashboard):控制台主要负责管理推送规则、监控、集群限流分配管理、机器发现等。 |
2)Sentinel vs Hystrix
对比内容 |
Sentinel |
Hystrix |
隔离策略 |
信号量隔离 |
线程池隔离/信号量隔离 |
熔断降级策略 |
基于响应时间或失败比率 |
基于失败比率 |
实时指标实现 |
滑动窗口 |
滑动窗口(基于RxJava) |
规则配置 |
支持多种数据源 |
支持多种数据源 |
扩展性 |
多个扩展点 |
插件的形式 |
基于注解的支持 |
支持 |
支持 |
限流 |
基于QPS,支持基于调用关系的限流 |
不支持 |
流量整形 |
支持慢启动、匀速器模式 |
不支持 |
系统负载保护 |
支持 |
不支持 |
控制台 |
开箱即用,可配置规则、查看秒级监控、机器发现等 |
不支持 |
常见框架的适配 |
Servlet、Spring Cloud、Dubbo、gRPC等 |
Servlet、Spring Cloud Netflix |
|
|
|
3)Sentinel基本概念
• 资源
资源是Sentinel的关键概念。它可以是Java应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。 只要通过Sentinel API定义的代码,就是资源,能够被Sentinel保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。 |
• 规则
围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。 |
4)Sentinel涉及依赖
<!-- sentinel --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> <version>2.2.9.RELEASE</version> </dependency>
<!-- sentinel-parameter --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-parameter-flow-control</artifactId> <version>1.8.5</version> </dependency>
<!-- Sentinel-gateway --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId> <version>1.8.5</version> </dependency> |
1.2.Sentinel核心功能
1.2.1.流量控制
流量控制在网络传输中是一个常用的概念,它用于调整网络包的发送数据。然而,从系统稳定性角度考虑,在处理请求的速度上,也有非常多的讲究。任意时间到来的请求往往是随机不可控的,而系统的处理能力是有限的。我们需要根据系统的处理能力对流量进行控制。 Sentinel作为一个调配器,可以根据需要把随机的请求调整成合适的形状,如下图所示:
流量控制有以下几个角度:
• 资源的调用关系,例如资源的调用链路,资源和资源之间的关系;
• 运行指标,例如 QPS、线程池、系统负载等;
• 控制的效果,例如直接限流、冷启动、排队等。
Sentinel的设计理念是让您自由选择控制的角度,并进行灵活组合,从而达到想要的效果。
1.2.2.熔断降级
1)什么是熔断降级
除了流量控制以外,及时对调用链路中的不稳定因素进行熔断也是Sentinel的使命之一。由于调用关系的复杂性,如果调用链路中的某个资源出现了不稳定,可能会导致请求发生堆积,进而导致级联错误。
Sentinel和Hystrix的原则是一致的: 当检测到调用链路中某个资源出现不稳定的表现,例如请求响应时间长或异常比例升高的时候,则对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联故障。
2)Sentinel熔断降级设计
Hystrix通过线程池隔离的方式,来对依赖(在Sentinel的概念中对应资源)进行了隔离。这样做的好处是资源和资源之间做到了最彻底的隔离。缺点是除了增加了线程切换的成本(过多的线程池导致线程数目过多),还需要预先给各个资源做线程池大小的分配。
Sentinel熔断降级设计:
并发线程数限制:和资源池隔离的方法不同,Sentinel通过限制资源并发线程的数量,来减少不稳定资源对其它资源的影响。这样不但没有线程切换的损耗,也不需要您预先分配线程池的大小。当某个资源出现不稳定的情况下,例如响应时间变长,对资源的直接影响就是会造成线程数的逐步堆积。当线程数在特定资源上堆积到一定的数量之后,对该资源的新请求就会被拒绝。堆积的线程完成任务后才开始继续接收请求。 响应时间降级:除了对并发线程数进行控制以外,Sentinel还可以通过响应时间来快速降级不稳定的资源。当依赖的资源出现响应时间过长后,所有对该资源的访问都会被直接拒绝,直到过了指定的时间窗口之后才重新恢复。 |
3)系统自适应保护
Sentinel同时提供系统维度的自适应保护能力。防止雪崩,是系统防护中重要的一环。当系统负载较高的时候,如果还持续让请求进入,可能会导致系统崩溃,无法响应。在集群环境下,网络负载均衡会把本应这台机器承载的流量转发到其它的机器上去。如果这个时候其它的机器也处在一个边缘状态的时候,这个增加的流量就会导致这台机器也崩溃,最后导致整个集群不可用。
针对这个情况,Sentinel提供了对应的保护机制,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。
1.3.Sentinel熔断限流
Sentinel可以简单的分为Sentinel核心库和Dashboard。核心库不依赖Dashboard,但是结合Dashboard可以取得最好的效果。我们先来学习Sentinel核心库的使用,后面再学习Dashboard使用。
创建测试项目producer与consumer项目学习Sentinel的应用。
consumer:
在bootstrap.yml配置nacos服务地址:
server: port: 10088 spring: application: name: consumer-server cloud: nacos: config: server-addr: 123.60.23.244:8848 discovery: server-addr: 123.60.23.244:8848
feign: compression: request: enabled: true mime-types: text/html,application/xml,application/json min-request-size: 2048 response: enabled: true
logging: level: dream.myself.consumer: debug |
consumer Feign调用接口及配置:
ProducerFeign:
import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(value = "producer-server") public interface ProducerFeign {
@GetMapping("/producer/status/{status}") String consumer(@PathVariable(value = "status") String status); } |
ConsumerController:
import dream.myself.consumer.api.ProducerFeign; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;
@RestController @RequestMapping("consumer") public class ConsumerController {
private ProducerFeign producer;
public ConsumerController(ProducerFeign producer) {
this.producer = producer; }
@GetMapping("getMessage") public String consumerMessage(){
return producer.consumer("ready"); } } |
ConsumerApplication:
@SpringBootApplication @EnableFeignClients(basePackages = "dream.myself.consumer.api") public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args); }
@Bean public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL; } } |
producer:
在bootstrap.yml配置nacos服务地址:
server: port: 10086 spring: application: name: producer-server cloud: nacos: config: server-addr: 123.60.23.244:8848 discovery: server-addr: 123.60.23.244:8848 |
ProducerController:
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.env.Environment; 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("producer") public class ProducerController {
private static final Logger LOGGER = LoggerFactory.getLogger(ProducerController.class);
private final Environment environment;
public ProducerController(Environment environment) {
this.environment = environment; }
@GetMapping("/status/{status}") String consumer(@PathVariable(value = "status") String status) {
String port = environment.getProperty("server.port"); LOGGER.info("server.port:" + port); return "Producer massage:" + status + ", Receive request server port:" + port; } } |
discovery表示可以发现服务,config表示可以配置服务信息。这两个值会默认访问localhost,nacas运行在本地时只需要配置一个即可,如果nacos运行在远端,需要将两个参数均配置远端IP地址,否则会一直报错查找本地localhost地址。
启动项目,可以看到producer与consumer都已经注册到nacos服务中去了。
在本地IDEA中可以看到项目中已经成功拉取了远端的nacos服务信息。
1.3.1.SpringBoot集成
如果在SpringBoot项目中使用Sentinel,首先需要引入spring-cloud-starter-alibaba-sentinel 依赖。
1.3.1.1.@SentinelResource注解
@SentinelResource用于定义资源,并提供可选的异常处理和fallback配置项。
@SentinelResource注解包含以下属性:
value |
资源名称,必须项(不能未空) |
blockHandler/blockHandlerClass |
blockHandler对应处理BlockException的函数名称,可选项。 ♞blockHandler函数访问范围需要是public; ♞返回类型需要与原方法相匹配,参数类型需要和原方法相匹配并且最后加一个额外的参数,类型为BlockException。 ♞ blockHandler函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定blockHandlerClass为对应的类的Class对象,注意对应的函数必需为static函数,否则无法解析。 |
fallback/fallbackClass |
fallback函数名称,可选项,用于在抛出异常的时候提供fallback处理逻辑。fallback函数可以针对所有类型的异常(除了exceptionsToIgnore 里面排除掉的异常类型)进行处理。fallback函数签名和位置要求: ♞返回值类型必须与原函数返回值类型一致; ♞方法参数列表需要和原函数一致,或者可以额外多一个Throwable类型的参数用于接收对应的异常。 ♞fallback函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定fallbackClass为对应的类的Class对象,注意对应的函数必需为static函数,否则无法解析。 |
defaultFallback(1.6.0开始) |
默认的fallback函数名称,可选项,通常用于通用的fallback逻辑 (即可以用于很多服务或方法)。默认fallback函数可以针对所有类 型的异常(除了exceptionsToIgnore里面排除掉的异常类型)进行处 理。若同时配置了fallback和defaultFallback,则只有 fallback 会生效。defaultFallback函数签名要求: ♞返回值类型必须与原函数返回值类型一致; ♞方法参数列表需要为空,或者可以额外多一个Throwable类型的参数用于接收对应的异常。 ♞ defaultFallback函数默认需要和原方法在同一个类中。若希望使用其他类的函数,则可以指定fallbackClass为对应的类的Class对象,注意对应的函数必需为static 函数,否则无法解析。 |
exceptionsToIgnore(1.6.0开始) |
用于指定哪些异常被排除掉,不会计入异常统计中,也不会进入fallback逻辑中,而是会原样抛出。 |
entryType |
entry类型,可选项(默认为EntryType.OUT) |
|
|
1.3.1.2.blockHandler
集成Sentinel使用@SentinelResource的blockHandler返回默认错误信息。在consumer项目中引入spring-cloud-starter-alibaba-sentinel依赖,依赖如下:
<!-- sentinel --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> <version>2.2.9.RELEASE</version> </dependency> |
我们为consumer先添加一个方法 blockExHandler()用来处理程序发生BlockException异常的时候,执行默认操作,代码如下:
@RestController @RequestMapping("consumer") public class ConsumerController {
private ProducerFeign producer;
public ConsumerController(ProducerFeign producer) {
this.producer = producer; }
@SentinelResource(value = "getMessage", blockHandler = "blockExHandler") @GetMapping("getMessage") public String consumerMessage() throws SystemBlockException {
String ready = producer.consumer("ready"); ready = null; if (StringUtils.isEmpty(ready)) {
throw new SystemBlockException("getMessage", "The server is busy. Please try again later."); } return ready; }
public String blockExHandler(BlockException exception) {
return exception.getRuleLimitApp(); } } |
在consumerMessage()方法添加一个@SentinelResource注解,用来标注资源,表示当前方法需要执行限流、降级。在注解中添加value属性,用来标注资源,这个资源就是@GetMapping("getMessage")中的参数,一定要设置一致否则Sentinel无法根据url资源监测到consumerMessage()方法。blockHandler用来表示当前方法发生BlockException异常的时候,将处理流程交给指定的方法blockExHandler()处理,blockExHandler()的参数列表和返回值必须与consumerMessage()方法一致,在参数列表的末尾多添加一个BlockException参数,此时blockExHandler()方法必须和抛出异常的方法在同一个类中,这是一种降级操作。
如果此时不在同一个类中,我们可以在@SentinelResource中添加blockHandlerClass属性,指定降级处理类的方法所在的类,代码如下:
@RestController @RequestMapping("consumer") public class ConsumerController {
private ProducerFeign producer;
public ConsumerController(ProducerFeign producer) {
this.producer = producer; }
@SentinelResource(value = "getMessage", blockHandler = "blockExHandler", blockHandlerClass = {BlockHandler.class}) @GetMapping("getMessage") public String consumerMessage() throws SystemBlockException {
String ready = producer.consumer("ready"); ready = null; if (StringUtils.isEmpty(ready)) {
throw new SystemBlockException("getMessage", "The server is busy. Please try again later."); } return ready; } } public class BlockHandler {
public static String blockExHandler(BlockException exception) {
return exception.getRuleLimitApp(); } } |
blockHandlerClass属性配置的类中属性方法必须是静态的,否则Sentinel无法找到blockExHandler()方法。
访问http://127.0.0.1:10088/consumer/getMessage
1.3.1.3.fallback
如果我们希望抛出任何异常都能处理,都能调用默认处理方法,而并非只是BlockException异常才调用,此时可以使用@SentinelResource的fallback属性,代码如下:
@RestController @RequestMapping("consumer") public class ConsumerController {
private ProducerFeign producer;
public ConsumerController(ProducerFeign producer) {
this.producer = producer; }
@SentinelResource(value = "getMessage", fallback = "fallExHandler") @GetMapping("getMessage") public String consumerMessage() {
String ready = producer.consumer("ready"); ready = null; if (StringUtils.isEmpty(ready)) {
throw new RuntimeException(""); } return ready; }
public String fallExHandler() {
return "The server is busy. Please try again later."; } } |
fallExHandler() 函数,函数签名与原函数一致或加一个Throwable类型的参数。
访问http://127.0.0.1:10088/consumer/getMessage
如果发生异常执行的方法和当前发生异常的方法不在同一个类中,可以使用@SentinelResource注解的fallbackClass实现,代码如下:
@RestController @RequestMapping("consumer") public class ConsumerController {
private ProducerFeign producer;
public ConsumerController(ProducerFeign producer) {
this.producer = producer; }
@SentinelResource(value = "getMessage", fallback = "fallExHandler", fallbackClass = {FallHandler.class}) @GetMapping("getMessage") public String consumerMessage() {
String ready = producer.consumer("ready"); ready = null; if (StringUtils.isEmpty(ready)) {
throw new RuntimeException(""); } return ready; } } public class FallHandler {
public static String fallExHandler() {
return "The server is busy. Please try again later."; } } |
同样fallExHandler() 这个函数必须是静态才能生效。
1.3.1.4.defaultFallback
上面无论是blockHandler还是fallback,每个方法发生异常,都要为方法独立创建一个处理异常的方法,效率非常低,我们可以使用@SentinelResource注解的defaultFallback属性,为一个类指定一个全局的处理错误的方法,代码如下:
@RestController @RequestMapping("consumer") @SentinelResource(defaultFallback = "defaultExFallback") public class ConsumerController {
private ProducerFeign producer;
public ConsumerController(ProducerFeign producer) {
this.producer = producer; }
@SentinelResource(value = "getMessage") @GetMapping("getMessage") public String consumerMessage() {
String ready = producer.consumer("ready"); ready = null; if (StringUtils.isEmpty(ready)) {
throw new RuntimeException(""); } return ready; }
public String defaultExFallback() {
return "The server is busy. Please try again later."; } } |
此时需要注意,defaultFallback属性指定的方法入参必须为空,最多可以增加一个异常对象。另外在需要做熔断降级的方法上面一定要指定资源名词,多个方法资源名字不同,否则Sentinel无法找到需要熔断降级的方法。
访问http://127.0.0.1:10088/consumer/getMessage
若blockHandler和fallback都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。若未配置 blockHandler、fallback 和 defaultFallback,则被限流降级时会将 BlockException 直接抛出(若方法本身未定义throws BlockException则会被JVM包装一层 UndeclaredThrowableException)。
1.3.2.限流降级规则
Sentinel支持多种限流规则,规则我们可以在代码中直接定义,规则属性如下:
Field |
说明 |
默认值 |
resource |
资源名,资源名是限流规则的作用对象 |
|
count |
限流阈值 |
|
grade |
限流阈值类型,QPS模式(1)或并发线程数模式(0) |
QPS模式 |
limitApp |
流控针对的调用来源 |
default,代表不区分调用来源 |
strategy |
调用关系限流策略:直接处理、链路、关联 |
根据资源本身(直接) |
controlBehavior |
流控效果(直接拒绝/WarmUp/匀速+排队等待),不支持按调用关系限流 |
直接拒绝 |
clusterMode |
是否集群限流 |
否 |
|
|
|
1.3.2.1.流量控制
理解上面规则的定义之后,我们可以通过调用FlowRuleManager.loadRules()方法来用硬编码的方式定义流量控制规则。
1)QPS流量控制
我们先实现流量基于QPS(Queries Per Second)控制,在consumer中添加如下方法加载限流规则,当ConsumerApplication初始化完成之后加载规则,代码如下:
@SpringBootApplication @EnableFeignClients(basePackages = "dream.myself.consumer.api") public class ConsumerApplication {
public static void main(String[] args) throws InterruptedException {
ConfigurableApplicationContext applicationContext = SpringApplication.run(ConsumerApplication.class, args); }
@Bean public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL; }
/*** * 初始化规则 */ @PostConstruct private void initFlowQpsRule() {
//规则集合 List<FlowRule> rules = new ArrayList<>(); //定义一个规则,添加资源名称 FlowRule rule = new FlowRule("getMessage"); // 设置阈值 rule.setCount(2); //设置限流阈值类型 rule.setGrade(RuleConstant.FLOW_GRADE_QPS); //default,代表不区分调用来源 rule.setLimitApp("default"); //将定义的规则添加到集合中 rules.add(rule); //加载规则 FlowRuleManager.loadRules(rules); } } |
注意这里这是的资源必须要是真实存在的url访问资源路径名称。
ConsumerController:
@RestController @RequestMapping("consumer") @SentinelResource(defaultFallback = "defaultExFallback") public class ConsumerController {
private ProducerFeign producer;
public ConsumerController(ProducerFeign producer) {
this.producer = producer; }
@SentinelResource(value = "getMessage") @GetMapping("getMessage") public String consumerMessage() throws InterruptedException {
String ready = producer.consumer("ready"); if (StringUtils.isEmpty(ready)) {
throw new RuntimeException(""); } return ready; }
public String defaultExFallback() {
return "The server is busy. Please try again later."; } } |
访问http://127.0.0.1:10088/consumer/getMessage
当QPS达到阈值时就会触发熔断降级函数defaultExFallback()。
2)线程数流量控制
我们修改限流阈值类型,代码如下:
@SpringBootApplication @EnableFeignClients(basePackages = "dream.myself.consumer.api") public class ConsumerApplication {
public static void main(String[] args) throws InterruptedException {
ConfigurableApplicationContext applicationContext = SpringApplication.run(ConsumerApplication.class, args); }
@Bean public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL; }
/*** * 初始化规则 */ @PostConstruct private void initFlowQpsRule() {
//规则集合 List<FlowRule> rules = new ArrayList<>(); //定义一个规则,添加资源名称 FlowRule rule = new FlowRule("getMessage"); // 设置阈值 rule.setCount(2); //设置限流阈值类型 rule.setGrade(RuleConstant.FLOW_GRADE_THREAD); //default,代表不区分调用来源 rule.setLimitApp("default"); //将定义的规则添加到集合中 rules.add(rule); //加载规则 FlowRuleManager.loadRules(rules); } } |
借助Jmeter模拟多个线程访问http://127.0.0.1:10088/consumer/getMessage
1.3.2.2.熔断降级
熔断降级规则包含下面几个重要的属性:
|
|
|
Field |
说明 |
默认值 |
resource |
资源名,即规则的作用对象 |
|
grade |
熔断策略,支持慢调用比例/异常比例/异常数策略 |
慢调用比例 |
count |
慢调用比例模式下为慢调用临界 RT(超出该值计为慢调用);异常比例/异常数模式下为对应的阈值 |
|
timeWindow |
熔断时长,单位为s |
|
minRequestAmount |
熔断触发的最小请求数,请求数小于该值时即使异常比率超出阈值也不会熔断(1.7.0 引入) |
5 |
statIntervalMs |
统计时长(单位为 ms),如 60*1000 代表分钟级(1.8.5引入) |
1000ms |
slowRatioThreshold |
慢调用比例阈值,仅慢调用比例模式有效(1.8.5引入) |
|
同一个资源可以同时有多个降级规则。理解上面规则的定义之后,我们可以通过调用DegradeRuleManager.loadRules() 方法来用硬编码的方式定义流量控制规则,在ConsumerApplication中定义规则定义如下:
@SpringBootApplication @EnableFeignClients(basePackages = "dream.myself.consumer.api") public class ConsumerApplication {
public static void main(String[] args) throws InterruptedException {
ConfigurableApplicationContext applicationContext = SpringApplication.run(ConsumerApplication.class, args); }
@Bean public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL; }
/*** * 初始化规则 */ @PostConstruct private void initFlowQpsRule() {
//规则集合 List<FlowRule> rules = new ArrayList<>(); //定义一个规则,添加资源名称 FlowRule rule = new FlowRule("getMessage"); // 设置阈值 rule.setCount(2); //设置限流阈值类型 rule.setGrade(RuleConstant.FLOW_GRADE_THREAD); //default,代表不区分调用来源 rule.setLimitApp("default"); //将定义的规则添加到集合中 rules.add(rule); //加载规则 FlowRuleManager.loadRules(rules); }
/*** * 熔断降级规则 */ @PostConstruct private void initDegradeRule() {
//降级规则集合 List<DegradeRule> rules = new ArrayList<DegradeRule>(); //降级规则对象 DegradeRule rule = new DegradeRule(); //设置资源 rule.setResource("getMessage"); //设置触发降级阈值 rule.setCount(2); //熔断降级策略,支持慢调用比例/异常比例/异常数策略 //DEGRADE_GRADE_RT:平均响应时间 //DEGRADE_GRADE_EXCEPTION_RATIO:异常比例数量 //DEGRADE_GRADE_EXCEPTION_COUNT:异常数 rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); //熔断窗口时长,单位为 s rule.setTimeWindow(10); //将规则添加到集合中 rules.add(rule); //加载规则 DegradeRuleManager.loadRules(rules); } } |
在ConsumerController中休眠10秒,测试平均响应时间。
@RestController @RequestMapping("consumer") @SentinelResource(defaultFallback = "defaultExFallback") public class ConsumerController {
private ProducerFeign producer;
public ConsumerController(ProducerFeign producer) {
this.producer = producer; }
@SentinelResource(value = "getMessage") @GetMapping("getMessage") public String consumerMessage() throws InterruptedException {
String ready = producer.consumer("ready"); Thread.sleep(10000); if (StringUtils.isEmpty(ready)) {
throw new RuntimeException(""); } return ready; }
public String defaultExFallback() {
return "The server is busy. Please try again later."; } } |
在Jmeter中基本都执行了降级函数逻辑,只有几个成功的。
此时在浏览器中再次访问http://127.0.0.1:10088/consumer/getMessage,仍然走的是降级的函数逻辑,只有10秒钟后才会进入正常的服务访问。
1.3.2.3.系统自我保护
Sentinel系统自适应限流从整体维度对应用入口流量进行控制,结合应用的Load、CPU使用率、总体平均RT、入口QPS和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统规则包含下面几个重要的属性:
Field |
说明 |
默认值 |
highestSystemLoad |
load1触发值,用于触发自适应控制阶段 |
-1 (不生效) |
avgRt |
所有入口流量的平均响应时间 |
-1 (不生效) |
maxThread |
入口流量的最大并发数 |
-1 (不生效) |
qps |
所有入口资源的QPS |
-1 (不生效) |
highestCpuUsage |
当前系统的CPU使用率(0.0-1.0) |
-1 (不生效) |
|
|
|
理解上面规则的定义之后,我们可以通过调用SystemRuleManager.loadRules() 方法来用硬编码的方式定义流量控制规则。
在consumer的ConsumerController中创建自我保护方法:
@SpringBootApplication @EnableFeignClients(basePackages = "dream.myself.consumer.api") public class ConsumerApplication {
public static void main(String[] args) throws InterruptedException {
ConfigurableApplicationContext applicationContext = SpringApplication.run(ConsumerApplication.class, args); }
@Bean public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL; }
/*** * 初始化规则 */ @PostConstruct private void initFlowQpsRule() {
//规则集合 List<FlowRule> rules = new ArrayList<>(); //定义一个规则,添加资源名称 FlowRule rule = new FlowRule("getMessage"); // 设置阈值 rule.setCount(2); //设置限流阈值类型 rule.setGrade(RuleConstant.FLOW_GRADE_THREAD); //default,代表不区分调用来源 rule.setLimitApp("default"); //将定义的规则添加到集合中 rules.add(rule); //加载规则 FlowRuleManager.loadRules(rules); }
/*** * 熔断降级规则 */ @PostConstruct private void initDegradeRule() {
//降级规则集合 List<DegradeRule> rules = new ArrayList<DegradeRule>(); //降级规则对象 DegradeRule rule = new DegradeRule(); //设置资源 rule.setResource("getMessage"); //设置触发降级阈值 rule.setCount(2); //熔断降级策略,支持慢调用比例/异常比例/异常数策略 //DEGRADE_GRADE_RT:平均响应时间 //DEGRADE_GRADE_EXCEPTION_RATIO:异常比例数量 //DEGRADE_GRADE_EXCEPTION_COUNT:异常数 rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); //熔断窗口时长,单位为 s rule.setTimeWindow(10); //将规则添加到集合中 rules.add(rule); //加载规则 DegradeRuleManager.loadRules(rules); }
/*** * 系统自我保护 */ @PostConstruct private void initSystemRule() {
//系统自我保护集合 List<SystemRule> rules = new ArrayList<>(); //创建系统自我保护规则 SystemRule rule = new SystemRule(); //CPU使用率 值为0-1,-1 (不生效) rule.setHighestCpuUsage(0.2); //所有入口资源的 QPS,-1 (不生效) rule.setQps(10); //入口流量的最大并发数,-1 (不生效) rule.setMaxThread(5); //所有入口流量的平均响应时间,单位:秒,-1 (不生效) rule.setAvgRt(5); //load1 触发值,用于触发自适应控制阶段,系统最高负载,建议取值 CPU cores * 2.5 rule.setHighestSystemLoad(20); //将规则加入到集合 rules.add(rule); SystemRuleManager.loadRules(rules); } } |
我们可以测试CPU使用率自我保护,将rule.setHighestCpuUsage(0.2);的值改为0.99,访问http://127.0.0.1:10088/consumer/getMessage。
1.3.2.4.热点数据
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的Top K数据,并对其访问进行限制。比如:
1:商品ID为参数,统计一段时间内最常购买的商品ID并进行限制 2:用户ID为参数,针对一段时间内频繁访问的用户ID进行限制 |
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
Sentinel利用LRU策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。
要使用热点参数限流功能,需要引入以下依赖:
<!-- sentinel-parameter --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-parameter-flow-control</artifactId> <version>1.8.5</version> </dependency> |
然后为对应的资源配置热点参数限流规则,并在entry的时候传入相应的参数,即可使热点参数限流生效。
热点参数规则(ParamFlowRule)类似于流量控制规则(FlowRule):
属性 |
说明 |
默认值 |
resource |
资源名,必填 |
|
count |
限流阈值,必填 |
|
grade |
限流模式 |
QPS模式 |
durationInSec |
统计窗口时间长度(单位为秒),1.6.0 版本开始支持 |
1s |
controlBehavior |
流控效果(支持快速失败和匀速排队模式),1.6.0 版本开始支持 |
快速失败 |
maxQueueingTimeMs |
最大排队等待时长(仅在匀速排队模式生效),1.6.0 版本开始支持 |
0ms |
paramIdx |
热点参数的索引,必填,对应 SphU.entry(xxx, args)中的参数索引位置 |
|
paramFlowItemList |
参数例外项,可以针对指定的参数值单独设置限流阈值,不受前面 count 阈值的限制。仅支持基本类型和字符串类型 |
|
clusterMode |
是否是集群参数流控规则 |
false |
clusterConfig |
集群流控相关配置 |
|
在consumer中创建一个筛选方法,根据传入的搜索值做筛选:
@SpringBootApplication @EnableFeignClients(basePackages = "dream.myself.consumer.api") public class ConsumerApplication {
public static void main(String[] args) throws InterruptedException {
ConfigurableApplicationContext applicationContext = SpringApplication.run(ConsumerApplication.class, args); }
@Bean public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL; }
/*** * 热点参数初始化 */ @PostConstruct private static void initParamFlowRules() {
ParamFlowRule rule = new ParamFlowRule("search") //参数下标为0 .setParamIdx(0) //限流模式为QPS .setGrade(RuleConstant.FLOW_GRADE_QPS) //统计窗口时间长度(单位为秒) .setDurationInSec(10) //流控效果(支持快速失败和匀速排队模式) //CONTROL_BEHAVIOR_DEFAULT:限流行为,直接拒绝 //CONTROL_BEHAVIOR_WARM_UP:限流行为,匀速排队 //CONTROL_BEHAVIOR_RATE_LIMITER:限流行为,匀速排队 .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT) //最大排队等待时长(仅在匀速排队模式生效 CONTROL_BEHAVIOR_RATE_LIMITER) //.setMaxQueueingTimeMs(600) //最大阈值为5 .setCount(5); // 为特定参数单独设置阈值. //如下配置:当下标为0的参数值为tj的时候,阈值到达2的时候则执行限流 ParamFlowItem item = new ParamFlowItem() //参数类型为String类型 .setClassType(String.class.getName()) //设置阈值为2 .setCount(2) //需要统计的值 .setObject(String.valueOf("test")); rule.setParamFlowItemList(Collections.singletonList(item)); //加载热点数据 ParamFlowRuleManager.loadRules(Collections.singletonList(rule)); } } |
我们可以对热门参数比如id的参数流量进行控制,对热点数据执行特殊限流,比如参数值为test的时候执行限流,在ConsumerController中创建限流配置,代码如下:
@RestController @RequestMapping("consumer") @SentinelResource(defaultFallback = "defaultExFallback") public class ConsumerController {
private ProducerFeign producer;
public ConsumerController(ProducerFeign producer) {
this.producer = producer; } @SentinelResource(value = "getMessage") @GetMapping("getMessage") public String consumerMessage() throws InterruptedException {
String ready = producer.consumer("ready"); if (StringUtils.isEmpty(ready)) {
throw new RuntimeException(""); } return ready; }
@SentinelResource(value = "search") @GetMapping("search/{id}") public String search(@PathVariable("id") String id) {
return "Get the search id:" + id; }
public String defaultExFallback() {
return "The server is busy. Please try again later."; } } |
访问http://127.0.0.1:10088/consumer/dev连续5次会限流。
访问http://127.0.0.1:10088/consumer/search/test连续2次就限流了。
1.3.3.OpenFeign支持
Sentinel适配了Feign组件。如果想使用,除了引入spring-cloud-starter-alibaba-sentinel的依赖外还需要2个步骤:
1:配置文件打开Sentinel对Feign的支持:feign.sentinel.enabled=true 2:加入spring-cloud-starter-openfeign依赖使Sentinel starter中的自动化配置类生效 |
使用Sentinel实现Feign调用降级、限流,先把之前的案例中@SentinelResource相关注解全部注释掉,再实现Feign集成。
ConsumerApplication:
@SpringBootApplication @EnableFeignClients(basePackages = "dream.myself.consumer.api") public class ConsumerApplication {
public static void main(String[] args) throws InterruptedException {
SpringApplication.run(ConsumerApplication.class, args); }
@Bean public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL; }
} |
ConsumerController:
@RestController @RequestMapping("consumer") public class ConsumerController {
private ProducerFeign producer;
public ConsumerController(ProducerFeign producer) {
this.producer = producer; }
@GetMapping("getMessage") public String consumerMessage() throws InterruptedException {
String ready = producer.consumer("ready"); if (StringUtils.isEmpty(ready)) {
throw new RuntimeException(""); } return ready; }
@GetMapping("search/{id}") public String search(@PathVariable("id") String id) {
return "Get the search id:" + id; }
public String defaultExFallback() {
return "The server is busy. Please try again later."; } } |
在Consumer中引入OpenFeight依赖:
<!-- sentinel --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> <version>2.2.9.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> <version>2.2.5.RELEASE</version> </dependency> |
还需要在Feign调用客户端(也就是Consumer)中开启Feign的支持,配置如下:
server: port: 10088
spring: application: name: consumer-server cloud: nacos: config: server-addr: 123.60.23.244:8848 discovery: server-addr: 123.60.23.244:8848 metadata: version: 10.01.20 type: consumer-version
feign: compression: request: enabled: true mime-types: text/html,application/xml,application/json min-request-size: 2048 response: enabled: true #开启Sentinel对Feign的支持 sentinel: enabled: true
logging: level: dream.myself.consumer: debug |
如果配置在nacos上面则需要去nacos上面去更新配置。
为了测试程序异常能实现降级操作,在ProducerController 中让程序休息1秒模拟socket链接超时,测试接下来的程序。
@RestController @RequestMapping("producer") public class ProducerController {
private static final Logger LOGGER = LoggerFactory.getLogger(ProducerController.class);
private final Environment environment;
public ProducerController(Environment environment) {
this.environment = environment; }
@GetMapping("/status/{status}") String consumer(@PathVariable(value = "status") String status) throws InterruptedException {
String port = environment.getProperty("server.port"); LOGGER.info("server.port:" + port); Thread.sleep(1000); return "Producer massage:" + status + ", Receive request server port:" + port; } } |
1.3.3.1.fallback
我们可以为Feign接口创建一个实现类,在实现类中处理程序异常降级处理方法,代码如下:
@Component public class ProducerFeignFallback implements ProducerFeign {
@Override public String consumer(String status) {
return "The server is busy. Please try again later."; } } |
我们还需要在Feign接口上添加fallback属性指定降级处理的类,代码如下:
@FeignClient(value = "producer-server", fallback = ProducerFeignFallback.class) public interface ProducerFeign {
@GetMapping("/producer/status/{status}") String consumer(@PathVariable(value = "status") String status); } |
访问http://127.0.0.1:10088/consumer/getMessage会出现熔断降级。
1.3.3.2.fallbackFactory
我们可以为Feign接口创建一个降级处理的工厂对象,在工厂对象中处理程序异常降级处理方法,代码如下:
@Component public class ProducerFeignFallback implements FallbackFactory<ProducerFeign> {
@Override public ProducerFeign create(Throwable throwable) {
return new ProducerFeign() {
@Override public String consumer(String status) {
return "The server is busy. Please try again later."; } }; }
} |
我们还需要在Feign接口上添加fallbackFactory属性指定讲解处理的类,代码如下:
@FeignClient(value = "producer-server", fallbackFactory = ProducerFeignFallback.class) public interface ProducerFeign {
@GetMapping("/producer/status/{status}") String consumer(@PathVariable(value = "status") String status); } |
访问http://127.0.0.1:10088/consumer/getMessage会出现熔断降级。
2.0.Gateway集成
我们的项目流量入口是SpringCloud Gateway,因此我们重点讲解Sentinel集成Gateway。
2.1.Sentinel网关支持
Sentinel支持对Spring Cloud Gateway、Zuul等主流的API Gateway进行限流。
Sentinel 1.6.0 引入了Sentinel API Gateway Adapter Common模块,此模块中包含网关限流的规则和自定义API的实体和管理逻辑:
• GatewayFlowRule:网关限流规则,针对API Gateway的场景定制的限流规则,可以针对不同route或自定义的API分组进行限流,支持针对请求中的参数、Header、来源IP等进行定制化的限流。
• ApiDefinition:用户自定义的API定义分组,可以看做是一些URL匹配的组合。比如我们可以定义一个API叫my_api,请求path模式为/foo/** 和/baz/**的都归到my_api这个API分组下面。限流的时候可以针对这个自定义的API分组维度进行限流。
其中网关限流规则GatewayFlowRule的字段解释如下:
• resource:资源名称,可以是网关中的route名称或者用户自定义的API分组名称。
• resourceMode :规则是针对API Gateway的route( RESOURCE_MODE_ROUTE_ID )还是用户在Sentinel 中定义的API分组( RESOURCE_MODE_CUSTOM_API_NAME ),默认是route。
• grade:限流指标维度,同限流规则的grade字段。
• count:限流阈值 。
• intervalSec:统计时间窗口,单位是秒,默认是1秒。
• controlBehavior:流量整形的控制效果,同限流规则的controlBehavior字段,目前支持快速失败和匀速排队两种模式,默认是快速失败。
• burst:应对突发请求时额外允许的请求数目。
• maxQueueingTimeoutMs:匀速排队模式下的最长排队时间,单位是毫秒,仅在匀速排队模式下生效。
• paramItem:参数限流配置。若不提供,则代表不针对参数进行限流,该网关规则将会被转换成普通流控规则;否则会转换成热点规则。其中的字段:
○ parseStrategy:从请求中提取参数的策略,目前支持提取来源
→ IP( PARAM_PARSE_STRATEGY_CLIENT_IP )、
→ Host( PARAM_PARSE_STRATEGY_HOST )、
→ 任意Header( PARAM_PARSE_STRATEGY_HEADER 、
→ 任意URL参数( PARAM_PARSE_STRATEGY_URL_PARAM )
四种模式。
○ fieldName:若提取策略选择Header模式或URL参数模式,则需要指定对应的header名称或URL参数名称。
○ pattern:参数值的匹配模式,只有匹配该模式的请求属性值会纳入统计和流控;若为空则统计该请求属性的所有值。(1.6.2 版本开始支持)
○ matchStrategy:参数值的匹配策略,目前支持
→ 精确匹配( PARAM_MATCH_STRATEGY_EXACT )、
→ 子串匹配( PARAM_MATCH_STRATEGY_CONTAINS )、
→ 正则匹配( PARAM_MATCH_STRATEGY_REGEX )
(1.6.2 版本开始支持)
用户可以通过GatewayRuleManager.loadRules(rules)手动加载网关规则,或通过GatewayRuleManager.register2Property(property)注册动态规则源动态推送(推荐方式)。
2.2.集成Gateway
我们如果想要让微服务网关集成Sentinel,需要引入依赖包,使用时只需注入对应的SentinelGatewayFilter 实例以及SentinelGatewayBlockExceptionHandler实例即可。
创建一个gateway服务:
@SpringBootApplication public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args); } } |
GatewayController
@RestController public class GatewayController {
@GetMapping("getMessage") public String getMessage() {
return "I am a Controller!"; }
@PostMapping("postMessage") public String postMessage(@RequestParam String message) {
return "I get the word:" + message; } } |
bootstrap.yml
server: port: 10084 spring: application: name: gateway-server cloud: nacos: config: server-addr: 123.60.23.244:8848 discovery: server-addr: 123.60.23.244:8848 gateway: routes: - id: producer-server uri: lb://gateway-server predicates: - Path=/gateway/** filters: - StripPrefix=1 |
引入依赖:
<!-- Sentinel-gateway --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-spring-cloud-gateway-adapter</artifactId> <version>1.8.5</version> </dependency> |
实例引入:创建配置类GatewayConfiguration :
@Configuration public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers; private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider, ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList); this.serverCodecConfigurer = serverCodecConfigurer; }
/** * 限流的异常处理器 */ @Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer); }
/*** * Sentinel路由处理核心过滤器 */ @Bean @Order(-1) public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter(); } } |
此时集成就完成了。
2.3.配置/API定义
1)API定义
正如前面所说,ApiDefinition用户自定义的API定义分组,可以看做是一些URL匹配的组合。比如我们可以定义一个API叫my_api,请求path模式为/foo/**和/baz/**的都归到my_api这个API分组下面。限流的时候可以针对这个自定义的API分组维度进行限流。
我们在配置类GatewayConfiguration创建Api:
/*** * Api定义 */ private void initCustomizedApis() {
//Api集合 Set<ApiDefinition> definitions = new HashSet<>(); ApiDefinition api = new ApiDefinition("my_api") .setPredicateItems(new HashSet<ApiPredicateItem>() {
{
add(new ApiPathPredicateItem().setPattern("/gateway/**")); add(new ApiPathPredicateItem().setPattern("/consumer/**")); add(new ApiPathPredicateItem().setPattern("/producer/**") //参数值的匹配策略 // 精确匹配(PARAM_MATCH_STRATEGY_EXACT) // 子串匹配(PARAM_MATCH_STRATEGY_CONTAINS) // 正则匹配(PARAM_MATCH_STRATEGY_REGEX) .setMatchStrategy(SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX)); }}); definitions.add(api); //加载Api GatewayApiDefinitionManager.loadApiDefinitions(definitions); } |
2)规则创建
GatewayFlowRule网关限流规则,针对API Gateway的场景定制的限流规则,可以针对不同route或自定义的API分组进行限流,支持针对请求中的参数、Header、来源IP等进行定制化的限流。
我们在配置类GatewayConfiguration创建配置:
/*** * 规则定义 */ private void initGatewayRules() {
//网关限流规则 Set<GatewayFlowRule> rules = new HashSet<GatewayFlowRule>(); //商品微服务规则配置 //资源名称,可以是网关中的 route 名称或者用户自定义的 API 分组名称 rules.add(new GatewayFlowRule("consumer-server") //限流阈值 .setCount(2) //应对突发请求时额外允许的请求数目。 .setBurst(2) //统计时间窗口,单位是秒,默认是 1 秒。 .setIntervalSec(1) //限流行为 //CONTROL_BEHAVIOR_RATE_LIMITER 匀速排队 //CONTROL_BEHAVIOR_DEFAULT 快速失败(默认) .setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER) //匀速排队模式下的最长排队时间,单位是毫秒,仅在匀速排队模式下生效。 .setMaxQueueingTimeoutMs(1000) ); //限流配置 rules.add(new GatewayFlowRule("producer-server") //限流阈值 .setCount(3) //统计时间窗口,单位是秒,默认是 1 秒。 .setIntervalSec(1) ); //加载网关规则 GatewayRuleManager.loadRules(rules); } |
平时工作中对参数过滤的需求并不多,这里的参数过滤就不在讲解了。
3)配置/Api加载
上面两个方法创建了API并且创建了配置,但并没有在程序启动加载,我们可以采用@PostConstruct注解实现加载调用,在GatewayConfiguration中创建方法:
/*** * 初始化加载Api和规则 */ @PostConstruct public void doInit() {
initCustomizedApis(); initGatewayRules(); } |
3.0.Sentinel控制台
Sentinel控制台是流量控制、熔断降级规则统一配置和管理的入口,它为用户提供了机器自发现、簇点链路自发现、监控、规则配置等功能。在Sentinel控制台上,我们可以配置规则并实时查看流量控制效果。
3.1.安装
Sentinel提供一个轻量级的开源控制台,它提供机器发现以及健康情况管理、监控(单机和集群),规则管理和推送的功能。
Sentinel控制台包含如下功能:
• 查看机器列表以及健康情况:收集Sentinel客户端发送的心跳包,用于判断机器是否在线。
• 监控(单机和集群聚合):通过Sentinel客户端暴露的监控API,定期拉取并且聚合应用监控信息,最终可以实现秒级的实时监控。
• 规则管理和推送:统一管理推送规则。
• 鉴权:生产环境中鉴权非常重要。这里每个开发者需要根据自己的实际情况进行定制。
Sentinel控制台安装可以基于jar包启动的方式安装:Releases · alibaba/Sentinel (github.com),也可以基于docker安装。
这里使用jar包方式安装,解压安装包在linux终端输入命令:
nohup java -Dserver.port=9200 -Dcsp.sentinel.dashboard.server=localhost:9200 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.6.jar & |
在windows上启动jar包(以下测试均在本地windows机器上监控服务):
java -Dserver.port=9200 -Dcsp.sentinel.dashboard.server=localhost:9200 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard-1.8.6.jar |
参数解释:
-Dcsp.sentinel.log.dir:日志保存路径,默认:${user.home}/logs/csp/
-Dserver.port=9200:控制台服务端口,默认:8080
-Dcsp.sentinel.dashboard.server=localhost:9200:控制台访问端口,默认:8080
-Dcsp.sentinel.heartbeat.client.ip=localhost:客户端心跳ip,多网卡需要指定这个ip,否则启动后报错,可忽略
-Dproject.name=sentinel-dashboard:控制台显示名称
-Dsentinel.dashboard.auth.username=sentinel:控制台登录账号,默认:sentinel
-Dsentinel.dashboard.auth.password=sentinel:控制台登录密码,默认:sentinel
-jar sentinel-dashboard-1.8.6.jar:运行sentinel1.8.6 jar包
安装好了后,我们可以直接访问 http://127.0.0.1:9200访问控制台,默认用户名和密码都sentinel:
登录后如下:
提示:sentinel-dashboard监控服务的时候最好在同一台服务器上,远端的sentinel-dashboard虽然可以连接上,但是监控服务非常麻烦,可能没有实时监控信息。
SpringCloud工程接入Sentinel,直接引入如下依赖包:
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> <version>2.2.9.RELEASE</version> </dependency> |
3.2.接入控制台
我们将Gateway微服务网关、producer与consumer项目接入Sentinel控制台,首先引入依赖包:
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> <version>2.2.9.RELEASE</version> </dependency> |
spring-cloud-starter-alibaba-sentinel中已经引入了sentinel-parameter-flow-control依赖,可以将它删除了。
在核心配置文件中配置Sentinel服务地址。
server: port: 10084 spring: application: name: gateway-server cloud: nacos: config: server-addr: 123.60.23.244:8848 namespace: 1e327238-6212-4179-9a28-4c186039485f discovery: server-addr: 123.60.23.244:8848 namespace: 1e327238-6212-4179-9a28-4c186039485f gateway: routes: - id: producer-server uri: lb://gateway-server predicates: - Path=/gateway/** filters: - StripPrefix=1 sentinel: transport: port: 8719 dashboard: 127.0.0.1:9200 |
spring: application: name: consumer-server cloud: nacos: config: server-addr: 123.60.23.244:8848 group: DEFAULT_GROUP namespace: 1e327238-6212-4179-9a28-4c186039485f file-extension: yaml extension-configs[0]: data-id: datasource.yaml discovery: server-addr: 123.60.23.244:8848 group: DEFAULT_GROUP namespace: 1e327238-6212-4179-9a28-4c186039485f sentinel: transport: port: 8719 dashboard: 127.0.0.1.244:9200 |
server: port: 10086 spring: application: name: producer-server cloud: nacos: config: server-addr: 123.60.23.244:8848 namespace: 1e327238-6212-4179-9a28-4c186039485f discovery: server-addr: 123.60.23.244:8848 namespace: 1e327238-6212-4179-9a28-4c186039485f weight: 5 sentinel: transport: port: 8719 dashboard: 127.0.0.1.244:9200 |
这里的spring.cloud.sentinel.transport.port:8719端口配置会在应用对应的机器上启动一个Http Server,Server会与Sentinel控制台做交互,比如限流规则拉取。
此时我们出发一些请求操作,再看Sentinel控制台会检测到多个服务:
3.3.可视化管理
3.3.1.实时监控
同一个服务下的所有机器的簇点信息会被汇总,并且秒级地展示在"实时监控"。
注意: 实时监控仅存储5分钟以内的数据,如果需要持久化,需要通过调用实时监控接口来定制。
如果要获取监控数据,直接调用 http://localhost:8719/clusterNode 即可获取,效果如下:
3.3.2.熔断规则
我们可以选择 熔断规则>新增熔断规则 ,如下图:
熔断规则的熔断策略有3种,分别是慢调用比例、异常比例、异常数,和程序中是一样的。
3.3.3.热点数据
热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的Top K数据,并对其访问进行限制。
3.3.32.流控规则
我们可以在【流控规则】页面中新增,点击【流控规则】进入页面新增页面,如下图:
资源名:其实可以和请求路径保持一致,这里的流控模式为QPS,触发流控执行阈值为1,流控模式为让当前请求的资源快速失败。
这里的参数和我们程序中的参数其实是一样的,如下说明:
resource:资源名,即限流规则的作用对象 count: 限流阈值 grade: 限流阈值类型(QPS 或并发线程数) limitApp: 流控针对的调用来源,若为 default 则不区分调用来源 strategy: 调用关系限流策略 controlBehavior: 流量控制效果(直接拒绝、Warm Up、匀速排队) |
流控效果和程序中也是一样的:
快速失败: 当QPS超过任何规则的阈值后,新的请求就会立即拒绝,拒绝方式为抛出FlowException . 这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。 Warm Up: 当系统长期处理低水平的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值的上限,给系统一个预热的时间,避免冷系统被压垮。 排队等待: 匀速排队严格控制请求通过的时间间隔,也即是让请求以均匀的速度通过,对应的是漏桶算法。 |
3.3.3.1.流控规则-关联
关联是一个让步操作,被关联的资源会受到关联资源的流控影响。关联的资源一旦发生熔断降级,被关联的资源也会被熔断降级。
设置关联资源流控:
使用Jmeter无限访问/consumer/search/{id}资源,让它发生熔断降级。
此时我们去访问/consumer/getMessage资源会发生熔断降级。
3.3.3.2.流控规则-链路
多个请求同时调用同一个节点,可以设置单独对某个路径进行限流。
链路限流需要一个过滤器支持,这样可以将同一节点的不同链路从通配符中分离出来实现单路径资源拦截:
添加依赖:
<dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-web-servlet</artifactId> <version>1.8.5</version> <scope>compile</scope> </dependency> |
在consumer启动类中添加过滤器:
@SpringBootApplication @EnableFeignClients(basePackages = "dream.myself.consumer.api") public class ConsumerApplication {
public static void main(String[] args) throws InterruptedException {
SpringApplication.run(ConsumerApplication.class, args); }
@Bean public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL; }
@Bean public FilterRegistrationBean sentinelFilterRegistrationBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean(); registrationBean.setFilter(new CommonFilter()); // 过滤所有请求 registrationBean.addUrlPatterns("/*"); // 入口资源关闭聚合 请求路径合并判断 不同参数会生成新的资源 registrationBean.addInitParameter(CommonFilter.WEB_CONTEXT_UNIFY, "false"); registrationBean.setName("sentinelFilter"); registrationBean.setOrder(1); return registrationBean; } } |
未添加过滤器,所有的路径均为一个通配符资源。
添加过滤器后,不同的路径变成不同的资源,可以实现对单个路径的资源流控。
添加对search节点的id资源路径的流控规则:
测试id访问超过2次被拦截了,其他路径资源未被拦截。
3.3.3.3.流控规则-Warm up
假设阈值为10,warm up会慢慢的将阈值升至为10,让服务器慢慢适应流控,防止服务器性能不够好或者加载数据较多比较繁忙这种情况,让服务器慢慢增加访问量。
3.3.3.4.流控规则-授权
授权根据服务传过来的参数添加黑白名单。