概念
当A、B、G、H掉线,其他服务就没法通信了
随着微服务的流行,服务和服务之间的稳定性变得越来越重要。Sentinel 以流量为切入点,从流量控制、流量路由、熔断降级、系统自适应过载保护、热点流量防护等多个维度保护服务的稳定性。
特性:
富的应用场景:Sentinel 承接了阿里巴巴近 10 年的双十一大促流量的核心场景,例如秒杀(即突发流量控制在系统容量可以承受的范围)、消息削峰填谷、集群流量控制、实时熔断下游不可用应用等。
完备的实时监控:Sentinel 同时提供实时的监控功能。您可以在控制台中看到接入应用的单台机器秒级数据,甚至 500 台以下规模的集群的汇总运行情况。
广泛的开源生态:Sentinel 提供开箱即用的与其它开源框架/库的整合模块,例如与 Spring Cloud、Apache Dubbo、gRPC、Quarkus 的整合。您只需要引入相应的依赖并进行简单的配置即可快速地接入 Sentinel。同时 Sentinel 提供 Java/Go/C++ 等多语言的原生实现。
完善的 SPI 扩展机制:Sentinel 提供简单易用、完善的 SPI 扩展接口。您可以通过实现扩展接口来快速地定制逻辑。例如定制规则管理、适配动态数据源等。
主要用途:围绕资源的实时状态设定规则,所有规则都可以进行动态实时调整。如服务降级熔断、系统流量控制、系统自适应过载保护等。
Sentinel熔断降级会在调用链路中某个资源出现不稳定状态时(例如调用超时或异常比例升高),对这个资源的调用进行限制,让请求快速失败,避免影响到其它的资源而导致级联错误。当资源被降级后,在接下来的降级时间窗口之内,对该资源的调用都自动熔断(默认行为是抛出DegradeException)。
熔断、降级、限流
熔断
服务熔断的作用类似于我们家用的保险丝,当某服务出现不可用或响应超时的情况时,为了防止整个系统出现雪崩,暂时停止对该服务的调用。
停止是说,当前服务一旦对下游服务进行熔断,当请求到达时,当前服务不再对下游服务进行调用,而是使用设定好的策略(如构建默认值)直接返回。
暂时是说,熔断后,并不会一直不再调用下游服务,而是以一定的策略(如每分钟调用 10 次,若均返回成功,则增大调用量)试探调用下游服务,当下游服务恢复可用时,自动停止熔断。
降级
降级是指当自身服务压力增大时,采取一些手段,增强自身服务的处理能力,以保障服务的持续可用。比如,下线非核心服务以保证核心服务的稳定、降低实时性、降低数据一致性。
为了预防某些功能出现负荷过载或者响应慢的情况,在其内部暂时舍弃一些非核心接口和数据的请求(如评论、积分),而直接返回一个提前准备好的 fallback(退路) 错误处理信息。释放CPU和内存资源,以保证整个系统的稳定性和可用性。
所谓熔断降级就是为了解决微服务之间调用后产生的异常问题,如A调用了B,而B是微信支付或其他第三方的接口,由于当时的网络原因导致B的响应时长增加,从而造成调用方A的线程池被占用,极端情况下,A的线程池被耗尽,整个服务雪崩。
QPS是对请求的数量进行控制,而无法对于进入服务内的请求是否异常进行控制,熔断降级就是对进入服务之中的请求是否异常来进行控制,从而保护我们的服务。
所以当某个资源的响应时间的线程超过设定线程的阈值时,对于其可以进行熔断降级处理,暂时得关闭访问,熔断器open,当熔断时长到达之后,熔断器进入half-open状态,允许一个请求进入,无论该请求是否正常都会返回,然后如果这个请求正常则熔断器close,如果这个请求异常则熔断器继续open,继续熔断,反复上述步骤。
限流
限流是指上游服务对本服务请求 QPS 超过阙值时,通过一定的策略(如延迟处理、拒绝处理)对上游服务的请求量进行限制,以保证本服务不被压垮,从而持续提供稳定服务。常见的限流算法有滑动窗口、令牌桶、漏桶等。
docker安装Sentinel
docker pull bladex/sentinel-dashboard:1.8.0
docker run --name sentinel --restart=always -d -p 8858:8858 bladex/sentinel-dashboard:1.8.0
账号/密码都是 sentinel
项目中应用
注册到sentinel(服务监控)
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2021.1</version>
</dependency>
spring:
cloud:
sentinel:
transport:
client-ip: 192.168.56.1
dashboard: 192.168.56.10:8858 //sentinel 仪表盘地址
port: 8719 //默认 sentinel api 端口
实时监控
感觉不需要有这个也可以实时监控啊
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
Actuator默认情况下,只有/info和/heath端点是启用的。因为Actuator本身没有保护,所以大多数端点默认是禁用的,需要我们来选择对外暴露哪些端点。通过这2个属性management.endpoints.web.exposure.include和management.endpoints.web.exposure.exclude(排除哪些)。通过management.endpoints.web.exposure.include属性,可以指定哪些端点想要暴露出来。
我理解的端点指的是需要监控的API接口
management:
endpoints:
web:
exposure:
include: '*'
限流
流控规则
流控规则主要分为QPS 和 线程数两大控制方向, 而其后的处理方式有: 直接,关联,链路三种方式,处理效果又分为:快速失败,Warm Up(冷启动),匀速排队。
-
QPS (Query per second) : 即每秒的请求数量
-
线程数: 即服务中的并发量,值得注意的是,线程数就是并发量,而QPS不一定是并发量,如QPS为1000,而由于服务的耗时操作少,线程执行后归还到线程池的速度快,可能只需要10个线程即可完成,所以要明确QPS与线程数的区别
-
直接: 顾名思义,即是直接进行接下来的操作,是对被保护的服务进行操作
-
关联: 在关联资源触碰到阈值时对被保护资源进行操作
-
链路: 在多个调用中,针对某一条调用链路进行操作,如果触碰到阈值,则该条链路无法访问请求的资源
-
快速失败: 直接拒绝请求
-
Warm Up: 冷启动,如果启动需要耗时操作时可以如此设置,防止在刚开始进行耗时操作时过多的访问压垮服务,而给与一定的时间进行预热,之后达到处理峰值
-
匀速排队: 是为了针对访问在不同的时间内,访问量不同,因此设置一个时间长度,来匀速的处理请求,实现削峰填谷,让程序平稳的进行,使用到了漏桶算法,值得注意的是该处理方式不支持QPS> 1000 的场景
访问某个资源超过阈值时,该资源被保护不让访问
我理解warm up 作用预防在于再短时间内一下子过多qps导致压垮服务 在服务启动时,对该资源直接进行流控操作,在某段预热时长(单位秒)内,慢慢达到设定的阈值,期间超出的部分直接拒绝,如此达到保护目标资源的目的,Warm Up 的起始值为设定阈值的 1/3(如设定300,则起始阈值为100)
在访问目标资源时,由于访问在不同时间段内的访问量不同,设置此操作 在设定的时间段内(单位:毫秒),处理 阈值 个数的请求 , 每一个请求的处理时间为: 设置的超时时间 / 阈值
如果关联资源的QPS超过阈值,则被保护资源拒绝访问 注意点: 关联资源之间不一定就是调用关系,可能并没有关系,而设置之后是控制关联资源的QPS阈值来限制目标资源的访问权限
也是对关联资源在超过设定的QPS阈值时,对目标资源进行冷启动处理,初始阈值依然是设定阈值的 1/3 而后在预热时长内达到设定阈值,预热时间内超出阈值的请求直接快速失败
关联资源在超过设定阈值时,对于目标资源进行匀速排队的操作
machine-root / \ / \ Entrance1 Entrance2 / \ / \ DefaultNode(nodeA) DefaultNode(nodeA)
针对同一个资源有两个入口,Entrance1 和Entrance2 ,他们都调用了同一个资源nodeA,那么这样就形成了一个链路,对此,我们可以设置入口资源为Entrance1 或 Entrance2 中的一个,表示在某一个入口的QPS值达到阈值之后,切断这条链路,使该入口无法被访问
```java
上述介绍的冷启动和匀速排队的策略相同,只是针对的是链路中的某一个入口达到QPS阈值之后的操作
线程数超过阈值的操作一致
降级规则
最大 RT (Response Time) : 响应时间,就是在规定什么叫做 “慢” , 单个请求的响应时长大于我们设定的最大RT,那么这个请求就叫做 “慢请求”
比例阈值: 响应时间大于设定RT的线程占总请求数的比例,取值0.0~1.0之间
熔断时长: 达到阈值之后服务请求拒绝的时长(单位:秒)
最小请求数: 1秒钟内发出的最少需要的请求的总数量
解读: 如果一秒钟内发送了5个请求,而其中 比例阈值 * 最小请求数 的个数的请求超过了最大RT 的时间,那么目标资源就会被熔断降级,熔断时长单位为秒。
断路器的工作流程:
1、达到熔断的阈值之后,断路器的状态为open
2、熔断时长内所有的请求都被拒绝,无法进入
3、熔断时长之后,断路器的状态变为half-open,此时允许一个请求进来
4、若该请求没有异常,则断路器的状态为close,之后回归正常
5、若该请求依然异常,则断路器再次变为open状态,所有请求无法进入,等待下一次熔断时长之后
热点规则
授权规则
熔断降级fegin托底
@FeignClient(value = "mall-member", fallback = MemberFeginServiceFallBack.class)
@Component
public interface MemberFeginService {
@PostMapping("/mallmember/member/register")
public R register(@RequestBody UserRegisterVO userRegisterVO);
@PostMapping("/mallmember/member/login")
public R login(@RequestBody LoginVO loginVO);
}
/**
* @author guanglin.ma
* @date 2024-01-26 13:32
*/
@Component
@Slf4j
public class MemberFeginServiceFallBack implements MemberFeginService {
Logger logger = LoggerFactory.getLogger(MemberFeginServiceFallBack.class);
@Override
public R register(UserRegisterVO userRegisterVO) {
logger.error("注册失败哦");
return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(),
BizCodeEnume.UNKNOW_EXCEPTION.getMsg());
}
@Override
public R login(LoginVO loginVO) {
logger.error("登录失败哦");
return R.error(BizCodeEnume.UNKNOW_EXCEPTION.getCode(),
BizCodeEnume.UNKNOW_EXCEPTION.getMsg());
}
}
feign:
sentinel:
enabled: true
限流
public class SentinelUrlBlockHandler implements BlockExceptionHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, BlockException e) throws Exception {
R r = R.error(500,"访问太快,稍后再试!");
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.getWriter().write(JSON.toJSONString(r));
}
}
自定义资源(不前测试没有成功)
// 1.5.0 版本开始可以利用 try-with-resources 特性(使用有限制)
// 资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串。
try (Entry entry = SphU.entry("resourceName")) {
// 被保护的业务逻辑
// do something here...
} catch (BlockException ex) {
// 资源访问阻止,被限流或被降级
// 在此处进行相应的处理操作
}
// 资源名可使用任意有业务语义的字符串
if (SphO.entry("自定义资源名")) {
// 务必保证finally会被执行
try {
/**
* 被保护的业务逻辑
*/
} finally {
SphO.exit();
}
} else {
// 资源访问阻止,被限流或被降级
// 进行相应的处理操作
}
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-annotation-aspectj</artifactId>
<version>1.8.0</version>
</dependency>
@SentinelResource(value = "test1Resource",blockHandler = "test1BlockHandler")
@Override
public String test1() {
// 1.5.0 版本开始可以利用 try-with-resources 特性(使用有限制)
// 资源名可使用任意有业务语义的字符串,比如方法名、接口名或其它可唯一标识的字符串。
// try (Entry entry = SphU.entry("test1Resource")) {
// 被保护的业务逻辑
// do something here...
logger.error("正常访问");
return "正常访问";
// } catch (BlockException ex) {
// // 资源访问阻止,被限流或被降级
// // 在此处进行相应的处理操作
// logger.error("限流中");
// return "限流中";
//
// }
}
public void test1BlockHandler(BlockException blockException){
logger.error("限流中");
}
网关流控
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
<version>2021.1</version>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
<version>2021.1</version>
</dependency>
spring:
cloud:
sentinel:
transport:
dashboard: 192.168.56.10:8858
port: 8719
eager: true
management:
endpoints:
web:
exposure:
include: '*'
控制仪表可能不显示网关的控制列表,我只这么解决的
持久化
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<version>1.8.0</version>
</dependency>
spring:
cloud:
nacos:
discovery:
server-addr: 192.168.56.10:8848 #nacos地址
config:
server-addr: 192.168.56.10:8848 #nacos地址 配置中心
auto-refresh: true # 是否自动刷新,默认为 false
sentinel:
transport:
client-ip: 192.168.56.1
dashboard: 192.168.56.10:8858
port: 8719
eager: true
# Sentinel 规则持久化
datasource:
ds1:
nacos:
server-addr: 192.168.56.10:8848
dataId: test11
groupId: DEFAULT_GROUP
data-type: json
# 流控
rule-type: flow
degrade:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}-degrade-rules
groupId: SENTINEL_GROUP
# 降级
rule-type: degrade
system:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}-system-rules
groupId: SENTINEL_GROUP
# 系统
rule-type: system
authority:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}-authority-rules
groupId: SENTINEL_GROUP
# 授权
rule-type: authority
param-flow:
nacos:
server-addr: localhost:8848
dataId: ${spring.application.name}-param-flow-rules
groupId: SENTINEL_GROUP
# 热点
rule-type: param-flow
[
{
// 资源名
"resource": "/test",
// 针对来源,若为 default 则不区分调用来源
"limitApp": "default",
// 限流阈值类型(1:QPS;0:并发线程数)
"grade": 1,
// 阈值
"count": 1,
// 是否是集群模式
"clusterMode": false,
// 流控效果(0:快速失败;1:Warm Up(预热模式);2:排队等待)
"controlBehavior": 0,
// 流控模式(0:直接;1:关联;2:链路)
"strategy": 0,
// 预热时间(秒,预热模式需要此参数)
"warmUpPeriodSec": 10,
// 超时时间(排队等待模式需要此参数)
"maxQueueingTimeMs": 500,
// 关联资源、入口资源(关联、链路模式)
"refResource": "rrr"
}
]
[
{
// 资源名
"resource": "/test1",
"limitApp": "default",
// 熔断策略(0:慢调用比例,1:异常比率,2:异常计数)
"grade": 0,
// 最大RT、比例阈值、异常数
"count": 200,
// 慢调用比例阈值,仅慢调用比例模式有效(1.8.0 引入)
"slowRatioThreshold": 0.2,
// 最小请求数
"minRequestAmount": 5,
// 当单位统计时长(类中默认1000)
"statIntervalMs": 1000,
// 熔断时长
"timeWindow": 10
}
]
[
{
// 资源名
"resource": "/test1",
// 限流模式(QPS 模式,不可更改)
"grade": 1,
// 参数索引
"paramIdx": 0,
// 单机阈值
"count": 13,
// 统计窗口时长
"durationInSec": 6,
// 是否集群 默认false
"clusterMode": 默认false,
//
"burstCount": 0,
// 集群模式配置
"clusterConfig": {
//
"fallbackToLocalWhenFail": true,
//
"flowId": 2,
//
"sampleCount": 10,
//
"thresholdType": 0,
//
"windowIntervalMs": 1000
},
// 流控效果(支持快速失败和匀速排队模式)
"controlBehavior": 0,
//
"limitApp": "default",
//
"maxQueueingTimeMs": 0,
// 高级选项
"paramFlowItemList": [
{
// 参数类型
"classType": "int",
// 限流阈值
"count": 222,
// 参数值
"object": "2"
}
]
}
]
[
{
// RT
"avgRt": 1,
// CPU 使用率
"highestCpuUsage": -1,
// LOAD
"highestSystemLoad": -1,
// 线程数
"maxThread": -1,
// 入口 QPS
"qps": -1
}
]
[
{
// 资源名
"resource": "sentinel_spring_web_context",
// 流控应用
"limitApp": "/test",
// 授权类型(0代表白名单;1代表黑名单。)
"strategy": 0
}
]