官网:https://github.com/alibaba/Sentinel
中文文档:https://sentinelguard.io/zh-cn/docs/introduction.html
类似Hystrix,以流量为切入点,从流量路由、流量控制、流量整形、熔断降级、系统自适应过载保护、热点流量防护等多个维度来帮助开发者保障微服务的稳定性
Hystrix与Sentinel区别
下载与安装
https://github.com/alibaba/Sentinel/releases
下载的是一个jar包
运行前提:8080端口不被占用,有java8环境
启动命令:java -jar sentinel-dashboard-1.7.0.jar
启动完成后访问http://localhost:8080/#/login,账号密码均为sentinel
Sentinel特性
Sentinel 分为两个部分:
先启动nacos
新建cloudalibaba-sentinel-service8401模块
pom文件
<artifactId>cloudalibaba-sentinel-service8401</artifactId>
<dependencies>
<dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<groupId>com.mzr.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--SpringCloud ailibaba sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!--openfeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- SpringBoot整合Web组件+actuator -->
<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>
</dependencies>
yml
server:
port: 8401
spring:
application:
name: cloudalibaba-sentinel-service
cloud:
nacos:
discovery:
server-addr: 192.168.254.130:80 #Nacos服务注册中心地址,使用集群地址(可以看我上一章关于nacos笔记)
sentinel:
transport:
dashboard: localhost:8080 #配置Sentinel dashboard地址
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719
management:
endpoints:
web:
exposure:
include: '*'
主启动类
@EnableDiscoveryClient
@SpringBootApplication
public class MainApp8401
{
public static void main(String[] args) {
SpringApplication.run(MainApp8401.class, args);
}
}
controller
@RestController
@Slf4j
public class FlowLimitController
{
@GetMapping("/testA")
public String testA()
{
return "------testA";
}
@GetMapping("/testB")
public String testB()
{
log.info(Thread.currentThread().getName()+"\t"+"...testB");
return "------testB";
}
}
启动微服务
访问http://localhost:8080/ 我们发现sentinel里空空如也,这是为什么呢?
因为sentinel使用的是懒加载机制,我们只需执行一次访问http://localhost:8401/testA即可
此时,sentinel正在监控微服务8401
资源名:唯一名称,默认请求路径
针对来源:Sentinel可以针对调用者进行限流,填写微服务名,默认default(不区分来源)
阈值类型单机阈值:
QPS(每秒钟的请求数量):当调用该api的QPS达到阈值的时候,进行限流。
线程数:当调用该api的线程数达到阈值的时候,进行限流
是否集群:不需要集群
流控模式:
直接:api达到限流条件时,直接限流
关联:当关联的资源达到阈值时,就限流自己
链路:只记录指定链路上的流量(指定资源从入口资源进来的流量,如果达到阈值就进行限流)【api级别的针对来源】
流控效果:
快速失败:直接失败,抛异常
Warm up:根据codeFactor (冷加载因子,默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值
排队等待:匀速排队,让请求以匀速的速度通过,阈值类型必须设置为QPS,否则无效
演示:表示/请求路径testA 1秒钟内查询1次就是OK,若超过次数1,就直接快速失败,报默认错误
当我们快速连点时,就会报错
线程数与QPS大致相同,设置为1时表示在一个线程正在处理时,有其他线程进来会返回Blocked by Sentienl(flow limiting),之后全部使用QPS演示
直接调用默认报错信息,技术方面OK,but是否应该有我们自己的后续处理(我们自定义报错信息)?
当与A关联的资源B达到阀值后,就限流A自己(支付微服务达到阈值,订单模块限流,避免连坐)
演示:设置/请求路径testB QPS为1,模拟并发密集访问/testB一定会导致B达到阈值,观察A的情况
postman模拟并发密集访问/testB(20个线程间隔0.3s访问一次)
在模拟期间我们访问 http://localhost:8401/testA 会发现A挂了,这就是关联模式
只针对从指定链路访问到本资源的请求做统计,判断是否超过阈值。
演示:有两条链路 /testA -> /common、/testB -> /common
如果从 /testA 进入到 /common的请求阈值超过了1次/s,则对/testA 进行限流
直接失败,抛出异常Blocked by Sentinel (flow limiting)
源码:com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController
秒杀系统在开启的瞬间,会有很多流量上来,很有可能把系统打死,预热方式就是把为了保护系统,可慢慢的把流量放进来,慢慢的把阀值增长到设置的阀值。
Warm up ( RuleConstant.CONTROL_BEHAVIOR_MARM_UP)方式,即预热/冷启动方式。当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮
。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮
。详细文档可以参考流量控制- Warm Up文档,具体的例子可以参见WarmUpFlowDemo。
通常冷启动的过程系统允许通过的QPS曲线如下图所示:
源码所在类:com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController
默认coldFactlor为3,即请求QPS 从threshold / 3开始,经预热时长逐渐升至设定的QPS阈值
演示:阀值为10+预热时长设置5秒。
系统初始化的阀值为10/3约等于3,即阀值刚开始为3,然后过了5秒后阀值才慢慢升高恢复到10
匀速排队(RuleConstant.CONTROL_BEHAVIOR_RATE_LINITER)方式会严格控制请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。详细文档可以参考流量控制-匀速器模式,具体的例子可以参见PaceFlowDemo。
该方式的作用如下图所示:
这种方式主要用于处理间隔性突发的流量,例如消息队列。
想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
匀速排队,让请求以均匀的速度通过,阀值类型必须设成QPS,否则无效。
演示:/testA每秒1次请求,超过的话就排队等待,等待的超时时间为20000毫秒。
当1s内持续进入5个请求即每秒请求量QPS>=5
,对应时刻的平均响应时间(秒级)均超过阈值 count
,(以ms为单位),那么在接下的时间窗口( DegradeRule中的timewindow,以s为单位)之内,对这个方法的调用都会自动地熔断(抛出DegradeException)。
注意Sentinel默认统计的RT上限是4900 ms,超出此阈值的都会算作4900ms,若需要变更此上限可以通过启动配置项
-Dcsp.sentinel.statistic.max.rt=xxx来配置。
当资源的每秒请求量QPS>=5
,并且每秒异常总数占通过量的比值超过阈值
( DegradeRule中的count)之后,资源进入降级状态,即在接下的时间窗口( DegradeRule 中的timeWindow,以s为单位)之内
,对这个方法的调用都会自动地返回。
异常比率的阈值范围是[0.0,1.0],代表0% -100%。
当资源近1分钟的异常数目超过阈值
之后会进行熔断。注意由于统计时间窗口是分钟级别
的,若timewindow小于60s,则结束熔断状态后仍可能再进入熔断状态。时间窗口一定要大于等于60秒。
Sentinel的断路器是没有半开状态的
半开的状态系统自动去检测是否请求有异常,没有异常就关闭断路器恢复使用,
有异常则继续打开断路器不可用。具体可以参考Hystrix
@GetMapping("/testD")
public String testD()
{
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
log.info("testD 测试RT");
return "------testD";
}
永远一秒钟打进来10个线程(大于5个了)调用testD,我们希望200毫秒(程序设计为1s)处理完本次任务,如果超过200毫秒还没处理完,在未来1秒钟的时间窗口内,断路器打开(保险丝跳闸)微服务不可用,保险丝跳闸断电了
后续我停止jmeter,没有这么大的访问量了,断路器关闭(保险丝恢复),微服务恢复OK
@GetMapping("/testD")
public String testD()
{
log.info("testD 异常比例");
int age = 10/0;
return "------testD";
}
启动测试,访问http://localhost:8401/testD
单独访问一次,必然来一次报错一次(int age = 10/0),调一次错一次
开启jmeter后,直接高并发发送请求,多次调用达到我们的配置条件(异常比例0.2)
断路器开启(保险丝跳闸),微服务不可用了,再次访问不再报错error而是服务降级了。
@GetMapping("/testE")
public String testE()
{
log.info("testE 异常数");
int age = 10/0;
return "------testE";
}
启动测试,访问http://localhost:8401/testE
第一次访问绝对报错(int age = 10/0),因为除数不能为零我们看到error窗口
但是达到5次报错后,进入熔断后降级
源码类:com.alibaba.csp.sentinel.slots.block.BlockException
何为热点?热点即经常访问的数据。
很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
兜底方法 分为系统默认和客户自定义两种
之前的case,限流出问题后,都是用sentinel系统默认的提示:Blocked bySentinel (flow limiting)
我们能不能自定义呢?
类似hystrix,某个方法出问题了,就找对应的兜底降级方法?
@SentinelResource可以帮我们实现,这里先引入使用以下,之后会详细讲解
这里声明一下:@SentinelResource与热点规则没有必然联系,这里使用@SentinelResource只是为了演示自定义返回限流提示
在8401模块controller添加方法
@GetMapping("/testHotKey")
//@SentinelResource的value值随意,唯一即可,一般为了编码统一规范与请求地址保持一致
//blockHandler = "deal_testHotKey" 如果违背了Sentinel配置规则,则调用deal_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";
}
//方法头基本与@SentinelResource配置方法一致,只是入参必须要加BlockException exception
public String deal_testHotKey (String p1, String p2, BlockException exception)
{
return "------deal_testHotKey,o(╥﹏╥)o"; //sentinel系统默认的提示:Blocked by Sentinel (flow limiting)
}
演示:方法testHotKey里面第一个参数只要QPS超过每秒1次,马上降级处理,并返回自定义结果
资源名:这里可以传@SentinelResource的value
参数索引:传入的热点参数的索引(从0开始)
启动测试
一秒访问一次 http://localhost:8401/testHotKey?p1=0 返回值正常,当我们快速访问两次会发现返回o(╥﹏╥)o,发现服务已经降级
此时我们去掉@SentinelResource中的blockHandler = “deal_testHotKey”,再次访问
异常打到了前台用户界面看到,不友好,所以建议一定要配置上blockHandler
上述案例演示了第一个参数p1,当QPS超过1秒1次点击后马上被限流
偶尔也会有特殊情况,比如我们希望参数p1值为5时,它的阈值可以达到200,值为1时阈值为依然为1
这时我们应该如何配置呢?这里需要用到高级配置,如下
整体配置含义:参数下标是第0个也就是p1时,当QPS超过1秒1次点击后马上被限流,当p1值为5时,阈值则变为200,其他值阈值都为1
启动测试
p1=1时,我们快速连续访问两次便会触发限流
当p1=5时,我们手动快速点击多次(不超过200)都正常返回,达到我们的预期
此时,我们突然手贱,在controller层添加了程序异常(int age = 10/0),如下
@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";
}
启动测试
发现异常打到了前台用户界面,怎么回事??
SentinelResource处理的是sentinel控制台配置的违规情况
,有blockHandler方法配置的兜底处理
RuntimeException是java运行时报出的,运行时异常RunTimeException,@SentinelResource不管
总结:sentinelResource主管配置出错,运行出错该走异常走异常
我们上边学习的限流都是方法级别的,只对某个方法有效,如果我们想对整个系统进行限流
便需要使用系统规则,从整体维度对应用入口流量进行控制
系统规则支持以下的模式:
系统规则使用的不多,颗粒度太大
使用入口QPS演示
当QPS超过1秒1次时,触发系统服务限流
启动测试
我们分别单点访问/testA、/testB方法是正常返回结果的
当我们快速连续访问/testA、/testB,AB均已限流
得出结论:我们配置的系统规则对所有方法都有效
改造8401微服务
新增RateLimitController
@SentinelResource包含blockHandler 属性
@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 服务不可用");
}
}
配置流控规则
启动测试,访问http://localhost:8401/byResource,一秒一次正常返回
快速多次访问,返回了自己定义的限流处理信息,限流发生
@SentinelResource不包含blockHandler 属性
如果我们此时去掉@SentinelResource(value = “byResource”,blockHandler = “handleException”)中的blockHandler
再次快速多次访问 http://localhost:8401/byResource,会发现出现了error页面 很不友好对吧!!这个我们在学习热点限流时已经接触过了
此时重新启动微服务8401看看
Sentinel控制台,流控规则消失了? ? ? ?
这个问题后续会讲解
继续使用该方法
@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 服务不可用");
}
}
配置流控规则
启动测试,访问一次 http://localhost:8401/byResource
快速多次访问,会发现返回默认信息而不是我们自定义的信息
区别
按url限流: 配置资源名为访问路径,使用url限流时触发流控规则会报
默认
错误。按资源限流: 如果@SentinelResource
指定了blockHandler 会执行自定义的方法 如上handleException()
没指定blockHandler 会将异常打到了前台用户界面看到,很不友好!!
实际开发中超过阈值时,我们要使用@SentinelResource注释一定要定义一些兜底的方法。
面临的问题
系统默认的,没有体现我们自己的业务要求。
依照现有条件,我们自定义的处理方法又和业务代码耦合在一块
,不直观。
每个业务方法都添加一个兜底的,代码膨胀
加剧。
全局统—的处理方法没有体现。
创建CustomerBlockHandler类用于自定义限流处理逻辑
创建自定义限流处理类com.mzr.springcloud.CustomerBlockHandler
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");
}
}
RateLimitController
@GetMapping("/rateLimit/customerBlockHandler")
@SentinelResource(value = "customerBlockHandler",
//限流时分门别类的选择调用哪个类中的哪个异常方法
//例如CustomerBlockHandler中的handlerException2方法
blockHandlerClass = CustomerBlockHandler.class,
blockHandler = "handlerException2")
public CommonResult customerBlockHandler() {
return new CommonResult(200, "按客戶自定义", new Payment(2020L, "serial003"));
}
配置流控规则
启动测试,访问一次 http://localhost:8401/rateLimit/customerBlockHandler
多次快速访问,可以看到返回的是我们自定义的方法
测试后我们自定义的出来了进一步说明
sentinel整合ribbon+openFeign+fallback
新建cloudalibaba-provider-payment9003/9004
9003/9004配置基本相同,记得修改端口号
pom
<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.mzr.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>
</dependencies>
yml
server:
port: 9003
spring:
application:
name: nacos-payment-provider
cloud:
nacos:
discovery:
server-addr: 192.168.254.138: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;
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;
}
}
新建消费者 cloudalibaba-consumer-nacos-order84
pom
<dependencies>
<!--SpringCloud openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--SpringCloud ailibaba nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!--SpringCloud ailibaba sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.mzr.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>
</dependencies>
yml
server:
port: 84
spring:
application:
name: nacos-order-consumer
cloud:
nacos:
discovery:
server-addr: 192.168.254.138: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
@EnableFeignClients
public class OrderNacosMain84
{
public static void main(String[] args) {
SpringApplication.run(OrderNacosMain84.class, args);
}
}
既然使用ribbon,肯定少不了我们的RestTemplate 配置
@Configuration
public class ApplicationContextConfig
{
@Bean
@LoadBalanced
public RestTemplate getRestTemplate()
{
return new RestTemplate();
}
}
controller
@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") //没有配置
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;
}
}
启动测试,访问http://localhost:84/consumer/fallback/1发现9004/9003交替出现,负载均衡访问
服务熔断无配置
看上诉代码得知上述代码中 @SentinelResource(value = “fallback”) 没有任何配置
此时我们访问http://localhost:84/consumer/fallback/4和http://localhost:84/consumer/fallback/5发现返回都是java程序运行异常的error页面,对于用户很不友好
服务熔断只配置fallback
修改注解配置为@SentinelResource(value = “fallback”,fallback = “handlerFallback”)并添加兜底方法
@RequestMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback",fallback = "handlerFallback") //fallback只负责业务异常
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);
}
重新启动,再次访问http://localhost:84/consumer/fallback/4、和http://localhost:84/consumer/fallback/5
可以发现当前返回已经相对友好些
服务熔断只配置blockHandler
修改注解配置为@SentinelResource(value = “fallback”,blockHandler = “blockHandler”)并添加sentinel配置
@RequestMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback",blockHandler = "blockHandler") //blockHandler只负责sentinel控制台配置违规
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);
}
此时访问http://localhost:84/consumer/fallback/4
访问第1次,此时还没有达到我们sentinel降级配置要求,由于程序异常并且没有fallback配置出现不友好的error页面
快速连续访问多次,此时达到sentinel降级配置触发降级
服务熔断同时配置fallback及blockHandler
修改注解配置为@SentinelResource(value = “fallback”,fallback = “handlerFallback”,blockHandler = “blockHandler”),并新增sentinel降级配置
@RequestMapping("/consumer/fallback/{id}")
@SentinelResource(value = "fallback",fallback = "handlerFallback",blockHandler = "blockHandler")
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);
}
新增sentinel降级配置
启动测试,访问一次http://localhost:84/consumer/fallback/4,此时并没有达到我们sentinel降级配置要求,但配置了fallback,所以程序异常会调用兜底方法返回错误信息
快速连续访问2次,此时达到我们sentinel降级配置要求,但同时程序异常会触发调用兜底方法,此时会返回什么呢??很明显返回的是限流信息
结论:若blockHandler和fallback都进行了配置,则被限流降级而抛出BlockException时只会进入blockHandler处理逻辑。
服务熔断配置exceptionsToIgnore
修改注解配置为@SentinelResource(value = “fallback”,fallback = “handlerFallback”,blockHandler = “blockHandler”,exceptionsToIgnore = {IllegalArgumentException.class})
其他配置与同时配置fallback及blockHandler案例
相同
重新启动,访问一次http://localhost:84/consumer/fallback/4
发现触发程序异常但没有调用我们配置的兜底方法,why???
修改84微服务,Feign组件一般是在消费侧,使用84消费者调用提供者9003
pom增加openFeign依赖
<!--SpringCloud openfeign -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
yml文件激活Sentinel对Feign的支持
# 激活Sentinel对Feign的支持
feign:
sentinel:
enabled: true
主启动类添加注解@EnableFeignClients
带@FeignClient注解的业务接口
@FeignClient(value = "nacos-payment-provider",fallback = PaymentFallbackService.class)
public interface PaymentService
{
@GetMapping(value = "/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id);
}
fallback = PaymentFallbackService.class
@Component
public class PaymentFallbackService implements PaymentService
{
@Override
public CommonResult<Payment> paymentSQL(Long id)
{
return new CommonResult<>(44444,"服务降级返回,---PaymentFallbackService",new Payment(id,"errorSerial"));
}
}
Controller
@RestController
@Slf4j
public class CircleBreakerController
{
//==================OpenFeign
@Resource
private PaymentService paymentService;
@GetMapping(value = "/consumer/paymentSQL/{id}")
public CommonResult<Payment> paymentSQL(@PathVariable("id") Long id)
{
return paymentService.paymentSQL(id);
}
}
启动测试84调用9003,访问 http://localhost:84/consumer/paymentSQL/1,服务当前正常运行
此时故意关闭9003微服务提供者,看84消费侧是否会自动降级调用兜底方法,还是会被自动耗死
答案是会自动降级调用兜底方法
注意注意!!
此案例只是演示了Sentinel对openFeign的支持,可以与Hystrix作一下对比看有什么区别,而关于sentinel本身的降级配置在此处并没有演示,大家可以自行配置尝试一下
还记得学习@SentinelResource详解时遇到的一个问题吗
一旦我们应用重启,Sentinel控制台配置的规则消失了? ? ? ?
生产环境需要将配置规则进行持久化
如何持久化
将限流配置规则持久化进Nacos保存,只要刷新8401某个rest地址, sentinel控制台的流控规则就能看到,只要Nacos里面的配置不删除,针对8401上sentinel上的流控规则持续有效
这里使用8401进行演示
RateLimitController新增方法byUrl
@GetMapping("/rateLimit/byUrl")
@SentinelResource(value = "byUrl")
public CommonResult byUrl() {
return new CommonResult(200, "按url限流测试OK", new Payment(2020L, "serial002"));
}
此时sentinel没有配置,我们正常访问 http://localhost:8401/rateLimit/byUrl
配置sentinel流控规则
此时快速连续访问http://localhost:8401/rateLimit/byUrl,会发现此时流控规则已经生效
此时我们将微服务重新启动,发现sentinel控制台配置信息已经没有了
如何持久化呢??
修改8401微服务
pom添加依赖
<!--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服务注册中心地址,使用集群地址(可以看我上一章关于nacos笔记)
server-addr: 192.168.254.138:8848
sentinel:
transport:
dashboard: localhost:8080 #配置Sentinel dashboard地址
#默认8719端口,假如被占用会自动从8719开始依次+1扫描,直至找到未被占用的端口
port: 8719
datasource:
ds1:
nacos:
server-addr: 192.168.254.138:8848
dataId: cloudalibaba-sentinel-service
groupId: DEFAULT_GROUP
data-type: json
rule-type: flow
添加Nacos业务规则配置
json串的含义
resource:资源名称
limitApp:来源应用
grade:阈值类型,0表示线程数,1表示QPS
count:单机阈值
strategy:流控模式,0表示直接,1表示关联,2表示链路
controlBehavior:流控效果,0表示快速失败,1表示Warm Up,2表示排队等待
clusterMode:是否集群
启动8401后刷新sentinel发现业务规则有了(没有就先访问http://localhost:8401/rateLimit/byUrl即可)
快速多次访问测试接口,发现流控规则已经生效
停止8401再看sentinel,发现sentinel控制台流控规则消失了
重新启动8401先访问http://localhost:8401/rateLimit/byUrl 再刷新sentinel页面
重新配置出现了,持久化验证通过~