【SpringCloudAlibaba】Sentinel使用

概述

官网

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

解决的问题

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

主要特性

在这里插入图片描述

配置

下载可视化控制台

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

在这里插入图片描述

面临的问题

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

客户自定义限流处理逻辑

@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
{
    
    
    @Override
    public 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-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业务规则配置

在这里插入图片描述

[
    {
    
    
        "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() {
    
    
                @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);
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_45742250/article/details/132468322