服务雪崩效应:因服务提供者的不可用,并将不可用逐渐放大的过程,就叫服务雪崩效应。
服务的可用性场景:
在一个高度服务化的系统中,我们实现的一个业务逻辑通常会依赖多个服务,如图所示
如果程序中的某一个服务不可用,就有可能出现线程池里面所有线程都因等待响应而被阻塞,从而造成整个链路服务不可用,进而导致整个系统的雪崩,如图所示:
在服务提供者不可用的时候,会出现大量重试的情况:用户重试、代码逻辑重试,这些重试最终导致进一步加大请求流量。所以归根结底导致雪崩效应的最根本原因是:大量请求线程同步等待造成的资源耗尽。当服务调用者使用同步调用时,会产生大量的等待线程占用资源。一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态,于是服务雪崩效应产生了。
从以下两方面入手:Reliability (稳定性)&& Resilience(恢复性)
常见的容错机制:超时机制、服务限流、隔离(线程隔离或者信号隔离)、服务熔断(远程服务不稳定或者网络抖动时暂时关闭,叫做服务熔断)
官方门户地址:https://sentinelguard.io/zh-cn/docs/introduction.html
GitHub Wiki:https://github.com/alibaba/Sentinel/wiki
Sentinel 是面向分布式服务架构的高可用防护组件,主要以流量为切入点,从流量控制、熔断降级、系统自适应保护等多个维度来帮助用户保障微服务的稳定性。江湖人称:分布式系统的流量防卫兵。
Sentinel具有一下特征:
阿里云提供了 企业级的 Sentinel 服务,应用高可用服务 AHAS
Sentinel | Hystrix | |
---|---|---|
隔离策略 | 信号量隔离 | 线程池隔离/信号量隔离 |
熔断降级策略 | 基于响应时间或失败比率 | 基于失败比率 |
实时指标实现 | 滑动窗口 | 滑动窗口(基于 RxJava) |
规则配置 | 支持多种数据源 | 支持多数据源 |
拓展性 | 多个拓展点 | 插件的形式 |
基于注解的支持 | 支持 | 支持 |
限流 | 基于QPS,支持基于调用关系的限流 | 有限的支持 |
流量整形 | 支持慢启动、匀速启动 | 不支持 |
系统负载保护 | 支持 | 不支持 |
控制台 | 开箱即用、可配置规则、秒级监控、机器发现等 | 不完善 |
常见框架的适配 | Servlet、Spring Cloud、Dubbo、gRPC等 | Servlet、Spring Cloud Netflix |
Sentinel 的使用可以分为两个部分:
核心库不依赖 Dashboard,但是结合 Dashboard 可以取得最好的效果。
https://github.com/alibaba/Sentinel/wiki/%E6%B3%A8%E8%A7%A3%E6%94%AF%E6%8C%81
官方文档:https://sentinelguard.io/zh-cn/docs/dashboard.html
sentinel 控制台包含如下功能:
从 release 页面 下载最新版本的控制台 jar 包
根据前面的文章,我们选择1.8.3版本
注意:启动 Sentinel 控制台需要 JDK 版本为 1.8 及以上版本。
从 Sentinel 1.6.0 起,Sentinel 控制台引入基本的登录功能,默认用户名和密码都是 sentinel。
使用如下命令启动控制台:
java -Dserver.port=8080 -Dcsp.sentinel.dashboard.server=localhost:8080 -Dproject.name=sentinel-dashboard -jar sentinel-dashboard.jar
其中 -Dserver.port=8080 用于指定 Sentinel 控制台端口为 8080。
控制台的一些特性可以通过配置项来进行配置,配置项主要有两个来源:System.getProperty() 和 System.getenv(),同时存在时后者可以覆盖前者
通过环境变量进行配置时,因为不支持 . 所以需要将其更换为 _
配置项 | 类型 | 默认值 | 最小值 |
---|---|---|---|
auth.enabled | boolean | true | - |
sentinel.dashboard.auth.username | String | sentinel | - |
sentinel.dashboard.auth.password | String | sentinel | - |
sentinel.dashboard.app.hideAppNoMachineMillis | Integer | 0 | 60000 |
sentinel.dashboard.removeAppNoMachineMillis | Integer | 0 | 120000 |
sentinel.dashboard.unhealthyMachineMillis | Integer | 60000 | 30000 |
sentinel.dashboard.autoRemoveMachineMillis | Integer | 0 | 300000 |
sentinel.dashboard.unhealthyMachineMillis | Integer | 60000 | 30000 |
server.servlet.session.cookie.name | Integer | sentinel_dashboard_cookie | - |
配置示例:
java -Dsentinel.dashboard.app.hideAppNoMachineMillis=60000
System.setProperty("sentinel.dashboard.app.hideAppNoMachineMillis", "60000");
sentinel_dashboard_app_hideAppNoMachineMillis=60000
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-sentinelartifactId>
dependency>
spring:
application:
name: service-sentinel_order # 应用名称(nacos会将该名称当作服务名称)
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # Nacos 配置中心地址 (端口不能为空)
username: nacos # Nacos用户名,默认为 nacos
password: nacos # Nacos密码,默认为 nacos
namespace: public # 名称空间,默认为 public
sentinel: # sentinel 配置
transport:
dashboard: 127.0.0.1:8080
监控接口通过和拒绝的QPS
注:时间监控需要调用接口之后,等一段时间才有展示(可能是电脑问题,我大概等了1分钟)
流控规则用于生产端(流量防卫兵)
流控规则(flow control),其原理是监控应用流量的QPS或并发数等指标,当达到指定的阈(yù)值时对流量进行控制,以避免被瞬间的流量高峰冲垮,从而保障应用的高可用。
注意:Sentinel规则默认是存储在内存中的,只要服务重启之后对应的规则也会消失。后面将会改用nacos持久化。
RT:响应时间 1/0.2s = 5
QPS:(每秒查询率,Queries-Per-Second)
对于上图的配置Sentinel把它抽象成一个FlowRule类,与其属性一一对应
并发线程数控制:用于保护业务线程池不被调用耗尽。例如:当应用所依赖的下游应用由于某种原因导致服务不稳定、响应延迟增加,对于调用者来说,意味着吞吐量下降和更多的线程数被占用,极端情况下甚至导致线程池耗尽。为了应对太多线程占用的情况,业内有使用隔离的方案,比如通过不同业务逻辑使用不同线程池来隔离业务自身之间的资源抢占(线程池隔离)。这种隔离方案随缘隔离性比较好,但是代价是线程数目太多,线程上下文切换的overhead(开销)比较大,特别是对低延迟的调用有比较大的影响。Sentinel 并发控制不负责创建和管理线程池,而是简单统计当前请求上下文的线程数目(正在执行的调用数目),如果超出阈值,新的请求会被立即拒绝,效果类似于信号量隔离。并发数控制通常在调用端进行配置。
springwebmvc接口资源限流入口在Hand的实现类的preHadnle方法中,对异常的处理是BlockExcep的实现类(如果需要根据业务场景细化异常处理,就不太适合使用统一异常处理)
sentinel 1.7.1 引入了 sentinel-spring-webmvc-adapter.jar
import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.authority.AuthorityException;
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 com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 自定义BlockExceptionHandler 全局异常处理
*/
@Slf4j
@Component
public class MyBlockExceptionHandler implements BlockExceptionHandler {
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
log.info("BlockExceptionHandler handle ============== {}",e.getRule());
// 记录异常信息(真实项目中会使用统一响应返回对象)
String exceptionInfo = "";
if (e instanceof FlowException){
exceptionInfo= "接口限流了!";
}else if(e instanceof DegradeException){
exceptionInfo= "服务降级了!";
}else if(e instanceof ParamFlowException){
exceptionInfo= "热点参数限流了!";
} else if(e instanceof SystemBlockException){
exceptionInfo= "触发系统保护规则了!";
}else if(e instanceof AuthorityException){
exceptionInfo= "授权规则不通过!";
}
// 返回数据
httpServletResponse.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.setContentType(MediaType.TEXT_HTML_VALUE);
httpServletResponse.getWriter().write(exceptionInfo);
}
}
关联模式:统计与当前资源相关的另一个资源,触发阈值时,对当前资源限流;
例如:当两个资源之间具有资源争抢或者依赖关系的时候,这两个资源并具有了关联。比如对数据库同一个字段的读操作和写操作,读的速度过高会影响写的速度,写的速度过高会影响读的速度。如果放任读写操作争夺资源,则争抢本身带来的开销会降低整体的吞吐量。可使用关联限流来避免具有关联关系的资源之间过度的争抢,举例来说,read_db
和write_db
这两个资源分别代表数据库读写,我们可以给read_db
设置限流规则来达到写优先的目的:设置strategy
为RuleConstant.STRATEGY_RELATE
(关联流控模式)同时设置 refResource
(关联资源)为write_db
。这样写数据库操作过于频繁时,读数据的请求会被限流。
不太理解的话可用下单操作来代替:读数据代表下单,写数据代表减少库存。减少库存接口操作处理不过来的时候,限制下单操作。
使用场景:比如用户支付时需要修改订单状态,同时用户要查询订单。查询和修改操作会争抢数据库锁,产生竞争。业务需求是有限支付和更新订单的业务,因此当修改订单业务触发阈值时,需要对查询订单业务限流。
链路流控是根据调用链路入口限流。
下面记录了资源之间的调用链路,这些资源通过调用关系,相互之间构成了一颗调用树。这棵树的根节点是一个名为getUser的虚拟节点,调用链的入口都是这个虚节点的子节点。
一颗典型的调用树如下图所示:
getUser
/ \
/ \
/sentinel/order/test1 /sentinel/order/test2
上图中两个接口(入口)都调用了资源getUser,Sentinel 允许只根据某个入口的统计信息对资源限流。比如我们可以设置 strategy 为 RuleConstant.STRATEGY_CHAIN (链路流控),同时设置 refResource 。
从1.6.3 版本开始,Sentinel Web Filter默认收敛了所有URL的入口context,因此链路限流不生效。
从1.7.0 版本开始(对应Spring Cloud Alibaba的2.1.1.RELEASE),官方在 CommonFilter 引入了WEB_CONTEXT_UNIFY
参数,用于控制是否收敛context。将其配置为 fase 即可根据不同的URL进行链路限流。
Spring Cloud Alibaba 2.1.1.RELEASE 之后的版本,可通过配置
“spring.cloud.sentinel.web-context-unify=false” 即可关闭收敛。
spring.cloud.sentinel.web-context-unify: false
注意:经测试,此场景拦截不到BlockException,对应@SentinelResource指定的资源必须在@SentinelResource注解中指定blockHandler处理BlockException
总结:为了解决链路规则引入ComonFilter的方式,除了此处问题,还会导致更多的问题,不建议使用ComonFilter的方式。流控链路模式的问题等待官方后续修复,或者使用AHAS。
没配置加前
添加链路流控规则
使用压测工具或者apipost(或者postman)流程测试功能,调用未被限制的接口,使getUserName方法达到触发流控条件
对应 FlowRule
中的 controlBehavior
字段
即直接拒绝(RuleConstant.CONTROL_BEHAVIOR_DEFAULT
)方式是默认的流量控制方式,当QPS超过任意规则的阈值后,新的请求就会被立即拒绝,拒绝方式为抛出FlowException。这种方式适用于对系统处理能力确切已知的情况下,比如通过压测确定了系统的准确水位时。具体的例子参见 FlowqpsDemo。
即冷启动(RuleConstant.CONTROL_BEHAVIOR_WARM_UP
)方式。该方式主要用于系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮的情况。具体的例子参见 WarmUpFlowDemo。
冷加载因子:默认 coldFactor 为 3,即请求 QPS 从 threshold / 3 开始,经预热时长逐渐升至设定的 QPS 阈值。
理想情况,当流量激增的时候,在4秒后达到请求阈值,即每次处理5条数据(这里使用压测工具)
即匀速器(RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER
)方式。Sentinel 匀速排队等待策略是 Leaky Bucket 算法结合虚拟队列等待机制实现的。这种方式严格控制了请求通过的间隔时间,也即是让请求以均匀的速度通过,对应的是漏桶算法。具体的例子参见 PaceFlowDemo。
该方式的作用如下图所示:
这种方式主要用于处理间隔性突发的流量,例如消息队列。想象一下这样的场景,在某一秒有大量的请求到来,而接下来的几秒则处于空闲状态,我们希望系统能够在接下来的空闲期间逐渐处理这些请求,而不是在第一秒直接拒绝多余的请求。
注意:
1.匀速排队模式暂时不支持 QPS > 1000 的场景。
2.当匀速器生效的时候,规则的限流类型一定是 RuleConstant.GRADE_QPS,否则该规则将不生效。当 count 设为 10的时候,则代表一秒匀速的通过 10 个请求,也就是每个请求平均间隔恒定为 1000 / 10 = 100 ms,每一个请求的最长等待时间(maxQueueingTimeMs)为 5 * 1000ms = 5s。
熔断降级规则用与消费端(查看第三方接口是否出现慢调用等异常状况)
熔断降级规则(DegradeRule)包含下面几个重要的属性:
慢调用比例 (SLOW_REQUEST_RATIO
):选择以慢调用比例作为阈值,需要设置允许的慢调用 RT(即最大的响应时间),请求的响应时间大于该值则统计为慢调用。当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且慢调用的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态
),若接下来的一个请求响应时间小于设置的慢调用 RT 则结束熔断,若大于设置的慢调用 RT 则会再次被熔断。
我们本次使用的Sentinel版本是1.8.3,在1.8之前的版本慢调用比例就是RT,慢调用相较于之前的RT加入了比例阈值,相当于多加了一个条件。
慢调用比例的熔断时机:在统计时长内,请求数大于5个,如若大于指定比例阈值的请求数的响应时间都大于最大RT,那么会熔断该服务,熔断时间为设置的熔断时长。
异常比例 (ERROR_RATIO
):当单位统计时长(statIntervalMs)内请求数目大于设置的最小请求数目,并且异常的比例大于阈值,则接下来的熔断时长内请求会自动被熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态
),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。异常比率的阈值范围是 [0.0, 1.0],代表 0%- 100%。
注意异常降级仅针对业务异常,对 Sentinel 限流降级本身的异常(BlockException)不生效。
异常数 (ERROR_COUNT
):当单位统计时长内的异常数目超过阈值之后会自动进行熔断。经过熔断时长后熔断器会进入探测恢复状态(HALF-OPEN 状态
),若接下来的一个请求成功完成(没有错误)则结束熔断,否则会再次被熔断。
feign:
sentinel:
enabled: true # 开启openFeign 整合sentinel
注意:需要配合@SentinelResource注解使用
Sentinel 利用 LRU 策略统计最近最常访问的热点参数,结合令牌桶算法来进行参数级别的流控。
Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性。
主要目的
推送模式 | 说明 | 优点 | 缺点 |
---|---|---|---|
原始模式 | API 将规则推送至客户端并直接更新到内存中,扩展写数据源(WritableDataSource) | 简单,无任何依赖 | 不保证一致性;规则保存在内存中,重启即消失。严重不建议用于生产环境 |
Pull 模式 | 扩展写数据源(WritableDataSource), 客户端主动向某个规则管理中心定期轮询拉取规则,这个规则中心可以是 RDBMS、文件 等 | 简单,无任何依赖;规则持久化 | 不保证一致性;实时性不保证,拉取过于频繁也可能会有性能问题。 |
Push 模式 | 扩展读数据源(ReadableDataSource),规则中心统一推送,客户端通过注册监听器的方式时刻监听变化,比如使用 Nacos、Zookeeper 等配置中心。这种方式有更好的实时性和一致性保证。生产环境下一般采用 push 模式的数据源。 | 规则持久化;一致性;快速 | 引入第三方依赖 |
模拟一个最简单的QPS流控规则
[
{
"resource": "/sentinel/order/testGlobalBlockException",
"count": 5,
"grade": 1,
"limitApp": "default",
"strategy": 0,
"controlBehavior": 0
}
]
参数详解:
<dependency>
<groupId>com.alibaba.cspgroupId>
<artifactId>sentinel-datasource-nacosartifactId>
dependency>
spring:
cloud:
sentinel:
datasource: # 对应配置类:SentinelProperties
ds1: # 数据源名称(自定义)
nacos: # 对应配置类NacosDataSourceProperties 还有一个父级AbstractDataSourceProperties
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
groupId: SENTINEL_GROUP
dataId: service-sentinel-order.json
data-type: json
rule-type: flow # 对应枚举:RuleType
# ds2: # 配置第二个数据源名称(如果有多个的话)
# .....