Sentinel 入门学习记录

    技术2023-09-16  181

    1.Sentinel是什么?

    随着分布式系统变得越来越流行,服务之间的可靠性变得比以往任何时候都更加重要。Sentinel是强大的流控制组件,以“流”为切入点,涵盖多个领域,包括流控制,并发限制,电路中断和自适应系统保护,以确保微服务的可靠性。 一句话讲就是Spring Cloud Alibaba用来替换之前的Hystrix的技术。

    2.有什么用?

    用来做系统流量控制、熔断降级、系统的负载保护等。

    3.下载并启动Sentinel

    1.下载地址 2.启动

    java -jar sentinel-dashboard-1.7.2.jar

    3.访问Dashboard,用户名密码都是sentinel

    4.测试准备

    1.新建一个springboot项目 2.引入依赖

    <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!-- sentinel-datasource-nacos 后续持久化用 --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>

    3.启动类添加注解@EnableDiscoveryClient

    @SpringBootApplication @EnableDiscoveryClient public class AlibabaSentinelServiceApplication { public static void main(String[] args) { SpringApplication.run(AlibabaSentinelServiceApplication.class, args); } }

    4.application.properties配置

    server.port=8401 spring.application.name=cloudalibaba-sentinel-service spring.cloud.nacos.discovery.server-addr=localhost:8848 spring.cloud.sentinel.transport.dashboard=localhost:8080 spring.cloud.sentinel.transport.port=8719 spring.cloud.sentinel.datasource.dsl.nacos.server-addr=localhost:8848 spring.cloud.sentinel.datasource.dsl.nacos.data-id=${spring.application.name} spring.cloud.sentinel.datasource.dsl.nacos.group-id=DEFAULT_GROUP spring.cloud.sentinel.datasource.dsl.nacos.data-type=json spring.cloud.sentinel.datasource.dsl.nacos.rule-type.=flow management.endpoints.web.exposure.include=*

    5.提供测试接口

    @RestController public class FlowLimitController { private static Logger log = LoggerFactory.getLogger(FlowLimitController.class); @GetMapping("/testA") public String testA(){ // try { // TimeUnit.MILLISECONDS.sleep(800); // } catch (InterruptedException e) { // e.printStackTrace(); // } return "testA-----"; } @GetMapping("/testB") public String testB(){ log.info(Thread.currentThread().getName() + "...testB "); return "testB -----"; } }

    6.启动sentinel和nacos 不懂nacos可以参考

    5.测试流控规则

    资源名:唯一名称,默认请求路径针对来源:Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)阈值类型/单机阈值 QPS(每秒钟的请求数量):当调用该api的QPS达到阈值的时候,进行限流线程数:当调用该api的线程数达到阈值的时候,进行限流 流控模式 直接:api达到限流条件时,直接限流关联:当关联的资源达到阈值时就限流自己。 A接口与B接口关联,当B接口到达阈值,让A接口限流起到保护B接口的作用。 例如支付接口与下单接口,当支付接口到达阈值,让下单接口限流,起到保护支付接口的作用。链路:只记录指定链路上的流量(指定资源入口资源进来的流量,如果达到阈值,就进行限流) 流控效果 快速失败:直接失败抛异常Warm up:根据codeFactor(冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值。 一般用于类似秒杀的功能。排队等候:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效。

    5.1测试QPS到达阈值

    配置如下: 当你连续点击看到如下限流提示(1s请求数量超过配置阈值)

    5.2测试线程数到达阈值

    配置如下: 为了方便测试让每个被调用的方法睡眠一会

    @GetMapping("/testA") public String testA(){ //测试线程阈值 try { TimeUnit.MILLISECONDS.sleep(800); } catch (InterruptedException e) { e.printStackTrace(); } return "testA-----"; }

    当你连续点击看到如下限流提示(1s请求数量超过配置阈值)

    5.3测试关联限流

    配置如下 用postman模拟与/testA关联的/testB到达阈值 postman启动之后调用/testA可以看到该接口被限流。

    5.4测试Warm up(限流冷启动)

    配置如下(访问流量忽然增大时,从阈值/冷加载因子 开始经过预热时长达到每秒可访问阈值,即从流量增大时刚开始可以支持每秒2次访问,经过3s可以支持每秒6次访问接口),默认冷加载因子(coldFactor)为3 可以点击访问/testA接口测试,开始慢慢慢点击没问题,然后加快速度会出现限流,过一会之后,限流就没有了。

    5.5.排队等候

    配置如下 为了方便查看调用,接口中打印线程名称

    @GetMapping("/testB") public String testB(){ log.info(Thread.currentThread().getName() + "...testB "); return "testB -----"; }

    重启项目调用,可以在控制台中看到调用记录

    6.测试降级规则

    官方文档

    RT: 当资源的平均响应时间超过阈值(这里阈值即自己配置的毫秒值)且在时间窗口内通过的请求>= 5,两个条件同时满足触发熔断降级窗口期后关闭断路器。RT最大4900(更大通过 -Dcsp.sentinel.statistic.max.rt=xxx 来配置) 异常比例: QPS>=5且异常比例(秒级统计)超过阈值时,触发降级;时间窗口结束后,关闭降级。异常数: 异常数(分钟统计)超过阈值,触发降级;时间窗口结束后,关闭降级

    Sentienl熔断降级会在调用调用链路中某个资源出现不稳定时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其他的资源而导致级联错误。 当资源降级后,在接下来的降级时间窗口之内,对该资源的调用自动熔断(默认行为是抛出DegradeException)


    6.1测试RT

    1.添加测试接口

    @GetMapping("/testD") public String testD(){ try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } log.info("testD 测试RT"); return "testD -----"; }

    为了让平均响应时间增大,在代码中加入sleep休眠。 2.利用JMater进行压力测试 1s 10个线程访问/testD接口 3.配置如下 4.启动JMeter,自己调用/testD,可以看到该接口已被熔断降级。 熔断分析:1.平均响应时间(1000ms)超出阈值(200ms) 2.在时间窗口内通过请求10*5=50>=5。满足两个条件,所以发生熔断降级

    6.2测试异常比例

    资源每秒请求量>=5&&每秒异常总数占通过量的比值超过阈值之后,资源进入降级状态,即在接下的时间窗口之内,对这个方法的调用都会自动的返回(服务熔断)。异常比率阈值范围[0.0,1.0],代表0%-100%。

    1.提供测试异常比例接口

    @GetMapping("/testException") public String testException(){ log.info("testException 异常比例"); int age = 10 /0 ; return "testException -----"; }

    2.利用JMater进行压力测试,除接口外其他配置同上面测试RT 3.sentinel配置 4.重启项目并启动JMeter进行测试,调用/testException可以服务熔断提示信息

    6.3测试异常数

    当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的,若时间窗口小于60s,则结束熔断状态后可能再进入熔断状态。

    1.提供测试接口

    @GetMapping("/testExceptionCount") public String testExceptionCount(){ log.info("testExceptionCount 异常数"); int age = 10 /0 ; return "testExceptionCount -----"; }

    2.sentinel配置 3.重启项目进行测试 前面5次访问都报错,后面访问进入熔断后降级。 熔断后降级如下图

    7.热点参数限流

    7.1普通热点参数限流

    1.提供接口进行测试

    @GetMapping("/testHotKey") @SentinelResource(value = "testHotKey", blockHandler = "dealTestHotKey") public String testHotKey(@RequestParam(value = "p1", required = false) String p1, @RequestParam(value = "p2", required = false) String p2){ return "testHotKey -----"; } public String dealTestHotKey(String p1, String p2, BlockException blockException){ return "dealTestHotKey---------"; }

    2.sentinel配置 上面的配置第一个参数p1,当QPS超过1秒1次点击后马上被限流。 3.重启项目测试

    7.2参数例外项热点限流

    1.sentinel配置如下 以上配置含义,当第一个参数p1的值为5时接口/testHotKey的流量阈值为200

    注意:

    @SentinelResource处理的是控制台配置的违规情况,有blockHandler方法配置的兜底处理。 但是@SentinelResource不管代码中出现的运行时期异常(RuntimeException)

    8.@SentinelResource配置

    8.1按资源名称或URL地址限流加后续处理

    1.提供接口

    /** * (违反sentinel配置)手动配置兜底处理blockHandler * @return */ @GetMapping(value = "/byResource") @SentinelResource(value = "byResource", blockHandler = "handleException") public CommonResult byResource(){ return new CommonResult(200, "按资源名称限流测试OK"); } public CommonResult handleException(BlockException blockException){ return new CommonResult<>(444, blockException.getClass().getCanonicalName()+"\t服务不可用" ); } /** * 默认处理 * @return */ @GetMapping("/rateLimit/byUrl") @SentinelResource(value = "byUrl") public CommonResult byUrl(){ return new CommonResult(200, "by url限流测试OK"); }

    2.sentinel配置 资源名称: url:

    3.重新启动测试 访问byResource当每秒访问超过1次时,使用自定义的兜底处理。 访问/rateLimit/byUrl当每秒访问超过1次时,使用默认处理。


    以上配置存在问题:

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

    8.1解决存在的问题

    1.提供接口

    public class CustomerBlockHandler { public static CommonResult handlerException(BlockException exception) { return new CommonResult(444, "客户自定义,global handlerException---1"); } public static CommonResult handlerException2(BlockException exception) { return new CommonResult(444, "客户自定义,global handlerException---2"); } } @GetMapping("/rateLimit/customerBlockHandler") @SentinelResource(value = "customerBlockHandler", blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException2") public CommonResult customerBlockHandler(){ return new CommonResult(200, "客户自定义 限流测试OK"); }

    2.sentinel中配置 3.重启测试

    9.整合Ribbon和OpenFeign

    9.1准备

    1.创建三个springboot项目,分别为alibaba-consumer2、alibaba-provider3、alibaba-provider4(alibaba-provider3/alibaba-provider4一样) 由于东西过多这里不罗列,详细参考https://github.com/xiaoxiaoshou/springclouddemo

    9.2Ribbon系列

    主要示例代码:

    @RestController @Slf4j @RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) public class CircleBreakerController { private static final String SERVICE_URL = "http://nacos-payment-provider"; @Resource private RestTemplate restTemplate; @RequestMapping("/consumer/fallback/{id}") // @SentinelResource(value = "fallback") //没有配置 // @SentinelResource(value = "fallback",fallback = "handlerFallback") //配置了fallback的,fallback只负责业务异常 // @SentinelResource(value = "fallback",blockHandler = "blockHandler") // 配置了blockHandler,只负责sentinel控制台配置违规 @SentinelResource(value = "fallback",fallback = "handlerFallback", blockHandler = "blockHandler") // 配置了blockHandler和fallback // @SentinelResource(value = "fallback",fallback = "handlerFallback", blockHandler = "blockHandler", exceptionsToIgnore = {IllegalArgumentException.class}) // 忽略运行时IllegalArgumentException异常不进行自定义处理 public CommonResult<Payment> fallback(@PathVariable("id") Long id){ CommonResult<Payment> commonResult = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class); if(id == 4){ throw new IllegalArgumentException("IllegalArgumentException,非法参数异常"); }else if(commonResult.getData() == null){ throw new NullPointerException("NullPointerException,该ID没有记录,空指针异常"); } return commonResult; } // 本例是fallback public CommonResult handlerFallback(Long id, Throwable e){ Payment payment = new Payment(id, null); return new CommonResult(444, "兜底异常handler,exception内容"+e.getMessage(), payment); } public CommonResult blockHandler(Long id, BlockException exception){ Payment payment = new Payment(id, null); return new CommonResult<>(445, "blockHandler-sentinel 限流,无此流水号:blockException" + exception.getMessage(), payment); } } fallback对应方法(handlerFallback方法)处理代码运行时期异常blockHandler对应方法(blockHandler)处理Sentinel中违规exceptionsToIgnore忽略运行时期某个异常不进行自定义处理

    配置Sentinel(对资源fallback阈值为1)并启动项目测试,可以看到既可以处理调用过程中对应Sentinel违规也可以处理运行时期异常。

    9.3 OpenFeign系列

    1.主要配置

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

    2.绑定对应服务

    @FeignClient(value = "nacos-payment-provider", fallback = PaymentFallbackService.class) public interface PaymentService { @GetMapping("/paymentSQL/{id}") CommonResult<Payment> paymentSQL(@PathVariable("id") Long id); } @Component public class PaymentFallbackService implements PaymentService { @Override public CommonResult<Payment> paymentSQL(Long id) { return new CommonResult<>(444, "fallback"); } }

    3.提供访问接口

    @Resource private PaymentService paymentService; @GetMapping("/consumer/paymentSQL/{id}") public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id){ return paymentService.paymentSQL(id); }

    4.重启项目即可冲过consumer接口调用第三方接口


    各熔断器的比较:

    10.规则持久化

    当你每次重启对应服务,你会发现在Sentinel中配置对应的规则就没有了,在生产环境中我们需要配置规则的持久化(持久化工具都可以,官方推荐Nacos)。

    1.引入依赖

    <!-- sentinel-datasource-nacos 后续持久化用 --> <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>

    2.配置文件配置

    spring.cloud.sentinel.datasource.dsl.nacos.server-addr=localhost:8848 spring.cloud.sentinel.datasource.dsl.nacos.data-id=${spring.application.name} spring.cloud.sentinel.datasource.dsl.nacos.group-id=DEFAULT_GROUP spring.cloud.sentinel.datasource.dsl.nacos.data-type=json spring.cloud.sentinel.datasource.dsl.nacos.rule-type=flow

    3.在nacos中配置

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

    配置项含义:

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