SpringCloud Alibaba Sentinel实现熔断与限流

    技术2022-07-11  123

    一、Sentinel概述

    1.1 官网

    https://github.com/alibaba/Sentinel

    中文

    https://github.com/alibaba/Sentinel/wiki/介绍

    1.2 Sentinel 是什么

    随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度保护服务的稳定性。

    Sentinel 具有以下特征:

    丰富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。完善的 SPI 扩展点:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。

    1.3 去哪下

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

    1.4 能干嘛

    1.5 怎么玩

    https://spring-cloud-alibaba-group.github.io/github-pages/greenwich/spring-cloud-alibaba.html#_spring_cloud_alibaba_sentinel

    服务使用中的各种问题

    服务雪崩服务降级服务熔断服务限流

    二、安装Sentinel控制台

    2.1 Sentinel组件由两部分组成

    核心库(Java客户端)不依赖任何框架/库,能够运行于所有Java运行时环境,同时对Dubbo/SpringCloud等框架也有较好的支持。

    控制台(Dashboard)基于SpringBoot开发,打包后可以直接运行,不需要额外的Tomcat等应用容器。

    后台前台8080

    2.2 安装步骤

    2.2.1 下载

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

    下载到本地sentinel-dashboard-1.7.0.jar

    2.2.2 运行命令

    前提

    Java8环境OK

    8080端口不能被占用

    命令

    java -jar sentinel-dashboard-1.7.0.jar

    访问sentinel管理界面

    http://localhost:8080

    登陆账号密码均为sentinel

    三、初始化演示工程

    3.1 启动Nacos8848成功

    http://localhost:8848/nacos/#/login

    3.2 新建Module

    cloudalibaba-sentinel-service8401

    3.2.1 POM

    <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>cloud2020</artifactId> <groupId>com.atguigu.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloudalibaba-sentinel-service8401</artifactId> <dependencies> <dependency> <groupId>com.atguigu.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>4.6.3</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> </project>

    3.2.2 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 port: 8719 #默认8719,假如被占用了会自动从8719开始依次+1扫描。直至找到未被占用的端口 management: endpoints: web: exposure: include: '*'

    3.2.3 主启动

    package com.atguigu.springcloud.alibaba; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @EnableDiscoveryClient @SpringBootApplication public class MainApp8401 { public static void main(String[] args) { SpringApplication.run(MainApp8401.class, args); } }

    3.2.4 业务类

    package com.atguigu.springcloud.alibaba.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class FlowLimitController { @GetMapping("/testA") public String testA() { return "------testA"; } @GetMapping("/testB") public String testB() { return "------testB"; } }

    3.3 启动Sentinel8080

    java -jar sentinel-dashboard-1.7.0

    http://localhost:8080/#/dashboard

    3.4 启动8401微服务查看sentinel控制台

    启动后,访问sentinel控制台,空空如也,啥都没有

    3.4.1 Sentinel采用的懒加载说明

    执行一次访问即可

    http://localhost:8401/testA

    http://localhost:8401/testB

    效果

    3.4.2 结论

    sentinel8080正在监控微服务8401

    四、流控规则

    4.1 基本介绍

    4.2 流控模式

    4.2.1 直接(默认)

    直接->快速失败

    系统默认

    测试

    1s一次没毛病

    狂点,限流

    思考

    直接调用默认报错信息,技术方面OK,但是,是否应该有我们自己的后续处理?

    类似有一个fallback的兜底方法。

    4.2.2 关联

    是什么

    当关联的资源达到阈值时,就限流自己当与A关联的资源B达到阈值后,就限流自己B惹事,A挂了

    用postman模拟并发密集访问testB

    访问testB成功

    postman里新建多线程集合组

    将访问地址添加进新线程组

    Run运行后发现testA挂了,大批量线程高并发访问B,导致A失效了

    点击访问http://localhost:8401/testA

    结果

    Blocked by Sentinel (flow limiting)

    4.2.3 链路

    多个请求调用了同一个微服务

    4.2 流控效果

    4.2.1 直接->快速失败(默认的流控处理)

    直接失败,抛出异常

    Blocked by Sentinel(flow limiting)

    源码

    com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController

    4.2.2 预热

    说明

    公式:阈值除以coldFactor(默认值为3),经过预热时长后才会达到阈值

    官网

    默认coldFactor为3,即请求QPS从threshold/3开始,经预热时长逐渐升至设定的QPS阈值。

    限流冷启动

    https://github.com/alibaba/Sentinel/wiki/限流---冷启动

    源码

    Warmup配置

    多次点击http://localhost:8401/testB

    刚开始不行,后续慢慢OK

    应用场景

    如:秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是为了保护系统,可慢慢的把流量放进来,慢慢的把阈值增长到设置的阈值。

    4.2.3 排队等待

    匀速排队,阈值必须设置为QPS

    官网

    源码

    com.alibaba.csp.sentinel.slots.block.flow.controller.RateLimiterController

    测试

    五、降级规则

    5.1 基本介绍

    Sentinel熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。

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

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

    半开的状态系统自动去检测是否请求有异常,没有异常就关闭断路器恢复使用,有异常则继续打开断路器不可用。具体可以参考Hystrix.

    5.2 降级策略实战

    5.2.1 RT

    是什么

    每秒查询率(QPS,Queries-per-second)是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。

    测试

    代码

    package com.atguigu.springcloud.alibaba.controller; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.concurrent.TimeUnit; @RestController @Slf4j public class FlowLimitController { @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()+"\t"+"testB"); return "------testB"; } @GetMapping("/testD") public String testD() { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } log.info("testD 测试RT"); return "------testD"; } }

    配置

    jmeter压测

    按照上述配置,永远一秒钟打进来10个线程(大于5个了),调用testD,我们希望200毫秒处理完本次任务,如果超过200毫秒还没处理完,在未来1秒钟的时间窗口内,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了。后续我停止jmeter,没有这么大的访问量了,断路器关闭(保险丝恢复),微服务恢复OK。

    5.2.2 异常比例

    是什么

    测试

    代码

    @GetMapping("/testD") public String testD() { log.info("testD 测试RT"); int age = 10/0; return "------testD"; }

    配置

    jmeter

    结论

    5.2.3 异常数

    是什么

    异常数是按照分钟统计的

    测试

    代码

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

    配置

    http://localhost:8401/testE

    jmeter

    前四次,报错

    五次以后,降级

    六、热点key限流

    6.1 基本介绍

    6.2 官网

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

    6.3 @SentinelResource

    6.3.1 兜底方法

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

    之前的case,限流出问题后,都是用sentinel系统默认的提示:Blocked by Sentinel(flow limiting)

    我们能不能自定?类似hystrix,某个方法出问题了,就找对应的兜底降级方法?

    6.3.2 结论

    从HystrixCommand到@SentinelResource

    6.4 代码

    com.alibaba.csp.sentinel.slots.block.BlockException

    @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"; }

    6.5 配置

    异常打到了前台用户界面看不到,不友好 

    @SentinelResource(value = "testHotKey")

    方法testHostKey里面第一个参数,只要QPS超过每秒1次,马上降级处理,用了我们自己定义的

    @SentinelResource(value = "testHotKey",blockHandler = "deal_testHotKey")

    6.6 测试

    只要不包含热点参数,没有限流规则,所以就不会报错

    6.7 参数例外项

    上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流

    6.7.1 特殊情况

    普通

    超过1秒钟一个后,达到阈值1后马上被限流

    特例

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

    6.7.2 配置

    6.7.3 测试

    前提条件:热点参数的注意点,参数必须是基本类型或者String

     

    七、系统规则

    7.1 是什么

    https://github.com/alibaba/Sentinel/wiki/系统自适应限流

    7.2 各项配置参数说明

    7.3 配置全局QPS

    全局性的,不具体方法,所有的

    八. @SentinelResource

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

    8.1.1 启动Nacos成功

    8.1.2 启动Sentinel成功

    8.1.3 Module

    cloudalibaba-sentinel-service8401

    POM

    <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>cloud2020</artifactId> <groupId>com.atguigu.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloudalibaba-sentinel-service8401</artifactId> <dependencies> <dependency> <groupId>com.atguigu.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>com.atguigu.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>4.6.3</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> </project>

    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 port: 8719 #默认8719,假如被占用了会自动从8719开始依次+1扫描。直至找到未被占用的端口 management: endpoints: web: exposure: include: '*'

    业务类

    package com.atguigu.springcloud.alibaba.controller; import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.atguigu.springcloud.entities.CommonResult; import com.atguigu.springcloud.entities.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 服务不可用"); } }

    8.1.4 配置流控规则

    配置步骤

    表示1秒钟内查询次数大于1,就跑到我们自定义的限流

    8.1.5 测试

    1秒钟点击1下,OK

    超过上述问题,疯狂点击,返回了自己定义的限流处理信息,限流发送

    8.1.6 额外问题

    此时关闭微服务8401看看

    Sentinel控制台,流控规则消失了?

    临时/持久?如何持久化?

    8.2 安装Url地址限流+后续处理

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

    8.2.1 业务类

    package com.atguigu.springcloud.alibaba.controller; import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.atguigu.springcloud.entities.CommonResult; import com.atguigu.springcloud.entities.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 服务不可用"); } @GetMapping("/rateLimit/byUrl") @SentinelResource(value = "byUrl") public CommonResult byUrl() { return new CommonResult(200,"按url限流测试OK",new Payment(2020L,"serial002")); } }

    8.2.2 访问一次

    http://localhost:8401/rateLimit/byUrl

    8.2.3 Sentinel控制台配置

    8.2.4 测试

    疯狂点击http://localhost:8401/rateLimit/byUrl

    结果

    8.3 上面兜底方法面临的问题

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

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

    8.4.1 创建customerBlockHandler类用于自定义限流处理逻辑

    package com.atguigu.springcloud.alibaba.myhandler; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.atguigu.springcloud.entities.CommonResult; import com.atguigu.springcloud.entities.Payment; /** * @author by Jak * @date 2020/7/3 */ public class CustomerBlockHandler { public static CommonResult handleException(BlockException exception) { return new CommonResult(4444, "按客户自定义,global,handlerException----1"); } public static CommonResult handleException2(BlockException exception) { return new CommonResult(4444, "按客户自定义,global,handlerException----2"); } }

    8.4.2 RateLimitController

    package com.atguigu.springcloud.alibaba.controller; import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.atguigu.springcloud.alibaba.myhandler.CustomerBlockHandler; import com.atguigu.springcloud.entities.CommonResult; import com.atguigu.springcloud.entities.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 服务不可用"); } @GetMapping("/rateLimit/byUrl") @SentinelResource(value = "byUrl") public CommonResult byUrl() { return new CommonResult(200,"按url限流测试OK",new Payment(2020L,"serial002")); } @GetMapping("rateLimit/customerBlockHandler") @SentinelResource(value = "customerBlockHandler", blockHandlerClass = CustomerBlockHandler.class, blockHandler = "handlerException2") public CommonResult customerBlockHandler() { return new CommonResult(200, "按客户自定义", new Payment(2020L, "serial003")); } }

    8.4.3 启动微服务后先调用一次

    http://localhost:8401/rateLimit/customerBlockHandler

    handlerException2兜底

    进一步说明

    8.5 更多注解属性说明

    8.5.1 Define Resource

    8.5.2 Sentinel主要有三个核心API

    SphU定义资源Tracer定义统计ContextUtil定义上下文

    九、服务熔断功能

    sentinel整合ribbon+openFeign+fallback

    9.1 Ribbon系列

    9.1.1 启动nacos和Sentinel

    9.1.2提供9003/9004

    新建cloudalibaba-provider-payment9003/9004

    POM

    <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>cloud2020</artifactId> <groupId>com.atguigu.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloudalibaba-provider-payment9003</artifactId> <dependencies> <!--SpringCloud ailibaba nacos --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity --> <groupId>com.atguigu.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </dependency> <!-- SpringBoot整合Web组件 --> <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> <!--日常通用jar包配置--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> </project>

    YML

    server: port: 9003 spring: application: name: nacos-payment-provider cloud: nacos: discovery: server-addr: localhost:8848 #配置Nacos地址 management: endpoints: web: exposure: include: '*'

    主启动

    package com.atguigu.springcloud.alibaba; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; /** * @author by Jak * @date 2020/7/3 */ @SpringBootApplication @EnableDiscoveryClient public class PaymentMain9003 { public static void main(String[] args) { SpringApplication.run(PaymentMain9003.class, args); } }

    业务类

    package com.atguigu.springcloud.alibaba.controller; import com.atguigu.springcloud.entities.CommonResult; import com.atguigu.springcloud.entities.Payment; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; import java.util.HashMap; @RestController public class PaymentController { @Value("${server.port}") private String serverPort; public static HashMap<Long, Payment> hashMap = new HashMap<>(); static{ hashMap.put(1L,new Payment(1L,"28a8c1e3bc2742d8848569891fb42181")); hashMap.put(2L,new Payment(2L,"bba8c1e3bc2742d8848569891ac32182")); hashMap.put(3L,new Payment(3L,"6ua8c1e3bc2742d8848569891xt92183")); } @GetMapping(value = "/paymentSQL/{id}") public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id){ Payment payment = hashMap.get(id); CommonResult<Payment> result = new CommonResult(200,"from mysql,serverPort: "+serverPort,payment); return result; } }

    测试地址

    9004同上

    http://localhost:9003/paymentSQL/1

    http://localhost:9004/paymentSQL/1

    9.1.3 消费者84

    1.新建cloudalibaba-consumer-nacos-order84

    2.POM

    <?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>cloud2020</artifactId> <groupId>com.atguigu.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloudalibaba-consumer-nacos-order84</artifactId> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId> </dependency> <dependency> <groupId>com.atguigu.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>${project.version}</version> </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> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> </project>

    3.YML

    server: port: 84 spring: application: name: nacos-order-consumer cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: # 配置Sentinel dashboard地址 dashboard: localhost:8080 # 默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口 port: 8719 #消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者) service-url: nacos-user-service: http://nacos-payment-provider

    4.主启动

    package com.atguigu.springcloud.alibaba; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; @EnableDiscoveryClient @SpringBootApplication @EnableFeignClients public class OrderNacosMain84 { public static void main(String[] args) { SpringApplication.run(OrderNacosMain84.class, args); } }

    5.业务类

    package com.atguigu.springcloud.alibaba.config; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.client.RestTemplate; /** * @author by Jak * @date 2020/7/3 */ @Configuration public class ApplicationContextConfig { @Bean @LoadBalanced public RestTemplate getRestTemplate() { return new RestTemplate(); } }

     

    package com.atguigu.springcloud.alibaba.controller; import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.atguigu.springcloud.entities.CommonResult; import com.atguigu.springcloud.entities.Payment; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; @RestController @Slf4j public class CircleBreakerController { public 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只负责业务异常 //@SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只负责sentinel控制台配置违规 @SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler", exceptionsToIgnore = {IllegalArgumentException.class}) public CommonResult<Payment> fallback(@PathVariable Long id) { CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/"+id, CommonResult.class,id); if (id == 4) { throw new IllegalArgumentException ("IllegalArgumentException,非法参数异常...."); }else if (result.getData() == null) { throw new NullPointerException ("NullPointerException,该ID没有对应记录,空指针异常"); } return result; } //fallback public CommonResult handlerFallback(@PathVariable Long id,Throwable e) { Payment payment = new Payment(id,"null"); return new CommonResult<>(444,"兜底异常handlerFallback,exception内容 "+e.getMessage(),payment); } //blockHandler public CommonResult blockHandler(@PathVariable Long id,BlockException blockException) { Payment payment = new Payment(id,"null"); return new CommonResult<>(445,"blockHandler-sentinel限流,无此流水: blockException "+blockException.getMessage(),payment); } }

    修改后请重启微服务

    热部署对java代码级生效及时对@SentinelResource注解内属性,有时效果不好

    目的

    fallback管运行异常blockHandler管配置违规

    测试地址

    依次开启9003、9004、84

    访问http://localhost:84/consumer/fallback/1

    先出现9003,再出现9004,有轮询负载均衡算法

    没有任何配置

    即没有熔断,也没有降级

    fallback/4,给客户error页面,不友好

    只配置fallback

    访问http://localhost:84/consumer/fallback/4,不再有错误页面,返回友好提示

    只配置blockHandler

     访问一次直接java报错

    快速点,满足Sentinel规则

    fallback和blockHandler都配置

    测试

    一秒钟1个没问题

    超过1个,即使正确的也会走到限流

    一秒一个,有错,java异常找fallback

    超过1个,找blockHandler

    忽略属性

    IllegalArgumentException异常忽略,不走兜底。NullPointerException没有配置,走兜底

    9.2 Feign系列

    9.2.1 修改84模块

    9.2.2 POM

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

    9.2.3 YML

    server: port: 84 spring: application: name: nacos-order-consumer cloud: nacos: discovery: server-addr: localhost:8848 sentinel: transport: # 配置Sentinel dashboard地址 dashboard: localhost:8080 # 默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口 port: 8719 #消费者将要去访问的微服务名称(注册成功进nacos的微服务提供者) service-url: nacos-user-service: http://nacos-payment-provider #开启sentinel对feign的支持 feign: sentinel: enabled: true

    9.2.4 主启动

    添加@EnableFeignClients启动Feign的功能

    package com.atguigu.springcloud.alibaba; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.openfeign.EnableFeignClients; @EnableDiscoveryClient @SpringBootApplication @EnableFeignClients public class OrderNacosMain84 { public static void main(String[] args) { SpringApplication.run(OrderNacosMain84.class, args); } }

    9.2.5 业务类

    带@FeignClient注解的业务接口

    package com.atguigu.springcloud.alibaba.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; /** * @author by Jak * @date 2020/7/3 */ @FeignClient(value = "nacos-payment-provider", fallback = PaymentFallbackService.class) public interface PaymentService { @GetMapping(value = "/paymentSQL/{id}") public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id); }

    Controller

    package com.atguigu.springcloud.alibaba.controller; import com.alibaba.csp.sentinel.annotation.SentinelResource; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.atguigu.springcloud.alibaba.service.PaymentService; import com.atguigu.springcloud.entities.CommonResult; import com.atguigu.springcloud.entities.Payment; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import javax.annotation.Resource; @RestController @Slf4j public class CircleBreakerController { public 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只负责业务异常 // @SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只负责sentinel控制台配置违规 // @SentinelResource(value = "fallback",fallback = "handlerFallback", blockHandler = "blockHandler") //两个都配,则被限流降级而抛出BlockException时,只会进入blockHandler @SentinelResource(value = "fallback", fallback = "handlerFallback", blockHandler = "blockHandler", exceptionsToIgnore = {IllegalArgumentException.class}) public CommonResult<Payment> fallback(@PathVariable Long id) { CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id); if (id == 4) { throw new IllegalArgumentException("IllegalArgumentException,非法参数异常...."); } else if (result.getData() == null) { throw new NullPointerException("NullPointerException,该ID没有对应记录,空指针异常"); } return result; } //blockHandler public CommonResult blockHandler(@PathVariable Long id, BlockException blockException) { Payment payment = new Payment(id, "null"); return new CommonResult<>(445, "blockHandler-sentinel限流,无此流水: blockException " + blockException.getMessage(), payment); } //fallback public CommonResult handlerFallback(@PathVariable Long id, Throwable e) { Payment payment = new Payment(id, "null"); return new CommonResult<>(444, "兜底异常handlerFallback,exception内容 " + e.getMessage(), payment); } // OpenFeign @Resource private PaymentService paymentService; @GetMapping(value = "/consumer/paymentSQL/{id}") public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id) { return paymentService.paymentSQL(id); } }

    9.2.6 测试

    测试84调用9003,此时故意关闭9003微服务提供者,看84消费侧自动降级,不会被耗死

    9.3 熔断框架比较

    十、规则持久化

    10.1 是什么

    一旦我们重启应用,sentinel规则将消失,生产环境需要将配置规则进行持久化。

    10.1.1 开启8401

    10.1.2 配置流控

    此时重启8401,刚才的配置已经没有了

    10.2 怎么玩

    将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效。

    10.3 步骤

    10.3.1 修改cloudalibaba-sentinel-service8401

    10.3.2 POM

    <dependency> <groupId>com.alibaba.csp</groupId> <artifactId>sentinel-datasource-nacos</artifactId> </dependency>

    10.3.3 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 port: 8719 #默认8719,假如被占用了会自动从8719开始依次+1扫描。直至找到未被占用的端口 datasource: ds1: nacos: server-addr: localhost:8848 dataId: cloudalibaba-sentinel-service groupId: DEAFAULT_GROUP data-type: json rule-type: flow management: endpoints: web: exposure: include: '*' spring: cloud: sentinel: datasource: ds1: nacos: server-addr:localhost:8848 dataid:${spring.application.name} groupid:DEFAULT_GROUP data-type:json rule-type:flow

    10.3.4 添加Nacos业务规则配置

     

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

    10.3.5 启动8401后刷新sentinel发现业务规则有了

    10.3.6 快速访问测试接口

    http://localhost:8401/rateLimit/byUrl

    10.3.7 停止8401再看sentinel

    10.3.8 重新启动8401再看sentinel

    乍一看还是没有,稍等一会儿

    多次调用,http://localhost:8401/rateLimit/byUrl

    重新配置出现了,持久化验证通过

    Processed: 0.020, SQL: 9