github官网: https://github.com/alibaba/Sentinel
中文文档:https://sentinelguard.io/zh-cn/docs/introduction.html
Sentinel是阿里中间件团队开源的,面向分布式服务架构的高可用流量防护组件,主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性。
Sentinel提供了两个服务组件:
资源是 Sentinel 的关键概念。它可以是 Java 应用程序中的任何内容,例如,由应用程序提供的服务,或由应用程序调用的其它应用提供的服务,甚至可以是一段代码。在接下来的文档中,我们都会用资源来描述代码块。
只要通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel 保护起来。大部分情况下,可以使用方法签名,URL,甚至服务名称作为资源名来标示资源。
围绕资源的实时状态设定的规则,可以包括流量控制规则、熔断降级规则以及系统保护规则。所有规则可以动态实时调整。
Sentinel 提供一个轻量级的开源控制台,它提供机器发现以及健康情况管理、监控(单机和集群),规则管理和推送的功能。
Sentinel 控制台包含如下功能:
release 页面 下载最新版本的控制台 jar 包
注意:启动 Sentinel 控制台需要 JDK 版本为 1.8 及以上版本。
使用如下命令启动控制台:
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
启动时加入 JVM 参数 -Dcsp.sentinel.dashboard.server=consoleIp:port 指定控制台地址和端口。若启动多个应用,则需要通过 -Dcsp.sentinel.api.port=xxxx 指定客户端监控 API 的端口(默认是 8719)。
从 Sentinel 1.6.0 起,Sentinel 控制台引入基本的登录功能,默认用户名和密码都是 sentinel。
<dependencies>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-nacosartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>com.atguigu.springcloudgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>${project.version}version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
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: '*'
package com.atguigu.springcloud;
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);
}
}
package com.atguigu.springcloud.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
@RestController
@Slf4j
public class FlowLimitController {
@GetMapping("/testA")
public String testA(){
return "------ testA";
}
@GetMapping("/testB")
public String testB(){
return "------ testB";
}
}
发现界面上没有数据,这是因为默认Sentinel是懒加载,需调用服务才可以出现
Sentinel流量控制(flow control)的原理是监控应用流量的QPS或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。
一条限流规则主要由下面几个因素组成,我们可以组合这些元素来实现不同的限流效果:
思考 :是否需要fallback兜底
当关联的资源达到阙值时,就限流自己
当与A关联的资源B达到阙值后,就限流A自己
比如在一个微服务中,两个接口都调用了同一个Service中的方法,并且该方法用SentinelResource(用于定义资源)注解标注了,然后对该注解标注的资源(方法)进行配置,则可以选择链路模式。
demo
package com.atguigu.springcloud.service;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import org.springframework.stereotype.Service;
@Service
public class FlowTestService {
// 定义限流资源
@SentinelResource("common")
public String common(){
return "common";
}
}
@Autowired
FlowTestService flowTestService;
@GetMapping("/testA")
public String testA(){
flowTestService.common();
return "------ testA";
}
@GetMapping("/testB")
public String testB(){
flowTestService.common();
return "------ testB222";
}
直接拒绝方式是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。
即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮
匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通.
该方式的作用如下图所示:
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
除了流量控制以外,对调用链路中不稳定的资源进行熔断降级也是保障高可用的重要措施之一。一个服务常常会调用别的模块,可能是另外的一个远程服务、数据库,或者第三方 API 等。例如,支付的时候,可能需要远程调用银联提供的 API;查询某个商品的价格,可能需要进行数据库查询。然而,这个被依赖服务的稳定性是不能保证的。如果依赖的服务出现了不稳定的情况,请求的响应时间变长,那么调用服务的方法的响应时间也会变长,线程会产生堆积,最终可能耗尽业务自身的线程池,服务本身也变得不可用。
现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用。因此我们需要对不稳定的弱依赖服务调用进行熔断降级,暂时切断不稳定调用,避免局部不稳定因素导致整体的雪崩。熔断降级作为保护自身的手段,通常在客户端(调用端)进行配置。
本文档针对 Sentinel 1.8.0 及以上版本。1.8.0 版本对熔断降级特性进行了全新的改进升级,请使用最新版本以更好地利用熔断降级的能力。
选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%。
当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
注意异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。
@SentinelResource 用于定义资源,并提供可选的异常处理和 fallback 配置项。 @SentinelResource 注解包含以下属性:
注:1.6.0 之前的版本 fallback 函数只针对降级异常(DegradeException)进行处理,不能针对业务异常进行处理。
特别地,若 blockHandler 和 fallback 都进行了配置,则被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。若未配置 blockHandler、fallback 和 defaultFallback,则被限流降级时会将 BlockException 直接抛出。
Controller
package com.atguigu.springcloud.controller;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
public class TestController {
//blockHandler 使用sentinel进行不同规则控制时的默认处理方案
// fallback:自定义业务出错时默认处理方案
// defaultFallback 指定一个业务错误时默认方案
@GetMapping("/test")
@SentinelResource(value = "testSentinel",blockHandler = "blockHandler",fallback = "fallCustomer",defaultFallback = "defaultFall") //作用:代表这是一个sentinel资源
public String demo(Integer id){
log.info("test ok...");
if(id<0){
throw new RuntimeException("id无效");
}
return "test ok !!!";
}
@GetMapping("/test2")
public String test(){
log.info("test2 ok...");
return "test2 ok !!!";
}
//
public String blockHandler(Integer id, BlockException e){
if(e instanceof FlowException){
return "当前请求过于火爆,您已被流控!!";
}
if(e instanceof DegradeException){
return "当前请求过于火爆,您已被降级!!";
}
if(e instanceof ParamFlowException){
return "当前请求过于火爆,您已被热点参数限流!!";
}
return "服务器快爆了,请稍后再试!!!";
}
//
public String defaultFall(){
return "默认处理:服务器出错了!!!";
}
public String fallCustomer(Integer id){
return "自定义服务器出错啦!!!";
}
}
上面的fallback属性和defaultFallback是我们指定业务代码中抛出异常的解决办法,优先执行fallback属性。
访问 http://localhost:8401/test?id=5
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。
正常情况下当每秒处理的请求超多10次,限流,若是热点数据当值为 11或者10的时候限流阈值分别为2、5
非热点数据访问
http://localhost:8401/test?id=5(快速点击几次也可以正常访问)
热点数据访问
http://localhost:8401/test?id=11(快速点击几次)
发现当我们频繁访问热点数据的时候已经被限流,并执行了blockHandler方法
系统保护规则是从应用级别的入口流量进行控制,从单台机器的 load、CPU 使用率、平均 RT、入口 QPS 和并发线程数等几个维度监控应用指标,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
系统保护规则是应用整体维度的,而不是资源维度的,并且仅对入口流量生效。入口流量指的是进入应用的流量(EntryType.IN),比如 Web 服务或 Dubbo 服务端接收的请求,都属于入口流量。
系统规则支持以下的模式:
新建cloud-alibaba-provider-payment9003
pom 文件
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>com.atguigu.springcloudgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>${project.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
application.yml 文件
server:
port: 9003
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址
management:
endpoints:
web:
exposure:
include: '*'
主启动类
@SpringBootApplication
@EnableDiscoveryClient
public class PaymentMain9003 {
public static void main(String[] args) {
SpringApplication.run(PaymentMain9003.class, args);
}
}
controller
@RestController
public class PaymentController {
@Value("${server.port}")
private String serverPort;
private static HashMap<Long, Payment> paymentHashMap = new HashMap<>();
static {
paymentHashMap.put(1L, new Payment(1L, "001"));
paymentHashMap.put(2L, new Payment(1L, "002"));
paymentHashMap.put(3L, new Payment(1L, "003"));
}
@GetMapping("/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable Long id) {
Payment payment = paymentHashMap.get(id);
CommonResult<Payment> result = new CommonResult<>(200, "from mysql ,serverport: " + serverPort, payment);
return result;
}
}
9004 跟9003配置一样,只是端口号不同
新建cloud-alibaba-consumer-nacos-order84
pom文件
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-actuatorartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
<dependency>
<groupId>com.atguigu.springcloudgroupId>
<artifactId>cloud-api-commonsartifactId>
<version>${project.version}version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-devtoolsartifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
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
主启动类
@EnableDiscoveryClient
@SpringBootApplication
public class OrderNacosMain84
{
public static void main(String[] args) {
SpringApplication.run(OrderNacosMain84.class, args);
}
}
业务类
配置类
@Configuration
public class ApplicationContextConfig {
@LoadBalanced
@Bean
public RestTemplate getRestTemplate(){
return new RestTemplate();
}
}
CircleBreakerController
package com.ayguigu.springcloud.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 com.ayguigu.springcloud.service.PaymentService;
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;
import javax.validation.constraints.Null;
@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 = {IllegalAccessError.class})
//业务异常 sentiner违规 忽略属性
public CommonResult<Payment> fallBack(@PathVariable("id") Long id) {
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);
if (id == 4) {
throw new IllegalAccessError("IllegalAccessError,非法参数异常");
} else if (result.getData() == null) {
throw new NullPointerException("NullPointerException ,该ID没有对应记录,空指针异常");
}
return result;
}
//只配置 fallback
public CommonResult<Payment> handlerFallback(@PathVariable("id") Long id, Throwable e) {
Payment payment = new Payment(id, null);
return new CommonResult<>(444, "兜底异常,handlerFallback,exception 内容 " + e.getMessage(), payment);
}
public CommonResult<Payment> blockHandler(@PathVariable("id") Long id, BlockException e) {
Payment payment = new Payment(id, null);
return new CommonResult<>(444, "兜底异常,blockHandler,exception 内容 " + e.getMessage(), payment);
}
}
测试 fallback 只负责业务异常 、 blockHandler 值负责sentinel 控制台配置违规
启动 9003、9004 服务提供者
访问 http://localhost:9003/paymentSQL/1
访问 http://localhost:9004/paymentSQL/1
只配置fallback
84controller
package com.ayguigu.springcloud.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 com.ayguigu.springcloud.service.PaymentService;
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;
/**
* @Author cjz
* @Date 2022/3/24 21:14
*/
@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", fallback = "handlerFallback") //fallback 只负责业务异常
public CommonResult<Payment> fallBack(@PathVariable("id") Long id) {
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);
if (id == 4) {
throw new IllegalAccessError("IllegalAccessError,非法参数异常");
} else if (result.getData() == null) {
throw new NullPointerException("NullPointerException ,该ID没有对应记录,空指针异常");
}
return result;
}
//只配置 fallback
public CommonResult<Payment> handlerFallback(@PathVariable("id") Long id, Throwable e) {
Payment payment = new Payment(id, null);
return new CommonResult<>(444, "兜底异常,handlerFallback,exception 内容 " + e.getMessage(), payment);
}
public CommonResult<Payment> blockHandler(@PathVariable("id") Long id, BlockException e) {
Payment payment = new Payment(id, null);
return new CommonResult<>(444, "兜底异常,blockHandler,exception 内容 " + e.getMessage(), payment);
}
@Resource
private PaymentService paymentService;
@GetMapping("/consumer/paymentSQL/{id}")
@SentinelResource(value = "feignSentinel")
public CommonResult<Payment> paymentSQL(@PathVariable Long id) {
return paymentService.paymentSQL(id);
}
}
http://localhost:84/consumer/fallback/1
http://localhost:84/consumer/fallback/4
此时就执行指定的fallback,对调用者比较友好。
只配置blockHandler
84 controller
package com.ayguigu.springcloud.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 com.ayguigu.springcloud.service.PaymentService;
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;
/**
* @Author cjz
* @Date 2022/3/24 21:14
*/
@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", blockHandler = "blockHandler") //blockHandler 值负责
public CommonResult<Payment> fallBack(@PathVariable("id") Long id) {
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);
if (id == 4) {
throw new IllegalAccessError("IllegalAccessError,非法参数异常");
} else if (result.getData() == null) {
throw new NullPointerException("NullPointerException ,该ID没有对应记录,空指针异常");
}
return result;
}
//只配置 fallback
public CommonResult<Payment> handlerFallback(@PathVariable("id") Long id, Throwable e) {
Payment payment = new Payment(id, null);
return new CommonResult<>(444, "兜底异常,handlerFallback,exception 内容 " + e.getMessage(), payment);
}
public CommonResult<Payment> blockHandler(@PathVariable("id") Long id, BlockException e) {
Payment payment = new Payment(id, null);
return new CommonResult<>(444, "兜底异常,blockHandler,exception 内容 " + e.getMessage(), payment);
}
@Resource
private PaymentService paymentService;
@GetMapping("/consumer/paymentSQL/{id}")
@SentinelResource(value = "feignSentinel")
public CommonResult<Payment> paymentSQL(@PathVariable Long id) {
return paymentService.paymentSQL(id);
}
}
配置sentinel
频繁访问会进入到blockHandler兜底方法
fallback和blockHandler都配置
84 controlelr
package com.ayguigu.springcloud.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 com.ayguigu.springcloud.service.PaymentService;
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;
/**
* @Author cjz
* @Date 2022/3/24 21:14
*/
@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", fallback = "handlerFallback", blockHandler = "blockHandler")
//业务异常 sentiner违规 忽略属性
public CommonResult<Payment> fallBack(@PathVariable("id") Long id) {
CommonResult<Payment> result = restTemplate.getForObject(SERVICE_URL + "/paymentSQL/" + id, CommonResult.class, id);
if (id == 4) {
throw new IllegalAccessError("IllegalAccessError,非法参数异常");
} else if (result.getData() == null) {
throw new NullPointerException("NullPointerException ,该ID没有对应记录,空指针异常");
}
return result;
}
//只配置 fallback
public CommonResult<Payment> handlerFallback(@PathVariable("id") Long id, Throwable e) {
Payment payment = new Payment(id, null);
return new CommonResult<>(444, "兜底异常,handlerFallback,exception 内容 " + e.getMessage(), payment);
}
public CommonResult<Payment> blockHandler(@PathVariable("id") Long id, BlockException e) {
Payment payment = new Payment(id, null);
return new CommonResult<>(444, "兜底异常,blockHandler,exception 内容 " + e.getMessage(), payment);
}
@Resource
private PaymentService paymentService;
@GetMapping("/consumer/paymentSQL/{id}")
@SentinelResource(value = "feignSentinel")
public CommonResult<Payment> paymentSQL(@PathVariable Long id) {
return paymentService.paymentSQL(id);
}
}
配置sentinel
频繁访问 http://localhost:84/consumer/fallback/2
结论:若 blockHandler 和 fallback 都进行了配置,被限流降级而抛出 BlockException 时只会进入 blockHandler 处理逻辑。
84 controller
我们给@SentinelResource注解添加配置exceptionsToIgnore = {IllegalArgumentException.class}
此时如果我们传的参数id=4,则会忽略IllegalArgumentException异常。
程序异常达到前台了,对用户不友好
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-openfeignartifactId>
dependency>
在yml 文件增加以下配置
# 激活Sentinel 对feign 的支持
feign:
sentinel:
enabled: true
PaymentService类
package com.ayguigu.springcloud.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;
@FeignClient(value = "nacos-payment-provider", fallback = PaymentFallbackService.class)
public interface PaymentService {
@GetMapping("/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}
PaymentFallbackService类
package com.ayguigu.springcloud.service;
import com.atguigu.springcloud.entities.CommonResult;
import com.atguigu.springcloud.entities.Payment;
import org.springframework.stereotype.Component;
@Component
public class PaymentFallbackService implements PaymentService {
@Override
public CommonResult<Payment> paymentSQL(Long id) {
return new CommonResult<>(444,"服务降级返回,---PaymentFallbackService",new Payment(id,"errorService"));
}
}
Controller:
@Resource
private PaymentService paymentService;
@GetMapping("/consumer/paymentSQL/{id}")
@SentinelResource(value = "feignSentinel")
public CommonResult<Payment> paymentSQL(@PathVariable Long id) {
return paymentService.paymentSQL(id);
}
主启动类
添加@EnableFeignClients启动Feign的功能
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class OrderMain84 {
public static void main(String[] args) {
SpringApplication.run(OrderMain84.class, args);
}
}
测试
一旦我们重启应用,sentinel规则将消失,生产环境需要将配置规则进行持久化
我们现在将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址,sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效。
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-nacosartifactId>
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
#添加nacos数据源的配置,将sentinel配置持久化进nacos里面
datasource:
ds1:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
management:
endpoints:
web:
exposure:
include: '*'
[
{
"resource": "/rateLimit/byUrl",
"limitApp": "default",
"grade": 1,
"count": 1,
"strategy": 0,
"controlBehavior": 0,
"clusterMode": false
}
]
@RestController
public class RateLimitController {
@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl")
public CommonResult<Payment> byUrl() {
return new CommonResult<>(200, "按URL限流测试通过", new Payment(2022L, "sencinel001"));
}
访问 http://localhost:8401/rateLimit/byUrl
配置重新出现,持久化成功