文章目录
- 概述
- 官网
- 解决的问题
- 主要特性
- 配置
- 下载可视化控制台
- POM
- YML
- 流控规则
- 直接(默认)
- 关联
- 链路
- 降级规则
- 降级策略实战
- RT
- 异常比例
- 异常数
- 热点key限流
- 示例:
- 高级选项:参数例外项
- 其他
- 系统规则
- @SentinelResource
- 按资源名称限流+后续处理
- 按照Url地址限流+后续处理
- 面临的问题
- 客户自定义限流处理逻辑
- 服务熔断功能
- OpenFeign
- 多种熔断框架比较
- 规则持久化
- 配置
- POM
- YML
- 添加Nacos业务规则配置
- 其他示例
- 自适应限流的示例
概述
官网
https://github.com/alibaba/Sentinel
中文:
https://github.com/alibaba/Sentinel/wiki/%E4%BB%8B%E7%BB%8D
https://sentinelguard.io/zh-cn/docs/introduction.html
解决的问题
- 服务雪崩
- 服务降级
- 服务熔断
- 服务限流
主要特性
配置
下载可视化控制台
https://github.com/alibaba/Sentinel/releases
POM
<!--SpringCloud ailibaba sentinel -->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--SpringCloud ailibaba sentinel-datasource-nacos 后续做持久化用到-->
<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
YML
server:port: 8401spring:application:name: cloudalibaba-sentinel-servicecloud:nacos:discovery:#Nacos服务注册中心地址server-addr: localhost:8848sentinel:transport:#配置Sentinel dashboard地址dashboard: localhost:8080#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口port: 8719management:endpoints:web:exposure:include: '*'
Sentinel采用的懒加载
流控规则
直接(默认)
- 资源名:默认rest路径名
- 来源:默认
关联
当与A关联的资源B达到阀值后,就限流A自己(B导致A挂)
B惹事,A挂了
1.预热
- 公式:阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值
- 默认coldFactor为3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。
- 刚开始不行,后续慢慢OK
应用场景:
如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值。
2.匀速排队
匀速排队,阈值必须设置为QPS
https://github.com/alibaba/Sentinel/wiki/%E6%B5%81%E9%87%8F%E6%8E%A7%E5%88%B6
设置含义:/testA每秒1次请求,超过的话就排队等待,等待的超时时间为20000毫秒。
链路
降级规则
https://github.com/alibaba/Sentinel/wiki/%E7%86%94%E6%96%AD%E9%99%8D%E7%BA%A7
-
Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,
让请求快速失败,避免影响到其它的资源而导致级联错误。 -
当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。
半开的状态系统自动去检测是否请求有异常,
没有异常就关闭断路器恢复使用,
有异常则继续打开断路器不可用。具体可以参考Hystrix
Sentinel的断路器是没有半开状态的
降级策略实战
RT
异常比例
按照上述配置,
单独访问一次,必然来一次报错一次(int age = 10/0),调一次错一次;
开启jmeter后,直接高并发发送请求,多次调用达到我们的配置条件了。
断路器开启(保险丝跳闸),微服务不可用了,不再报错error而是服务降级了。
异常数
时间窗口一定要大于等于60秒。
热点key限流
官网:
https://github.com/alibaba/Sentinel/wiki/%E7%83%AD%E7%82%B9%E5%8F%82%E6%95%B0%E9%99%90%E6%B5%81
何为热点
热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的TopN数据,并对其访问进行限流或者其它操作
兜底方法:
分为系统默认和客户自定义,两种
从HystrixCommand 到@SentinelResource
限流模式只支持QPS模式,固定写死了。(这才叫热点)
- @SentinelResource注解的方法参数索引,0代表第一个参数,1代表第二个参数,以此类推
- 单机阀值以及统计窗口时长表示在此窗口时间超过阀值就限流。
- 上面的抓图就是第一个参数有值的话,1秒的QPS为1,超过就限流,限流后调用dealHandler_testHotKey支持方法。
示例:
class TestController{// 此处value的值是资源名可以为abc都行与之后dashboard中配置的资源名对应就可以@GetMapping("/testHotKey")@SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")public String testHotKey(@RequestParam(value = "p1",required = false) String p1,@RequestParam(value = "p2",required = false) String p2){//int age = 10/0;return "------testHotKey";}public String deal_testHotKey (String p1, String p2, BlockException exception){return "------deal_testHotKey,o(╥﹏╥)o"; //sentinel系统默认的提示:Blocked by Sentinel (flow limiting)}
}
高级选项:参数例外项
前提条件
注意:热点参数的注意点,参数必须是基本类型或者String
当p1等于5的时候,阈值变为200
其他
@SentinelResource
处理的是Sentinel控制台配置的违规情况,有blockHandler方法配置的兜底处理;
RuntimeException
int age = 10/0,这个是java运行时报出的运行时异常RunTimeException,@SentinelResource不管
总结
@SentinelResource主管配置出错,运行出错该走异常走异常
// 有fallback解决后面会细看@SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey" fallBack="")
系统规则
官网:
https://github.com/alibaba/Sentinel/wiki/%E7%B3%BB%E7%BB%9F%E8%87%AA%E9%80%82%E5%BA%94%E9%99%90%E6%B5%81
仅对入口流量生效
- 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 达到阈值即触发系统保护。
@SentinelResource
按资源名称限流+后续处理
按照Url地址限流+后续处理
面临的问题
- 系统默认的,没有体现我们自己的业务要求。
- 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观。
- 每个业务方法都添加一个兜底的,那代码膨胀加剧。
- 全局统一的处理方法没有体现。
客户自定义限流处理逻辑
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",blockHandlerClass = CustomerBlockHandler.class,//异常处理类blockHandler = "handlerException2")//异常处理方法
public CommonResult customerBlockHandler()
{return new CommonResult(200,"按客戶自定义",new Payment(2020L,"serial003"));
}
public class CustomerBlockHandler
{public static CommonResult handlerException(BlockException exception){return new CommonResult(4444,"按客戶自定义,global handlerException----1");}public static CommonResult handlerException2(BlockException exception){return new CommonResult(4444,"按客戶自定义,global handlerException----2");}
}
服务熔断功能
sentinel整合ribbon+openFeign+fallback
fallback管运行异常(管java)
blockHandler管控制台配置违规(管dashboard中配置)
@SentinelResource(value = "fallback",
fallback = "handlerFallback",
blockHandler = "blockHandler")
若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。
exceptionsToIgnore
//exceptionsToIgnore 忽略该种异常,sentinel不进行流量拦截
@SentinelResource(value = "fallback",
fallback = "handlerFallback",blockHandler = "blockHandler",
exceptionsToIgnore = {IllegalArgumentException.class})
OpenFeign
POM
<!--SpringCloud openfeign -->
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
激活Sentinel对Feign的支持
# 激活Sentinel对Feign的支持
feign:sentinel:enabled: true
带@FeignClient注解的业务接口
/*** 使用 fallback 方式是无法获取异常信息的,* 如果想要获取异常信息,可以使用 fallbackFactory参数*/
@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)//调用中关闭9003服务提供者
PaymentFallbackService
@Component
public class PaymentFallbackService implements PaymentService
{@Overridepublic CommonResult<Payment> paymentSQL(Long id){return new CommonResult<>(444,"服务降级返回,没有该流水信息",new Payment(id, "errorSerial......"));}
}
Controller
//==================OpenFeign
@Resource
private PaymentService paymentService;@GetMapping(value = "/consumer/openfeign/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id)
{if(id == 4){throw new RuntimeException("没有该id");}return paymentService.paymentSQL(id);
}
主启动
@EnableFeignClients
多种熔断框架比较
规则持久化
配置
POM
<!--SpringCloud ailibaba sentinel-datasource-nacos -->
<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
YML
spring:application:name: cloudalibaba-sentinel-servicecloud:nacos:discovery:server-addr: localhost:8848 #Nacos服务注册中心地址sentinel:transport:dashboard: localhost:8080 #配置Sentinel dashboard地址port: 8719datasource:ds1:nacos:server-addr: localhost:8848dataId: cloudalibaba-sentinel-servicegroupId: DEFAULT_GROUPdata-type: jsonrule-type: flow
添加Nacos业务规则配置
[{"resource": "/rateLimit/byUrl","limitApp": "default","grade": 1,"count": 1,"strategy": 0,"controlBehavior": 0,"clusterMode": false}
]
刷新sentinel
重启服务后可能多次调用接口才会通过持久化验证
其他示例
摘自官网
自适应限流的示例
https://github.com/alibaba/Sentinel/blob/master/sentinel-demo/sentinel-demo-basic/src/main/java/com/alibaba/csp/sentinel/demo/system/SystemGuardDemo.java
public class SystemGuardDemo {private static AtomicInteger pass = new AtomicInteger();private static AtomicInteger block = new AtomicInteger();private static AtomicInteger total = new AtomicInteger();private static volatile boolean stop = false;private static final int threadCount = 100;private static int seconds = 60 + 40;public static void main(String[] args) throws Exception {tick();initSystemRule();for (int i = 0; i < threadCount; i++) {Thread entryThread = new Thread(new Runnable() {@Overridepublic void run() {while (true) {Entry entry = null;try {entry = SphU.entry("methodA", EntryType.IN);pass.incrementAndGet();try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {// ignore}} catch (BlockException e1) {block.incrementAndGet();try {TimeUnit.MILLISECONDS.sleep(20);} catch (InterruptedException e) {// ignore}} catch (Exception e2) {// biz exception} finally {total.incrementAndGet();if (entry != null) {entry.exit();}}}}});entryThread.setName("working-thread");entryThread.start();}}private static void initSystemRule() {SystemRule rule = new SystemRule();// max load is 3rule.setHighestSystemLoad(3.0);// max cpu usage is 60%rule.setHighestCpuUsage(0.6);// max avg rt of all request is 10 msrule.setAvgRt(10);// max total qps is 20rule.setQps(20);// max parallel working thread is 10rule.setMaxThread(10);SystemRuleManager.loadRules(Collections.singletonList(rule));}private static void tick() {Thread timer = new Thread(new TimerTask());timer.setName("sentinel-timer-task");timer.start();}static class TimerTask implements Runnable {@Overridepublic void run() {System.out.println("begin to statistic!!!");long oldTotal = 0;long oldPass = 0;long oldBlock = 0;while (!stop) {try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {}long globalTotal = total.get();long oneSecondTotal = globalTotal - oldTotal;oldTotal = globalTotal;long globalPass = pass.get();long oneSecondPass = globalPass - oldPass;oldPass = globalPass;long globalBlock = block.get();long oneSecondBlock = globalBlock - oldBlock;oldBlock = globalBlock;System.out.println(seconds + ", " + TimeUtil.currentTimeMillis() + ", total:"+ oneSecondTotal + ", pass:"+ oneSecondPass + ", block:" + oneSecondBlock);if (seconds-- <= 0) {stop = true;}}System.exit(0);}}
}