SpringCloudAlibaba - 分布式流量防卫兵Sentinel
一. Sentinel: 分布式系统的流量防卫兵 - 阿里巴巴产品
具体介绍可以看官方文档:https://github.com/alibaba/Sentinel/wiki/介绍,下面我们说点官方没有的东西:
服务保护的基本概念:
【服务限流/熔断】
服务限流目的是为了更好的保护我们的服务,在高并发的情况下,如果客户端请求的数量达到一定极限(后台可以配置阈值),请求的数量超出了设置的阈值,开启自我的保护,直接调用我们的服务降级的方法,不会执行业务逻辑操作,直接走本地falback的方法,返回一个友好的提示。
【服务降级】
在高并发的情况下, 防止用户一直等待,采用限流/熔断方法,使用服务降级的方式返回一个友好的提示给客户端,不会执行业务逻辑请求,直接走本地的falback的方法。提示语:当前排队人数过多,稍后重试~
【服务雪崩】
默认的情况下,Tomcat或者是Jetty服务器只有一个线程池去处理客户端的请求,这样的话就是在高并发的情况下,如果客户端所有的请求都堆积到同一个服务接口上, 那么就会产生tomcat服务器所有的线程都在处理该接口,可能会导致其他的接口无法访问。
【服务隔离机制】
线程池隔离机制:每个服务接口都有自己独立的线程池,互不影响,缺点就是占用cpu资源非常大。
信号量隔离机制:最多只有一定的阈值线程数处理我们的请求,超过该阈值会拒绝请求。
Sentinel | Hystrix | |
---|---|---|
隔离策略 | 基于并发数 | 线程池隔离/信号量隔离 |
熔断降级策略 | 基于响应时间或失败比率 | 基于失败比率 |
实时指标实现 | 滑动窗口 | 滑动窗口(基于 RxJava) |
规则配置 | 支持多种数据源 | 支持多种数据源 |
扩展性 | 多个扩展点 | 插件的形式 |
基于注解的支持 | 即将发布 | 支持 |
调用链路信息 | 支持同步调用 | 不支持 |
限流 | 基于 QPS / 并发数,支持基于调用关系的限流 | 不支持 |
流量整形 | 支持慢启动、匀速器模式 | 不支持 |
系统负载保护 | 支持 | 不支持 |
实时监控 API | 各式各样 | 较为简单 |
控制台 | 开箱即用,可配置规则、查看秒级监控、机器发现等 | 不完善 |
常见框架的适配 | Servlet、Spring Cloud、Dubbo、gRPC 等 | Servlet、Spring Cloud Netflix |
二. SpringBoot整合Sentinel
1. 手动代码配置
@RestController
public class OrderService {
private static final String GETORDER_KEY = "getOrder";
// 手动配置管理Api限流接口
@RequestMapping("/initFlowQpsRule")
public String initFlowQpsRule() {
List<FlowRule> rules = new ArrayList<>();
FlowRule rule1 = new FlowRule();
rule1.setResource(GETORDER_KEY);
// QPS控制在2以内
rule1.setCount(1);
// QPS限流
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule1.setLimitApp("default");
rules.add(rule1);
FlowRuleManager.loadRules(rules);
return "....限流配置初始化成功..";
}
@RequestMapping("/getOrder")
public String getOrders() {
Entry entry = null;
try {
entry = SphU.entry(GETORDER_KEY);
// 执行我们服务需要保护的业务逻辑(省略)
return "getOrder接口";
} catch (Exception e) {
e.printStackTrace();
return "流量已达上限.";
} finally {
if (entry != null) {
entry.exit();
}
}
}
}
启动项目,首先执行initFlowQpsRule接口;由于我们配置的是QPS(QueriesPerSecond意思是“每秒查询率”,是一台服务器每秒能够相应的查询次数)为1,所以一秒内超过一次请求,则走降级,即会执行catch的返回结果。(下图演示效果为一秒内刷新两次):
那么问题来了,每次重启都需要执行initFlowQpsRule接口,太繁琐了,那么我们可以加入到启动加载类中:
@Component
@Slf4j
public class SentinelApplicationRunner implements ApplicationRunner {
private static final String GETORDER_KEY = "getOrder";
@Override
public void run(ApplicationArguments args) throws Exception {
List<FlowRule> rules = new ArrayList<FlowRule>();
FlowRule rule1 = new FlowRule();
rule1.setResource(GETORDER_KEY);
// QPS控制在2以内
rule1.setCount(1);
// QPS限流
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule1.setLimitApp("default");
rules.add(rule1);
FlowRuleManager.loadRules(rules);
log.info(">>>限流服务接口配置加载成功>>>");
}
}
2. 注解形式配置管理Api限流
该方式也必须先执行initFlowQpsRule或者加入启动配置类,只是接口中不用Entry和try catch了:
@RestController
public class OrderService {
@RequestMapping("/initFlowQpsRule")
public String initFlowQpsRule() {
List<FlowRule> rules = new ArrayList<FlowRule>();
FlowRule rule1 = new FlowRule();
rule1.setResource("getOrderAnnotation");
// QPS控制在2以内
rule1.setCount(1);
// QPS限流
rule1.setGrade(RuleConstant.FLOW_GRADE_QPS);
rule1.setLimitApp("default");
rules.add(rule1);
FlowRuleManager.loadRules(rules);
return "限流配置初始化成功..";
}
@SentinelResource(value = "getOrderAnnotation", blockHandler = "getOrderQpsException")
@RequestMapping("/getOrderAnnotation")
public String getOrderAnnotation() {
return "getOrderAnnotation接口";
}
/**
* 被限流后返回的提示
*/
public String getOrderQpsException(BlockException e) {
e.printStackTrace();
return "该接口已经被限流啦!";
}
}
该效果和方式1效果一样,一秒内连续请求两次getOrderAnnotation接口,则会走限流回调方法getOrderQpsException。
3. Sentinel控制台管理限流接口
① 控制台环境搭建
下载对应Sentinel-Dashboardhttps://github.com/alibaba/Sentinel/releases/tag/1.7.1 下载后执行命令:
java -Dserver.port=8718 -Dcsp.sentinel.dashboard.server=localhost:8718 -Dproject.name=sentinel-dashboard -Dcsp.sentinel.api.port=8719 -jar D:\MyTools\sentinel-dashboard-1.6.2.jar
启动后访问:http://localhost:8718,默认用户名和密码都为sentinel,登录成功页面如下:
② SpringBoot接入Sentinel控制台
spring:
application:
# 服务名称
name: service-order
cloud:
nacos:
discovery:
# nacos注册地址
server-addr: 127.0.0.1:8848
config:
# 配置中心连接地址
server-addr: 127.0.0.1:8848
# 分组
group: DEFAULT_GROUP
# 类型
file-extension: yaml
sentinel:
transport:
dashboard: 127.0.0.1:8718
eager: true
服务加入身sentinel配置后,重启可以看到sentinel控制台多出了一个应用:
新写一个测试接口:
@RestController
public class OrderService {
@SentinelResource(value = "getOrderByConsole", blockHandler = "getOrderQpsException")
@RequestMapping("/getOrderByConsole")
public String getOrderByConsole() {
return "getOrderByConsole接口";
}
public String getOrderQpsException(BlockException e) {
e.printStackTrace();
return "该接口已经被限流啦!";
}
}
控制台需要配置资源名称getOrderByConsole:点击流控规则 → 新增流控规则:
三. Sentinel持久化
默认的情况下Sentinel的规则是存放在内存中,如果Sentinel客户端重启后,Sentinel数据规则可能会丢失。
Sentinel持久化机制支持四种持久化的机制:(这里我们只讲整合Nacos)
1. 本地文件
2. 携程阿波罗
3. Nacos
4. Zookeeper
1. 首先,在Nacos配置中心创建流控规则:(注意resource不要加/)
resource:资源名,即限流规则的作用对象
limitApp:流控针对的调用来源,若为 default 则不区分调用来源
grade:限流阈值类型(QPS 或并发线程数);0代表根据并发数量来限流,1代表根据QPS来进行流量控制
count:限流阈值
strategy:调用关系限流策略
controlBehavior:流量控制效果(直接拒绝、Warm Up、匀速排队)
clusterMode:是否为集群模式
2. 修改订单服务sentinel配置如下:
引入依赖并新增测试接口:
<!--sentinel 整合nacos -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>1.5.2</version>
</dependency>
@SentinelResource(value = "getOrderSentinel", blockHandler = "getOrderSentinelBlockHandler")
@RequestMapping("/getOrderSentinel")
public String getOrderSentinel() {
return "getOrderSentinel";
}
public String getOrderSentinelBlockHandler(BlockException e) {
return "当前访问人数过多,请稍后重试!";
}
启动订单服务,此时,可以看到Sentinel控制台已经出现1中在Nacos配置的流控规则:
访问http://localhost:8090/getOrderSentinel,一秒连续两次请求,则也会走blockHandler方法。
【小结】:注意一定要删掉 SentinelApplicationRunner类,因为SpringCloudAlibaba默认Sentinel整合Nacos持久化已经在服务启动的时候,加载Nacos的配置进服务内存,所以应该注释掉启动加载类,否则会产生冲突。
四. Gateway整合Sentinel实现服务限流
上面我们都是在单个服务(订单服务)演示流控规则,下面我们说一下在微服务网关Gateway中如何整合Sentiel。
官方文档:https://github.com/alibaba/Sentinel/wiki/网关限流
1. 首先在网关服务中额外引入以下依赖:
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-spring-cloud-gateway-adapter</artifactId>
<version>1.6.0</version>
</dependency>
2. 编写固定配置类(直接拷贝如下代码即可,无需修改):
@Configuration
public class GatewayConfiguration {
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public GatewayConfiguration(ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer) {
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
// Register the block exception handler for Spring Cloud Gateway.
return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public GlobalFilter sentinelGatewayFilter() {
return new SentinelGatewayFilter();
}
}
3. 在网关配置启动加载类,配置限流规则:
@Slf4j
@Component
public class SentinelApplicationRunner implements ApplicationRunner {
@Override
public void run(ApplicationArguments args) throws Exception {
initGatewayRules();
}
// 配置限流规则
private void initGatewayRules() {
Set<GatewayFlowRule> rules = new HashSet<>();
rules.add(new GatewayFlowRule("order")
// 限流阈值
.setCount(1)
// 统计时间窗口,单位是秒,默认是 1 秒
.setIntervalSec(1)
);
GatewayRuleManager.loadRules(rules);
}
}
注意new GatewayFlowRule("order") 里面的order即为配置文件中routes中的-id,而不是path。
启动网关,端口为81,访问返回如下:
可见返回的提示不太直观,我们可以更改返回错误码:
① 新建Handler实现WebExceptionHandler
public class JsonSentinelGatewayBlockExceptionHandler implements WebExceptionHandler {
public JsonSentinelGatewayBlockExceptionHandler(List<ViewResolver> viewResolvers, ServerCodecConfigurer serverCodecConfigurer) {
}
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
ServerHttpResponse serverHttpResponse = exchange.getResponse();
serverHttpResponse.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
byte[] datas = "{\"code\":403,\"msg\":\"API接口被限流\"}".getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = serverHttpResponse.bufferFactory().wrap(datas);
return serverHttpResponse.writeWith(Mono.just(buffer));
}
}
② GatewayConfiguration注释默认的SentinelGatewayBlockExceptionHandler,把自定义的注入到Spring容器中:
// @Bean
// @Order(Ordered.HIGHEST_PRECEDENCE)
// public SentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
// // Register the block exception handler for Spring Cloud Gateway.
// return new SentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
// }
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public JsonSentinelGatewayBlockExceptionHandler sentinelGatewayBlockExceptionHandler() {
// Register the block exception handler for Spring Cloud Gateway.
return new JsonSentinelGatewayBlockExceptionHandler(viewResolvers, serverCodecConfigurer);
}
重启项目,一秒连续两次访问,返回结果如下:(getOrderSentinel有自己的降级方法,网关优先级更高,先被执行)
五. Sentinel熔断降级
官方文档:https://github.com/alibaba/Sentinel/wiki/熔断降级
服务降级的策略:
1. rt(平均响应时间)【注意是单位时间内,并不是累加】
如果在1s内访问五次,平均的响应时间超出了我们在平台设置的阈值的情况下,直接触发我们的熔断执行我们服降级的方法。
在规定的时间窗口内(单位为秒)一直执行我们的服务降级的方法,不能够执行我们的真实业务逻辑。
// 平均响应时间RT
@SentinelResource(value = "getOrderRt", fallback = "getOrderRtFallback")
@RequestMapping("/getOrderRt")
public String getOrderRt() {
try {
Thread.sleep(300);
} catch (Exception e) {
}
return "正常执行我们业务逻辑";
}
public String getOrderRtFallback() {
return "Rt - 服务降级";
}
一秒连续访问5次接口,则会走降级方法,因为平均响应时间肯定大于10,下次访问则走降级方法,3秒后解除。
注意:① 代码中Thread.sleep(300),如果直接return如上图IDEA代码,则平均响应时间肯定不会大于10,则一秒内访问10次甚至更多,也不会走降级方法。
② 一旦服务重启后,降级规则失效,需要重新配置,同理也可以用Nacos持久化,自行学习,这里不讨论了~
2. 异常比例
比如客户端每秒s内发出5个请求,5个请求全部错误,这说明错误率为百分百。
每秒内发出5个请求,如果请求的异常占比超过我们设置的阈值占比的情况下,就会出发我们熔断,执行我们的服务降级方法,在规定的时间窗口内(单位为秒)不能执行我们真实业务逻辑。
@SentinelResource(value = "getOrderException", fallback = "getOrderExceptionFallback")
@RequestMapping("/getOrderException")
public String getOrderException(int age) {
int j = 1 / age;
return "正常执行我们业务逻辑:j" + j;
}
public String getOrderExceptionFallback(int age) {
return "错误率太高,展示无法访问该接口";
}
每秒请求 >= 5次,异常比例为100%,则走降级,同样持续3秒。
注意:如果代码中手动catch,则出异常直接进catch返回了,不会走降级方法,永远不会走~
3. 异常数
当资源近 1 分钟的异常数目超过阈值之后会进行熔断。注意由于统计时间窗口是分钟级别的(单位为分钟),若 timeWindow 小于 60s,则结束熔断状态后仍可能再进入熔断状态。
该方式也用getOrderException测试(同一个资源名称可以有多个降级方式),会发现近一分钟连续出现5次异常,第6次访问则会走降级方法,注意时间窗口单位为分钟,所以解封时间为1分钟。注意该方式手动catch,则也不会走降级方法。
【总结】:个人认为,异常比例和异常数没多大用处,Rt平均响应时间还是比较重要的;顺便说一下fallback与blockHandler的区别:fallback是服务熔断或者业务逻辑出现异常执行的方法(1.6版本以上),blockHandler 限流出现错误执行的方法。
六. Sentinel热点参数限流
官方文档:https://github.com/alibaba/Sentinel/wiki/热点参数限流
何为热点?热点即经常访问的数据。很多时候我们希望统计某个热点数据中访问频次最高的 Top K 数据,并对其访问进行限制。比如:
- 商品 ID 为参数,统计一段时间内最常购买的商品 ID 并进行限制
- 用户 ID 为参数,针对一段时间内频繁访问的用户 ID 进行限制
热点参数限流会统计传入参数中的热点参数,并根据配置的限流阈值与模式,对包含热点参数的资源调用进行限流。热点参数限流可以看做是一种特殊的流量控制,仅对包含热点参数的资源调用生效。
Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。热点参数限流支持集群模式。
1. 基本使用:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel</artifactId>
<version>0.2.2.RELEASE</version>
</dependency>
@RestController
@Slf4j
public class SeckillService {
// 秒杀的限流规则
private static final String SEKILL_RULE = "seckill";
public SeckillService() {
// Spring容器初始化时把热点参数配置加载到jvm内存
initSeckillRule();
}
private void initSeckillRule() {
ParamFlowRule rule = new ParamFlowRule(SEKILL_RULE)
// 对我们秒杀接口第0个参数实现限流
.setParamIdx(0)
.setGrade(RuleConstant.FLOW_GRADE_QPS)
// 每秒QPS最多访问1次,如一秒2次即开始限流
.setCount(1);
ParamFlowRuleManager.loadRules(Collections.singletonList(rule));
log.info(">>>秒杀接口限流策略配置成功<<<");
}
// 秒杀接口
@RequestMapping("/seckill")
public String seckill(Long userId, Long orderId) {
try {
Entry entry = SphU.entry(SEKILL_RULE, EntryType.IN, 1, userId);
return "秒杀成功";
} catch (Exception e) {
return "当前该用户访问频率过多,请稍后重试!";
}
}
}
启动服务,访问接口(带userId),一秒第二次访问则会走catch:
注意:如果不传热点参数userId,则永远都不会走限流,如http://localhost:8090/seckill?order=123123
2. 控制台自定义:(推荐)
@RestController
@Slf4j
public class SeckillService {
// 秒杀的限流规则
private static final String SEKILL_RULE = "seckill";
@RequestMapping("/seckill")
@SentinelResource(value = SEKILL_RULE)
public String seckill(Long userId, Long orderId) {
return "秒杀成功";
}
}
@RestControllerAdvice
public class InterfaceExceptionHandler {
@ResponseBody
@ExceptionHandler(ParamFlowException.class)
public String businessInterfaceException(ParamFlowException e) {
return "您当前访问的频率过高,请稍后重试!";
}
}
重启服务,控制台配完热点规则,效果和上面一样。
注意:① 窗口时间只能设为1秒,可能是版本bug (sentinel-dashboard-1.6.2.jar)。
② 热点参数限流后,不会走fallback和blockHandler,只能全局捕获异常来自定义返回信息。
③ Sentinel支持热词参数Vip通道,即可以指定某些参数的规则,这个比如在某些秒杀活动中可以作弊使用,当然仅做了解,还是不推荐这样做的 ! 方式:编辑热点规则 - 高级选项,如下图,指定第一个参数为136,qps阈值为10,即一秒最多可以有10次请求,添加后则可以在浏览器无限刷新,手速快的除外 ~
【本篇总结】
Sentinel可以说是分布式流量防御的神器,在SpringCloudAlibba中可以完全替代SpringCloud的Hystrix,易于维护,可持久化,官方文档详细,最后再贴出一遍官网文档:https://github.com/alibaba/Sentinel/wiki/介绍,有不懂的欢迎留言 ~
本文地址:https://blog.csdn.net/AkiraNicky/article/details/104183130
上一篇: MySQL日期与时间函数的使用汇总
下一篇: (Angular)前后端完成分页处理