SpringCloud:Sentinel实现熔断与限流(2)

热点key限流 / 系统规则(系统自适应限流)/ @SentinelResource 注解详解 /  服务熔断 / 持久化规则

1.热点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

热点参数限制会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限制。热点参数限流可以看作是一种特殊的流量控制,仅对包含热点参数的资源调用生效。

  • 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
  • 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制

1.1基本使用

兜底防范分为系统默认和客户自定义:

  1. sentinel系统默认的提示:Blocked by Sentinel (flow limiting)。
  2. 类似于@HystrixCommand, 引入@SentinelResource注解。

资源名:唯一路径,默认为请求路径。此处必须是 @SentinelResource 注解的 value 属性值,配置@GetMapping 的请求路径无效)

1.1.1测试方法

在8401的controller中,加入热点测试方法。

    @GetMapping("/testHotKey")
    @SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey") //这里的名称可以随便写,但是一般跟rest地址一样
    public String testHoutKey(@RequestParam(value = "p1",required = false) String p1,
                              @RequestParam(value = "p2",required = false) String p2){
        return "testhotkey**";
    }
    //这里是我们自定义的兜底方法,BlockException不要打成了BlockedException
    public String deal_testHotKey(String p1, String p2, BlockException exception){
        return "deal_testHotKey 错误页!!";
    }

@SentinelResource注解分析

  • 其中 value = "testHotKey" 是一个标识(Sentinel资源名),与rest的`/testHotKey`对应,这里value的值可以任意写,但是我们约定与rest地址一致,唯一区别是没有`/`。
  • blockHandler = "del_testHotKey" 则表示如果违背了Sentinel中配置的流控规则,就会调用我们自己的兜底方法del_testHotKey

1.1.2配置热点key限流规则

方法testHotKey里面第一个参数只要QPS超过每秒1次,马上降级处理

1.1.3 测试

  • 1次/s正常显示,迅速点击两次,触发热点限流,执行自定义兜底方法:http://127.0.0.1:8401/testHotKey?p1=a
  • 仅传入参数p2没有任何影响: http://localhost:8401/testHotKey?p2=b
  • 个通过jmeter压测http://localhost:8401/testHotKey?p1=a&p2=b,另外再单独使用浏览器访问http://localhost:8401/testHotKey?p2=b,发现只带参数p2访问没有任何影响。

参数例外项

期望p1参数当它是某个特殊值时,它的限流值和平时不一样,比如当p1的值等于5时,它的阈值可以达到200。

狂点http://localhost:8401/testHotKey?p1=5&p2=b 没有限流

其他

手动添加一个异常:

测试直接错误页面。

Sentinel它只管你有没有触发它的限流规则,也可以说只管这个web交互页面(控制台)里面的东西。 配置类的东西Sentinel可以管,java异常的错误Sentinel不管。

总结:
@SentinelResource主管配置出错,运行出错该走异常走异常

2.系统规则(系统自适应限流)

Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,(让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。)

  • 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 达到阈值即触发系统保护。

如把入口QPS设置为1,不管是/testA还是/testB 只要QPS > 1 整个系统就不能用。

3.@SentinelResource 注解详解

3.1按资源名称限流+后续处理

3.1.1 修改8401

(1) pom

引入我们自定义的公共api jar包

        <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
            <groupId>com.tinstu.cloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>

(2) 业务类

RateLimitController

package com.tinstu.springcloud.controller;

import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.tinstu.springcloud.bean.CommonResult;
import com.tinstu.springcloud.bean.Payment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class RateLimitController {
    @GetMapping("/byResource")
    @SentinelResource(value = "byResource", blockHandler = "handleException")
    public CommonResult byResource() {
        return new CommonResult(200, "按资源名称限流测试OK", new Payment(2020L, "serial001"));
    }

    public CommonResult handleException(BlockException exception) {
        return new CommonResult(444, exception.getClass().getCanonicalName() + "\t 服务不可用");
    }
}

3.1.2 配置流控规则——按资源名称添加流控规则

3.1.3测试

自测:

触发流控规则:

3.1.4 问题

如果我们重启8401会发现之前配置的一些规则都没有了。难道每次重启服务器都要重新配置一遍规则吗?规则如何进行持久化?

8.2 按照Url地址限流+后续处理

通过访问的URL来限流,会返回Sentinel自带默认的限流处理信息

修改业务层

@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl")
public CommonResult byUrl()
{
    return new CommonResult(200,"按url限流测试OK",new Payment(2020L,"serial002"));
}

按rest URI设置流控规则后,触发流控规则:

3.3 总结以及面临的问题

  • 依照现有条件,我们自定义的处理方法又和业务代码耦合在一块,不直观。如果都用系统默认的,就没有体现我们自己的业务要求。
  • 如果每个业务方法/API接口都添加一个兜底的,那代码膨胀加剧。
  • 全局统一的处理方法没有体现。

3.4 客户自定义限流处理逻辑

为了解决代码耦合与膨胀的问题

3.4.1创建CustomerBlockHandler类用于自定义限流处理逻辑(单独放在一个包里)

public class CustomerBlockHandler {
    public static CommonResult handlerException(BlockException e) {
        return new CommonResult(4444, "按客户自定义, global handlerException----1");
    }
    
    public static CommonResult handlerException2(BlockException e) {
        return new CommonResult(4444, "按客户自定义, global handlerException----2");
    }
}

3.4.2 修改RateLimitController,使用自定义处理逻辑类

    //CustomerBlockHandler自定义类,来处理服务降级、限流提示.....
    /**
     * 自定义通用的限流处理逻辑,
     * blockHandlerClass = CustomerBlockHandler.class
     * blockHandler = handleException2
     * 上述配置:找CustomerBlockHandler类里的handleException2方法进行兜底处理
     */
    //自定义通用的限流处理逻辑
    @GetMapping("/rateLimit/customerBlockHandler")
    @SentinelResource(value = "customerBlockHandler",
            blockHandlerClass = CustomerBlockHandler.class,
            blockHandler = "handlerException2")
    public CommonResult customerBlockHandler() {
        return new CommonResult(200, "按客户自定义", new Payment(2020L, "serial003"));
    }

3.4.3 测试

设置流控,触发流控后:

3.5 更多属性说明

文档:https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81

4.服务熔断

主要内容:

  • sentinel分别整合ribbon+openFeign以及设置fallback
  • 熔断框架比较

4.1 Ribbon系列

nacos中整合了Ribbon,所以直接使用nacos就行。启动nacos和Sentinel。

fallback和blockHandler

fallback管运行异常,blockHandler管配置违规。
fallback对应服务降级,就是服务出错了应该怎么办(需要有个兜底方法);
blockHandler对应服务熔断,就是我现在服务不可用,我应该怎么办,怎么给客户一个户提示(同样需要一个兜底方法)

同时配置fallback:处理业务异常(微服务自身异常,服务降级)和blockHandler:处理触发sentinel配置(微服务不可用,服务熔断)时。在没有违反sentinel规则时,出现业务异常(降级)走fallback方法;违反了sentinel规则时,直接微服务不可用(熔断),走blockHandler指定的自定义方法。

也就是说 有java异常的同时,也违反sentinel规则,此时会走blockHandler(熔断)

异常忽略属性

即出现指定的异常,不执行兜底方法,而是直接返回错误页面!

当然,触发流控之后,仍然通过blockHandler指定的方法进行熔断兜底。

Feign系列

修改消费者模块

(1) pom

加入feign的依赖

<!--SpringCloud openfeign -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

(2) yml

激活Sentinel对Feign的支持

# 激活Sentinel对Feign的支持
feign:
  sentinel:
    enabled: true

(3) 业务类

后续84的controller不找restTemplate(Ribbon),不是restTemplate去调用payment微服务中的接口。而是通过调用PaymentFeignService,service再去调用payment微服务中的端口。

Feign需要定义一个业务逻辑(service)接口+ @FeignClient注解以调用服务提供者。
新建PaymentFeignService interface:

PaymentFeignService

package com.atguigu.cloudalibaba.service;

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

// 指明调用失败的兜底方法在PaymentFallbackService
// 使用 fallback 方式是无法获取异常信息的,
// 如果想要获取异常信息,可以使用 fallbackFactory参数
@FeignClient(value = "nacos-payment-provider", fallback = PaymentFallbackService.class)
public interface PaymentFeignService {
    //去nacos-payment-provider服务中找相应的接口
    // 方法签名一定要和nacos-payment-provider中controller的一致
    // 对应9003、9004中的方法
    @GetMapping(value = "/paymentSQL/{id}")
    public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}

调用失败的兜底方法:PaymentFallbackService

package com.atguigu.cloudalibaba.service;

import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import org.springframework.stereotype.Component;

@Component   //不要忘记了
public class PaymentFallbackService implements PaymentFeignService {
    //如果nacos-payment-consumer服务中的相应接口出事了,我来兜底
    @Override
    public CommonResult<Payment> paymentSQL(Long id) {
        return new CommonResult<>(444,"服务降级返回,没有该流水信息-------PaymentFallbackService",new Payment(id, "errorSerial......"));
    }
}

controller加入openFeign的接口:

//==================OpenFeign
@Resource
private PaymentFeignService paymentFeignService;

@GetMapping(value = "/consumer/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) {
    if (id == 4) {
        throw new RuntimeException("没有该id");
    }
    return paymentFeignService.paymentSQL(id);
}

(4) 主启动类
加上@EnableFeignClient注解开启OpenFeign

(5) 测试

熔断框架比较

5.持久化规则

前面我们微服务新增的限流规则后,微服务关闭后就会丢失,当时配置都限流规则都是临时的。

案例——修改8401已完成持久化设置

(1) pom.

导入持久化所需依赖

<!--SpringCloud ailibaba sentinel-datasource-nacos 持久化-->
<dependency>
  <groupId>com.alibaba.csp</groupId>
  <artifactId>sentinel-datasource-nacos</artifactId>
</dependency>

(2) yaml

application.yaml

server:
  port: 8401

spring:
  application:
    name: cloudalibaba-sentinel-service
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848 #Nacos服务注册中心地址
    sentinel:
      transport:
        #配置Sentinel dashboard地址
        dashboard: localhost:8080
        #默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
        port: 8719
      # 关闭默认收敛所有URL的入口context,不然链路限流不生效
      # Spring Cloud Alibaba 需要2.1.1.RELEASE以上版本
      web-context-unify: false
#      filter:
#        enabled: false  # 关闭自动收敛

      #持久化配置
      datasource:
        ds1:
          nacos:
            server-addr: localhost:8848
            dataId: cloudalibaba-sentinel-service
            groupId: DEFAULT_GROUP
            data-type: json
            rule-type: flow

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

(3) 添加nacos业务规则配置

我们将sentinel的流控配置保存在nacos中,因为nacos的配置持久化在了数据库中。

[
    {
        "resource": "/rateLimit/byUrl",
        "limitApp": "default",
        "grade": 1,
        "count": 1,
        "strategy": 0,
        "controlBehavior": 0,
        "clusterMode": false
    }
]

resource:资源名称;
limitApp:来源应用;
grade:阈值类型,0表示线程数,1表示QPS;
count:单机阈值;
strategy:流控模式,0表示直接,1表示关联,2表示链路;
controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待;
clusterMode:是否集群。

注意:这里如果要配nacos的命名空间(public、dev、test)的话,应该是配namespace的id,不是名称

(4) 测试

启动8401,访问8401任意接口,刷新Sentinel。可以看到Sentinel中加载了通过nacos持久化的规则配置文件

关掉8401后发现流控规则没有了

再次启动8401查看sentinel,访问几次8401后流控规则又出现了。
查看数据库,发现规则持久化到数据库中了。

阅读剩余
THE END