【SpringCloudAlibaba】Sentinel使用

文章目录

概述

官网

https://github.com/alibaba/Sentinel

中文:
https://github.com/alibaba/Sentinel/wiki/介绍
https://sentinelguard.io/zh-cn/docs/introduction.html

解决的问题

  1. 服务雪崩
  2. 服务降级
  3. 服务熔断
  4. 服务限流

主要特性

配置

下载可视化控制台

https://github.com/alibaba/Sentinel/releases

POM

xml 复制代码
<!--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

yml 复制代码
server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        #Nacos服务注册中心地址
        server-addr: localhost:8848
    sentinel:
      transport:
        #配置Sentinel dashboard地址
        dashboard: localhost:8080
        #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
        port: 8719

management:
  endpoints:
    web:
      exposure:
        include: '*'

Sentinel采用的懒加载

流控规则

直接(默认)

  1. 资源名:默认rest路径名
  2. 来源:默认

关联

当与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/流量控制

设置含义:/testA每秒1次请求,超过的话就排队等待,等待的超时时间为20000毫秒。

链路

降级规则

https://github.com/alibaba/Sentinel/wiki/熔断降级

  • Sentinel 熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,

    让请求快速失败,避免影响到其它的资源而导致级联错误。

  • 当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出 DegradeException)。

半开的状态系统自动去检测是否请求有异常,

没有异常就关闭断路器恢复使用,

有异常则继续打开断路器不可用。具体可以参考Hystrix

Sentinel的断路器是没有半开状态的

降级策略实战

RT


异常比例


按照上述配置,

单独访问一次,必然来一次报错一次(int age = 10/0),调一次错一次;

开启jmeter后,直接高并发发送请求,多次调用达到我们的配置条件了。

断路器开启(保险丝跳闸),微服务不可用了,不再报错error而是服务降级了。

异常数

时间窗口一定要大于等于60秒。


热点key限流

官网:
https://github.com/alibaba/Sentinel/wiki/热点参数限流

何为热点

热点即经常访问的数据,很多时候我们希望统计或者限制某个热点数据中访问频次最高的TopN数据,并对其访问进行限流或者其它操作

兜底方法:

分为系统默认和客户自定义,两种

从HystrixCommand 到@SentinelResource

限流模式只支持QPS模式,固定写死了。(这才叫热点)

  • @SentinelResource注解的方法参数索引,0代表第一个参数,1代表第二个参数,以此类推
  • 单机阀值以及统计窗口时长表示在此窗口时间超过阀值就限流。
  • 上面的抓图就是第一个参数有值的话,1秒的QPS为1,超过就限流,限流后调用dealHandler_testHotKey支持方法。

示例:

java 复制代码
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主管配置出错,运行出错该走异常走异常

java 复制代码
// 有fallback解决后面会细看
 @SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey" fallBack="")

系统规则

官网:
https://github.com/alibaba/Sentinel/wiki/系统自适应限流
仅对入口流量生效

  • 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地址限流+后续处理

面临的问题

  1. 系统默认的,没有体现我们自己的业务要求。
  2. 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观。
  3. 每个业务方法都添加一个兜底的,那代码膨胀加剧。
  4. 全局统一的处理方法没有体现。

客户自定义限流处理逻辑

java 复制代码
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
        blockHandlerClass = CustomerBlockHandler.class,//异常处理类
        blockHandler = "handlerException2")//异常处理方法
public CommonResult customerBlockHandler()
{
    return new CommonResult(200,"按客戶自定义",new Payment(2020L,"serial003"));
}
java 复制代码
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中配置)

java 复制代码
@SentinelResource(value = "fallback",
fallback = "handlerFallback",
blockHandler = "blockHandler")

若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。
exceptionsToIgnore

java 复制代码
//exceptionsToIgnore 忽略该种异常,sentinel不进行流量拦截
@SentinelResource(value = "fallback",
fallback = "handlerFallback",blockHandler = "blockHandler", 
exceptionsToIgnore = {IllegalArgumentException.class})

OpenFeign

POM

xml 复制代码
<!--SpringCloud openfeign -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

激活Sentinel对Feign的支持

yml 复制代码
# 激活Sentinel对Feign的支持
feign:
  sentinel:
    enabled: true  

带@FeignClient注解的业务接口

java 复制代码
/**
 * 使用 fallback 方式是无法获取异常信息的,
 * 如果想要获取异常信息,可以使用 fallbackFactory参数
 */
@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)//调用中关闭9003服务提供者

PaymentFallbackService

java 复制代码
@Component
public class PaymentFallbackService implements PaymentService
{
    @Override
    public CommonResult<Payment> paymentSQL(Long id)
    {
        return new CommonResult<>(444,"服务降级返回,没有该流水信息",new Payment(id, "errorSerial......"));
    }
}

Controller

java 复制代码
//==================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);
}

主启动

java 复制代码
@EnableFeignClients

多种熔断框架比较

规则持久化

配置

POM

xml 复制代码
<!--SpringCloud ailibaba sentinel-datasource-nacos -->
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

YML

yml 复制代码
spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
    sentinel:
      transport:
        dashboard: localhost:8080 #配置Sentinel dashboard地址
        port: 8719
      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: cloudalibaba-sentinel-service
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

添加Nacos业务规则配置

json 复制代码
[
    {
        "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

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() {
                @Override
                public 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 3
        rule.setHighestSystemLoad(3.0);
        // max cpu usage is 60%
        rule.setHighestCpuUsage(0.6);
        // max avg rt of all request is 10 ms
        rule.setAvgRt(10);
        // max total qps is 20
        rule.setQps(20);
        // max parallel working thread is 10
        rule.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 {
        @Override
        public 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);
        }
    }
}
相关推荐
PGCCC15 分钟前
【PGCCC】postgresql 缓存池并发设计
数据库·缓存·postgresql
小爬虫程序猿22 分钟前
如何利用Python解析API返回的数据结构?
数据结构·数据库·python
wowocpp1 小时前
查看 磁盘文件系统格式 linux ubuntu blkid ext4
linux·数据库·ubuntu
Ai 编码助手7 小时前
MySQL中distinct与group by之间的性能进行比较
数据库·mysql
陈燚_重生之又为程序员7 小时前
基于梧桐数据库的实时数据分析解决方案
数据库·数据挖掘·数据分析
caridle7 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
白云如幻7 小时前
MySQL排序查询
数据库·mysql
萧鼎7 小时前
Python并发编程库:Asyncio的异步编程实战
开发语言·数据库·python·异步
^velpro^7 小时前
数据库连接池的创建
java·开发语言·数据库
荒川之神7 小时前
ORACLE _11G_R2_ASM 常用命令
数据库·oracle