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基本使用
兜底防范分为系统默认和客户自定义:
- sentinel系统默认的提示:Blocked by Sentinel (flow limiting)。
- 类似于@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) 业务类
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:
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);
}
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);
}
(5) 测试
熔断框架比较
5.持久化规则
前面我们微服务新增的限流规则后,微服务关闭后就会丢失,当时配置都限流规则都是临时的。
案例——修改8401已完成持久化设置
(1) pom.
导入持久化所需依赖
<!--SpringCloud ailibaba sentinel-datasource-nacos 持久化-->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
(2) 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后流控规则又出现了。
查看数据库,发现规则持久化到数据库中了。