1 .Sentinel 是什么
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 是面向分布式服务架构的流量控制组件,主要以 流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定 性。
源码地址:https://github.com/alibaba/Sentinel
官方文档:https://github.com/alibaba/Sentinel/wiki
Sentinel具有以下特征:
- 丰富的应用场景: Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控 制在系统容量可以承受的范围)、消息削峰填谷、实时熔断下游不可用应用等。
- 完备的实时监控: Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据, 甚至 500 台以下规模的集群的汇总运行情况。
- 广泛的开源生态: Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、 gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。
- 完善的 SPI 扩展点: Sentinel 提供简单易用、完善的 SPI 扩展点。您可以通过实现扩展点,快速的定制逻辑。 例如定制规则管理、适配数据源等。
- 阿里云提供了 企业级的 Sentinel 服务,应用高可用服务 AHAS
1.1Sentinel和Hystrix对比
https://github.com/alibaba/Sentinel/wiki/Sentinel%E4%B8%8EHystrix%E7%9A%84%E5%AF%B9%E6%AF%94
2.Sentinel快速开始
https://github.com/alibaba/Sentinel/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8
在官方文档中,定义的Sentinel进行资源保护的几个步骤:
1)定义资源
2)定义规则
3)检验规则是否生效
2.1Sentinel资源保护的方式
方式1:API实现
- 引入依赖
<!--sentinel核心库--><dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-core</artifactId><version>1.8.0</version></dependency>
- 编写测试逻辑
package com.tuling.sentinelnew.controller;import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
import com.alibaba.csp.sentinel.SphU;
import com.alibaba.csp.sentinel.Tracer;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.RuleConstant;
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.tuling.sentinelnew.pojo.User;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.List;@RestController
@Slf4j
public class HelloController {private static final String RESOURCE_NAME = "hello";private static final String USER_RESOURCE_NAME = "user";private static final String DEGRADE_RESOURCE_NAME = "degrade";// 进行sentinel流控@RequestMapping(value = "/hello")public String hello() {Entry entry = null;try {// 1.sentinel针对资源进行限制的entry = SphU.entry(RESOURCE_NAME);// 被保护的业务逻辑String str = "hello world";log.info("=====" + str + "=====");return str;} catch (BlockException e1) {// 资源访问阻止,被限流或被降级//进行相应的处理操作log.info("block!");return "被流控了!";} catch (Exception ex) {// 若需要配置降级规则,需要通过这种方式记录业务异常Tracer.traceEntry(ex, entry);} finally {if (entry != null) {entry.exit();}}return null;}/*** 定义规则* <p>* spring 的初始化方法*/@PostConstructprivate static void initFlowRules() {// 流控规则List<FlowRule> rules = new ArrayList<>();// 流控FlowRule rule = new FlowRule();// 为哪个资源进行流控rule.setResource(RESOURCE_NAME);// 设置流控规则 QPSrule.setGrade(RuleConstant.FLOW_GRADE_QPS);// 设置受保护的资源阈值// Set limit QPS to 20.rule.setCount(1);rules.add(rule);// 通过@SentinelResource来定义资源并配置降级和流控的处理方法FlowRule rule2 = new FlowRule();//设置受保护的资源rule2.setResource(USER_RESOURCE_NAME);// 设置流控规则 QPSrule2.setGrade(RuleConstant.FLOW_GRADE_QPS);// 设置受保护的资源阈值// Set limit QPS to 20.rule2.setCount(1);rules.add(rule2);// 加载配置好的规则FlowRuleManager.loadRules(rules);}@PostConstruct // 初始化public void initDegradeRule() {/*降级规则 异常*/List<DegradeRule> degradeRules = new ArrayList<>();DegradeRule degradeRule = new DegradeRule();degradeRule.setResource(DEGRADE_RESOURCE_NAME);// 设置规则侧率: 异常数degradeRule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT);// 触发熔断异常数 : 2degradeRule.setCount(2);// 触发熔断最小请求数:2degradeRule.setMinRequestAmount(2);// 统计时长: 单位:ms 1分钟degradeRule.setStatIntervalMs(60 * 1000); // 时间太短不好测// 一分钟内: 执行了2次 出现了2次异常 就会触发熔断// 熔断持续时长 : 单位 秒// 一旦触发了熔断, 再次请求对应的接口就会直接调用 降级方法。// 10秒过了后——半开状态: 恢复接口请求调用, 如果第一次请求就异常, 再次熔断,不会根据设置的条件进行判定degradeRule.setTimeWindow(10);degradeRules.add(degradeRule);DegradeRuleManager.loadRules(degradeRules);/*慢调用比率--DEGRADE_GRADE_RTdegradeRule.setGrade(RuleConstant.DEGRADE_GRADE_RT);degradeRule.setCount(100);degradeRule.setTimeWindow(10);//请求总数小于minRequestAmount时不做熔断处理degradeRule.setMinRequestAmount(2);// 在这个时间段内2次请求degradeRule.setStatIntervalMs(60*1000*60); // 时间太短不好测// 慢请求率:慢请求数/总请求数> SlowRatioThreshold ,// 这里要设置小于1 因为慢请求数/总请求数 永远不会大于1degradeRule.setSlowRatioThreshold(0.9);*/}/*** @param id* @return* @SentinelResource 改善接口中资源定义和被流控降级后的处理方法* 怎么使用: 1.添加依赖<artifactId>sentinel-annotation-aspectj</artifactId>* 2.配置bean——SentinelResourceAspect* value 定义资源* blockHandler 设置 流控降级后的处理方法(默认该方法必须声明在同一个类)* 如果不想在同一个类中 blockHandlerClass 但是方法必须是static* fallback 当接口出现了异常,就可以交给fallback指定的方法进行处理* 如果不想在同一个类中 fallbackClass 但是方法必须是static* <p>* blockHandler 如果和fallback同时指定了,则blockHandler优先级更高* exceptionsToIgnore 排除哪些异常不处理*/@RequestMapping("/user")@SentinelResource(value = USER_RESOURCE_NAME, fallback = "fallbackHandleForGetUser",/*exceptionsToIgnore = {ArithmeticException.class},*//*blockHandlerClass = User.class,*/ blockHandler = "blockHandlerForGetUser")public User getUser(String id) {int a = 1 / 0;return new User("xushu");}public User fallbackHandleForGetUser(String id, Throwable e) {e.printStackTrace();return new User("异常处理");}/*** 注意:* 1. 一定要public* 2. 返回值一定要和源方法保证一致, 包含源方法的参数。* 3. 可以在参数最后添加BlockException 可以区分是什么规则的处理方法** @param id* @param ex* @return*/public User blockHandlerForGetUser(String id, BlockException ex) {ex.printStackTrace();return new User("流控!!");}@RequestMapping("/degrade")@SentinelResource(value = DEGRADE_RESOURCE_NAME, entryType = EntryType.IN,blockHandler = "blockHandlerForFb")public User degrade(String id) throws InterruptedException {// 异常数\比例throw new RuntimeException("异常");/* 慢调用比例TimeUnit.SECONDS.sleep(1);return new User("正常");*/}public User blockHandlerForFb(String id, BlockException ex) {return new User("熔断降级");}}
StartApplication.java
package com.tuling.sentinelnew;import com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;@SpringBootApplication
public class StartApplication {public static void main(String[] args) {SpringApplication.run(StartApplication.class,args);}/*** @description: 注解支持的配置Bean* @method: sentinelResourceAspect* @author: wang fei* @date: 2023/1/22 16:46:26* @param: []* @return: org.springframework.beans.factory.annotation.Value**/@Beanpublic SentinelResourceAspect sentinelResourceAspect() {return new SentinelResourceAspect();}
}
测试结果:
缺点:
业务侵入性很强,需要在controller中写入非业务代码.
配置不灵活 若需要添加新的受保护资源 需要手动添加 init方法来添加流控规则
方式2:
@SentinelResource注解实现
@SentinelResource 注解用来标识资源是否被限流、降级。
blockHandler: 定义当资源内部发生了BlockException应该进入的方法(捕获的是Sentinel定义的异常)
fallback: 定义的是资源内部发生了Throwable应该进入的方法
exceptionsToIgnore:配置fallback可以忽略的异常
- 引入依赖
<!--sentinel启动器--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId></dependency>
- Controller中编写测试逻辑,添加@SentinelResource,并配置blockHandler和fallback
package com.wang.controller;import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
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 java.util.concurrent.TimeUnit;/*** @BelongsProject: SpringCloudAlibabaLearn* @BelongsPackage: com.wang.controllter* @Author: wang fei* @CreateTime: 2023-01-16 16:48* @Description: TODO* @Version: 1.0*/
@RestController
@RequestMapping("/order")
public class OrderController {@GetMapping("/pay")@SentinelResource(value = "pay",blockHandler = "flowBlockHandler")public String pay(){return "success";}@GetMapping("/flowThread")@SentinelResource(value = "flowThread",blockHandler = "flowBlockHandler")public String flowThread() throws InterruptedException {TimeUnit.SECONDS.sleep(5);return "success";}//自定义流控处理返回方法public String flowBlockHandler(BlockException e){return "流控成功 ,阻塞服务";}@GetMapping("/add")public String add(){return "下单成功";}@GetMapping("/get")public String get(){return "查询成功";}/*** 热点规则,必须使用@SentinelResource* @param id* @return* @throws InterruptedException*/@RequestMapping("/get/{id}")@SentinelResource(value = "getById",blockHandler = "HotBlockHandler")public String getById(@PathVariable("id") Integer id) throws InterruptedException {System.out.println("正常访问");return "正常访问";}public String HotBlockHandler(@PathVariable("id") Integer id,BlockException e) throws InterruptedException {return "热点异常处理";}
}
- 编写MyBlockExceptionHandler
package com.wang.exception;import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
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 com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wang.domain.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** @author 飞*/
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {Logger log= LoggerFactory.getLogger(this.getClass());@Overridepublic void handle(HttpServletRequest httpServletRequest, HttpServletResponse response, BlockException e) throws Exception {// getRule() 资源 规则的详细信息log.info("BlockExceptionHandler BlockException================"+e.getRule());Result r = null;if (e instanceof FlowException) {r = Result.error(100,"接口限流了");} else if (e instanceof DegradeException) {r = Result.error(101,"服务降级了");} else if (e instanceof ParamFlowException) {r = Result.error(102,"热点参数限流了");} else if (e instanceof SystemBlockException) {r = Result.error(103,"触发系统保护规则了");} else if (e instanceof AuthorityException) {r = Result.error(104,"授权规则不通过");}//返回json数据response.setStatus(500);response.setCharacterEncoding("utf-8");response.setContentType(MediaType.APPLICATION_JSON_VALUE);new ObjectMapper().writeValue(response.getWriter(), r);}
}
Result.java
package com.wang.domain;/*** @author 飞*/
public class Result<T> {private Integer code;private String msg;private T data;public Result(Integer code, String msg, T data) {this.code = code;this.msg = msg;this.data = data;}public Result(Integer code, String msg) {this.code = code;this.msg = msg;}public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}public T getData() {return data;}public void setData(T data) {this.data = data;}public static Result error(Integer code, String msg){return new Result(code,msg);}
}
server:port: 8070
spring:application:name: order-sentinelcloud:sentinel:transport:dashboard: 127.0.0.1:8080web-context-unify: false # 默认将调用链路收敛, 导致链路流控效果无效datasource:flow-rule:nacos:server-addr: 127.0.0.1:8848 # nacos地址username: nacospassword: nacosdataId: order-sentinel-flow-rulerule-type: flow
3.启动 Sentinel 控制台
下载控制台 jar 包并在本地启动:可以参见 此处文档
https://github.com/alibaba/Sentinel/releases
java ‐jar sentinel‐dashboard‐1.8.0.jar
- 用户可以通过如下参数进行配置:
-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 分钟;
java Dserver.port=8858 Dsentinel.dashboard.auth.username=xushu Dsentinel.dashboard.auth.password=123456 jar sentinel
dashboard1.8.0.jar
- 为了方便快捷启动可以在桌面创建.bat文件
java ‐Dserver.port=8858 ‐Dsentinel.dashboard.auth.username=xushu Dsentinel.dashboard.auth.password=123456 ‐jar D:\s
erver\sentinel‐dashboard‐1.8.0.jar
pause
访问http://localhost:8080/#/login ,默认用户名密码: sentinel/sentinel
Sentinel 会在客户端首次调用的时候进行初始化,开始向控制台发送心跳包,所以要确保客户端有访问量;
4. Spring Cloud Alibaba整合Sentine
2.2步骤
5.Sentinel控制台规则配置详解
5.1 实时监控
监控接口的通过的QPS和拒绝的QPS
5.2 簇点链路
用来显示微服务的所监控的API
5.3 流控规则
- 流量控制(flow control),其原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,
以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。 ==== FlowRule RT(响应时间) 1/0.2s =5
同一个资源可以创建多条限流规则。FlowSlot 会对该资源的所有限流规则依次遍历,直到有规则触发限流或者所有规则遍
历完毕。一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果。
参考文档: https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6
- 限流阈值类型
QPS(Query Per Second):每秒请求数,就是说服务器在一秒的时间内处理了多少个请求。
进入簇点链路选择具体的访问的API,然后点击流控按钮
- 并发线程数
并发数控制用于保护业务线程池不被慢调用耗尽。例如,当应用所依赖的下游应用由于某种原因导致服务不稳定、响应 延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数占用,极端情况下甚至导致线程池耗尽。为应对太多线 程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源争抢(线程 池隔离)。这种隔离方案虽然隔离性比较好,但是代价就是线程数目太多,线程上下文切换的 overhead 比较大,特别是
对低延时的调用有比较大的影响。Sentinel 并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目 (正在执行的调用数目),如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。并发数控制通常在调用端
进行配置。
- BlockException异常统一处理
springwebmvc接口资源限流入口在HandlerInterceptor的实现类AbstractSentinelInterceptor的preHandle方 法中,对异常的处理是BlockExceptionHandler的实现类
sentinel 1.7.1 引入了sentinel-spring-webmvc-adapter.jar
自定义BlockExceptionHandler 的实现类统一处理BlockException
package com.wang.exception;import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
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 com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wang.domain.Result;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;/*** @author 飞*/
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {Logger log= LoggerFactory.getLogger(this.getClass());@Overridepublic void handle(HttpServletRequest httpServletRequest, HttpServletResponse response, BlockException e) throws Exception {// getRule() 资源 规则的详细信息log.info("BlockExceptionHandler BlockException================"+e.getRule());Result r = null;if (e instanceof FlowException) {r = Result.error(100,"接口限流了");} else if (e instanceof DegradeException) {r = Result.error(101,"服务降级了");} else if (e instanceof ParamFlowException) {r = Result.error(102,"热点参数限流了");} else if (e instanceof SystemBlockException) {r = Result.error(103,"触发系统保护规则了");} else if (e instanceof AuthorityException) {r = Result.error(104,"授权规则不通过");}//返回json数据response.setStatus(500);response.setCharacterEncoding("utf-8");response.setContentType(MediaType.APPLICATION_JSON_VALUE);new ObjectMapper().writeValue(response.getWriter(), r);}
}
- 流控模式
基于调用关系的流量控制。调用关系包括调用方、被调用方;一个方法可能会调用其它方法,形成一个调用链路的层次
关系:
直接
资源调用达到设置的阈值后直接被流控抛出异常
关联
当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源便具有了关联。比如对数据库同一个字段的读操作和写 操作存在争抢,读的速度过高会影响写得速度,写的速度过高会影响读的速度。如果放任读写操作争抢资源,则争抢本 身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_db 和
write_db 这两个资源分别代表数据库读写,我们可以给 read_db 设置限流规则来达到写优先的目的:设置 strategy 为
RuleConstant.STRATEGY_RELATE 同时设置 refResource 为 write_db。这样当写库操作过于频繁时,读数据的请求会被限流。
链路
根据调用链路入口限流。
下面中记录了资源之间的调用链路,这些资源通过调用关系,相互之间构成一棵调用树。这棵树的根节点是一个名字为 getUser 的虚拟节点,调用链的入口都是这个虚节点的子节点。
一棵典型的调用树如下图所示:
上图中来自入口 /order/test1 和 /order/test2的请求都调用到了资源 getUser,Sentinel 允许只根据某个入口的统计信息对 资源限流。
测试会发现链路规则不生效
注意,高版本此功能直接使用不生效,如何解决?
从1.6.3 版本开始,Sentinel Web filter默认收敛所有URL的入口context,因此链路限流不生效。
1.7.0 版本开始(对应SCA的2.1.1.RELEASE),官方在CommonFilter 引入了
WEB_CONTEXT_UNIFY 参数,用于控制是否收敛context。将其配置为 false 即可根据不同的URL 进行链路限流。
SCA 2.1.1.RELEASE之后的版本,可以通过配置spring.cloud.sentinel.web-context-unify=false即可关闭收敛
spring.cloud.sentinel.web‐context‐unify: false
测试,此场景拦截不到BlockException,对应@SentinelResource指定的资源必须在@SentinelResource注解中指定
blockHandler处理BlockException
总结: 为了解决链路规则引入ComonFilter的方式,除了此处问题,还会导致更多的问题,不建议使用ComonFilter的方式。 流控链路模
式的问题等待官方后续修复,或者使用AHAS。
- 快速失败
(RuleConstant.CONTROL_BEHAVIOR_DEFAULT)方式是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被 立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统 的准确水位时。
Warm Up(激增流量)
Warm Up(RuleConstant.CONTROL_BEHAVIOR_WARM_UP)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量 突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐 增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。
冷加载因子: codeFactor 默认是3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。
通常冷启动的过程系统允许通过的 QPS 曲线如下图所示
编辑流控规则
jmeter测试 :
查看实时监控,可以看到通过QPS存在缓慢增加的过程
匀速排队(脉冲流量)
匀速排队(`RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER`)方式会严格控制请求通过的间隔时间,也即是让请 求以均匀的速度通过,对应的是漏桶算法。
该方式的作用如下图所示
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下 来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的 请求。
注意:匀速排队模式暂时不支持 QPS > 1000 的场景。
jemeter压测
查看实时监控,可以看到通过QPS为5,体现了匀速排队效果
- 降级规则
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。我们需要对不稳定 的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身 的手段,通常在客户端(调用端)进行配置。
- 熔断策略
慢调用比例
慢调用比例 (SLOW_REQUEST_RATIO):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间), 请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并 且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态 (HALFOPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会 再次被熔断。
jemeter压测/test接口,保证每秒请求数超过配置的最小请求数
查看实时监控,可以看到断路器熔断效果
异常比例
异常比例 (ERROR_RATIO):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状(HALFOPEN 状 态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],
代表 0% 100%。
查看实时监控,可以看到断路器熔断效果
异常数
异常数 (ERROR_COUNT):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探 测恢复状态(HALFOPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
注意:异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。
配置降级规则
- 整合openfeign进行降级
引入
<!-- openfeig 远程调用--><dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId></dependency><!--sentinel依赖--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId></dependency>
application.yml
feign:sentinel:# openfeign整合sentinelenabled: true
openfegin接口
package com.wang.feign;import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;@FeignClient(value="stock-nacos",path = "/stock",fallback = StockFeignServiceFallback.class)
public interface StockFeignService {@RequestMapping("/reduct")public String reduct2();
}
openfegin的fallback实现类
package com.wang.feign;import org.springframework.stereotype.Component;@Component
public class StockFeignServiceFallback implements StockFeignService {@Overridepublic String reduct2() {return "降级啦!!!";}
}
- 热点参数限流
热点识别流控
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的数据,并对其访问进行限 制。比如:
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
注意:
1. 热点规则需要使用@SentinelResource("resourceName")注解,否则不生效
2. 参数必须是7种基本数据类型才会生效
单机阈值: 针对所有参数的值进行设置的一个公共的阈值
1. 假设当前 参数 大部分的值都是热点流量, 单机阈值就是针对热点流量进行设置, 额外针对普通流量进行参数值流控
2. 假设当前 参数 大部分的值都是普通流量, 单机阈值就是针对普通流量进行设置, 额外针对热点流量进行参数值流控
配置热点参数规则
注意: 资源名必须是@SentinelResource(value="资源名")中 配置的资源名,热点规则依赖于注解
具体到参数值限流,配置参数值为3,限流阈值为1
- 系统规则
Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让
系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
Load 自适应(仅对 Linux/Unixlike 机器生效):系统的 load1 作为启发指标,进行自适应系统保护。当系统 load1 超过设定的启发值,且系统当前的并发线程数超过估算的系统容量时才会触发系统保护(BBR 阶段)。系统
容量由系统的 maxQps * minRt 估算得出。设定参考值一般是 CPU cores * 2.5。
https://www.cnblogs.com/gentlemanhai/p/8484839.html
CPU usage(1.5.0+ 版本):当系统 CPU 使用率超过阈值即触发系统保护(取值范围 0.01.0),比较灵敏。
平均 RT:当单台机器上所有入口流量的平均 RT 达到阈值即触发系统保护,单位是毫秒。
并发线程数:当单台机器上所有入口流量的并发线程数达到阈值即触发系统保护。
入口 QPS:当单台机器上所有入口流量的 QPS 达到阈值即触发系统保护。
测试结果
- 授权控制规则
很多时候,我们需要根据调用来源来判断该次请求是否允许放行,这时候可以使用 Sentinel 的来源访问控制(黑白名单
控制)的功能。来源访问控制根据资源的请求来源(origin)限制资源是否通过,若配置白名单则只有请求来源位于白名
单内时才可通过;若配置黑名单则请求来源位于黑名单时不通过,其余的请求通过。
来源访问控制规则(AuthorityRule)非常简单,主要有以下配置项:
resource:资源名,即限流规则的作用对象。
limitApp:对应的黑名单/白名单,不同 origin 用 , 分隔,如 appA,appB。
strategy:限制模式,AUTHORITY_WHITE 为白名单模式,AUTHORITY_BLACK 为黑名单模式,默认为白名单模式。
配置授权规则
第一步:实现com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser接口,在parseOrigin 方法中区分来源,并交给spring管理 注意:如果引入CommonFilter,此处会多出一个。
package com.wang.config;import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;@Component
public class MyRequestOriginParser implements RequestOriginParser {/*** 通过request获取来源标识,交给授权规则进行匹配* @param request* @return*/@Overridepublic String parseOrigin(HttpServletRequest request) {// 标识字段名称可以自定义String origin = request.getParameter("serviceName");
// if (StringUtil.isBlank(origin)){
// throw new IllegalArgumentException("serviceName参数未指定");
// }return origin;}
}
- 集群规则
为什么要使用集群流控呢?假设我们希望给某个用户限制调用某个 API 的总 QPS 为 50,但机器数可能很多(比如有 100 台)。这时候我们很自然地就想到,找一个 server 来专门来统计总的调用量,其它的实例都与这台 server 通信来判 断是否可以调用。这就是最基础的集群流控的方式。
另外集群流控还可以解决流量不均匀导致总体限流效果不佳的问题。假设集群中有 10 台机器,我们给每台机器设置单机 限流阈值为 10 QPS,理想情况下整个集群的限流阈值就为 100 QPS。不过实际情况下流量到每台机器可能会不均匀, 会导致总量没有到的情况下某些机器就开始限流。因此仅靠单机维度去限制的话会无法精确地限制总体流量。而集群流 控可以精确地控制整个集群的调用总量,结合单机限流兜底,可以更好地发挥流量控制的效果。
https://github.com/alibaba/Sentinel/wiki/%E9%9B%86%E7%BE%A4%E6%B5%81%E6%8E%A7
集群流控中共有两种身份:
Token Client:集群流控客户端,用于向所属 Token Server 通信请求 token。集群限流服务端会返回给客户端
结果,决定是否限流。
Token Server:即集群流控服务端,处理来自 Token Client 的请求,根据配置的集群规则判断是否应该发放
token(是否允许通过)。
Sentinel 集群流控支持限流规则和热点规则两种规则,并支持两种形式的阈值计算方式:
集群总体模式:即限制整个集群内的某个资源的总体 qps 不超过此阈值。
单机均摊模式:单机均摊模式下配置的阈值等同于单机能够承受的限额,token server 会根据连接数来计算总 的阈值(比如独立模式下有 3 个 client 连接到了 token server,然后配的单机均摊阈值为 10,则计算出的集群总量
就为 30),按照计算出的总的阈值来进行限制。这种方式根据当前的连接数实时计算总的阈值,对于机器经常进行 变更的环境非常适合。
启动方式
Sentinel 集群限流服务端有两种启动方式:
独立模式(Alone),即作为独立的 token server 进程启动,独立部署,隔离性好,但是需要额外的部署操作。独立模式适合作为 Global Rate Limiter 给集群提供流控服务。
嵌入模式(Embedded),即作为内置的 token server 与服务在同一进程中启动。在此模式下,集群中各个 实例都是对等的,token server 和 client 可以随时进行转变,因此无需单独部署,灵活性比较好。但是隔离性不 佳,需要限制 token server 的总 QPS,防止影响应用本身。嵌入模式适合某个应用集群内部的流控。
云上版本 AHAS Sentinel 提供开箱即用的全自动托管集群流控能力,无需手动指定/分配 token server 以及管理连接状 态,同时支持分钟小时级别流控、大流量低延时场景流控场景,同时支持 Istio/Envoy 场景的 Mesh 流控能力。
链接:http://note.youdao.com/noteshare?
id=fc7d801d1a8213fc4d1b691302e82a62&sub=675ACF22A59841AFAECD58A0A9B4E151
6.Sentinel规则持久化
- Sentinel持久化模式
Sentinel规则的推送有下面三种模式:
原始模式 :API 将规则推送至客户端并直接更新到内存中,扩展写数据源
(WritableDataSource) 简单,无任何依赖
Pull 模式:扩展写数据源(WritableDataSource), 客户端主动向某个规则管理中心定期轮询拉取规 则,这个规则中心可以是 RDBMS、文 件 等 简单,无任何依赖;规则持久化
Push 模式 :规则持久化;一致性;快速引入第三方依赖
6.1 原始模式
如果不做任何修改,Dashboard 的推送规则方式是通过 API 将规则推送至客户端并直接更
新到内存中:
这种做法的好处是简单,无依赖;坏处是应用重启规则就会消失,仅用于简单测试,不能
用于生产环境。
6.2 拉模式
pull 模式的数据源(如本地文件、RDBMS 等)一般是可写入的。使用时需要在客户端注册
数据源:将对应的读数据源注册至对应的 RuleManager,将写数据源注册至 transport 的
WritableDataSourceRegistry 中。
6.3 推模式
生产环境下一般更常用的是 push 模式的数据源。对于 push 模式的数据源,如远程配置中心
(ZooKeeper, Nacos, Apollo等等),推送的操作不应由 Sentinel 客户端进行,而应该经控
制台统一进行管理,直接进行推送,数据源仅负责获取配置中心推送的配置并更新到本
地。因此推送规则正确做法应该是 配置中心控制台/Sentinel 控制台 → 配置中心 →
Sentinel 数据源 → Sentinel,而不是经 Sentinel 数据源推送至配置中心。这样的流程就非
常清晰了
7.基于Nacos配置中心控制台实现推送
引入依赖
<!--sentinel启动器--><dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId></dependency><!--基于Nacos配置中心控制台实现推送--><dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-datasource-nacos</artifactId></dependency>
nacos配置中心中配置流控规则
[{"resource": "/order/pay","controlBehavior": 0,"count": 3.0,"grade": 1,
"limitApp": "default","strategy": 0}]
yml文件
server:port: 8070
spring:application:name: order-sentinelcloud:sentinel:transport:dashboard: 127.0.0.1:8080web-context-unify: false # 默认将调用链路收敛, 导致链路流控效果无效datasource:flow-rule:nacos:server-addr: 127.0.0.1:8848 # nacos地址username: nacospassword: nacosdataId: order-sentinel-flow-rulerule-type: flow
OrderController .java
package com.wang.controller;import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
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 java.util.concurrent.TimeUnit;/*** @BelongsProject: SpringCloudAlibabaLearn* @BelongsPackage: com.wang.controllter* @Author: wang fei* @CreateTime: 2023-01-16 16:48* @Description: TODO* @Version: 1.0*/
@RestController
@RequestMapping("/order")
public class OrderController {@GetMapping("/pay")
// @SentinelResource(value = "pay",blockHandler = "flowBlockHandler")public String pay(){return "success";}@GetMapping("/flowThread")@SentinelResource(value = "flowThread",blockHandler = "flowBlockHandler")public String flowThread() throws InterruptedException {TimeUnit.SECONDS.sleep(5);return "success";}//自定义流控处理返回方法public String flowBlockHandler(BlockException e){return "流控成功 ,阻塞服务";}@GetMapping("/add")public String add(){return "下单成功";}@GetMapping("/get")public String get(){return "查询成功";}/*** 热点规则,必须使用@SentinelResource* @param id* @return* @throws InterruptedException*/@RequestMapping("/get/{id}")@SentinelResource(value = "getById",blockHandler = "HotBlockHandler")public String getById(@PathVariable("id") Integer id) throws InterruptedException {System.out.println("正常访问");return "正常访问";}public String HotBlockHandler(@PathVariable("id") Integer id,BlockException e) throws InterruptedException {return "热点异常处理";}
}
登录sentinel
查看nacos配置的配置信息。