目录
前言
常见的容错方案:
1.1使用步骤
1.引入库
2.在yml增加暴露auctor端点选项
二、整合sentinel控制台到项目(sentinel使用懒加载模式)
二、使用Sentinel实现限流
2.1在sentinel控制台进行配置,实现限流
流控模式
流控效果
三、使用Sentinel实现降级(断路器模式)
三、使用Sentinel实现热点规则
四、使用Sentinel实现系统保护规则
五、使用Sentinel授权规则
六、Sentinel APi的使用
七、Sentinel整合RestTemplate
八、Sentinel整合Feign
九、Sentinel规则的持久化
9.1拉模式
9.2拉模式
现代微服务架构都是分布式的,由非常多的服务组成。不同服务之间相互调用,组成复杂的调用链路。以上的问题在链路调用中会产生放大的效果。复杂链路上的某一环不稳定,就可能会层层级联,最终导致整个链路都不可用,这便是著名的雪崩效应。
⬆️阿里官方文档描述,说人话版本⬇️
微服务架构系统往往包含很多微服务,各个微服务之间通过轻量级的通信机制进行通信,构成一个完整的应用系统。但每个微服务都不是100%可用的,各个微服务之间的调用构成一个复杂的链路网,如果其中一个微服务挂掉,那么调用方将一直等待提供方响应,线程进入等待/阻塞状态。随着时间推移,陷入等待的线程可能越来越多,当达到了最大线程数,服务将不能再创建新的线程,也就意味着无法再提供服务,进而引起整个微服务群故障。
Sentinel是阿里巴巴出品的面向微服务架构的流量控制组件,主要以资源为切入点,从限流,降级,熔断等多个维度对服务进行保护。
1、「请求超时」
思想:只要跑得快,我就不容易被猪队友拖死
为每次请求设置一个比较短的超时时间,无论请求是否成功,达到设定时间后,线程就会被释放。只要线程释放的速度足够快,调用方就不至于那么容易被服务提供方拖死。
2、「限流」
思想:我效率就这么高,人再多我也没办法
给一个资源实例设定一个qps值,超过阈值,直接拒绝服务,通过这种方式保护自己。
3、「仓壁模式」
思想:你挂归你挂,不关我的事
每个资源之间相互隔离,例如以controller举例,每个Controller对应一个线程池,当一个线程池挂掉,不影响另一个Controller的正常工作。
4、「断路器」
思想:通过监控+开关,比较智能
断路器的三种状态的转换
1、当服务A去调用服务B,调用成功,则断路器处于关闭状态
2、当调用服务B失败,错误率或失败次数达到某个阈值,断路器将打开,中断整个链路。当过一段时间后,断路器将处于半开状态,这时允许一小部分流量(1个)来继续调用服务B,如果服务调用成功,断路器将关闭,如果继续失败,则继续回到打开状态,过一会再进入半开。
一、给项目引入sentinel
org.springframework.cloud
spring-cloud-starter-alibaba-sentinel
management:
endpoints:
web:
exposure:
include: '*'
访问localhost:port/auctor/sentinel出现以下页面,说明成功
1、去sentinel官网下载项目对应的sentinel版本,并启动Release v1.8.6 · alibaba/Sentinel · GitHub
2、在项目yml指定sentinel控制台的地址
sentinel:
transport:
# 指定sentinel 控制台的地址
dashboard: localhost:8080
针对来源:可以根据不同的(微服务)调用来源设置不同的qps值,这里默认为default,默认对所有调用方生效
直接:当资源(接口)达到多少阈值,就限流
关联:保护关联资源的一种设计,当关联的资源(可以是一个接口)达到设定的阈值,就限流自己
链路:只记录指定链路上的流量。
链路设置示意图
快速失败: 直接失败,抛异常
warm up: 预热模式(会丢弃请求),让阈值缓慢的增加,经过预热时长,逐渐让qps渐升到设定的值。初始值=设定的阈值/冷加载因子(默认是3)。【适用于秒杀场景】
排队等待: 不丢弃请求,让请求以均匀的速通过,如果用改种方式,阈值类型必须选择qps,不能使用线程,还有超时时间选项,意思是达到这个超时时间,就丢弃掉该请求,处理下一个请求。【适用突然繁忙,但大多数时间空闲的场景】
降级策略:
RT:平均响应时间(单位s)「最大值为4900ms」。
如果RT超过设定阈值,并且在时间窗口内进入的请求>=5,就会触发降级。时间窗口结束,才会关闭降级
异常比例:统计的是秒级别的
异常数:分钟级别,时间窗口<60可能会出问题
热点规则:比较特殊的流控规则,支持根据特定参数来设定限流规则
使用场景:存在热点参数,某些参数的qps特别高,并且希望提升api可用性的场景
Quick Start
1、新建测试端点
@GetMapping("test-hot")
@SentinelResource("hot")
public String testHot(
@RequestParam(required = false) String a,
@RequestParam(required = false) String b
) {
return a + " " + b;
}
2、热点规则设置
当第一个参数a=1,就会触发这个热点规则。a!=1或a为null,都不会触发流控
展开高级选项,还可以设置参数例外项,并且支持多种基础数据类型(注意:参数类型必须为string)
该规则实际生产环境用的比较少
LOAD:当系统load1(1分钟的load) 超过阈值,且并发线程数超过系统容量时触发,建议设置为cpu核心数*2.5(仅对Unix生效)
linux查看load命令:uptime
RT:所有入口流量的平均RT达到阈值就触发
线程数:所有入口流量的并发线程数达到阈值触发
入口QPS:所有入口流量的QPS达到阈值触发
/shares/1该资源,只允许test的这个微服务调用
@GetMapping("/test-sentinel-resource")
@SentinelResource(
value = "test-sentinel-api", //注意:这里只是定义了资源名,具体流控/降级规则还需在sentinel控制台进行配置
blockHandler = "block",
blockHandlerClass = TestControllerBlockHandlerClass.class,
fallback = "fallback"
)
public String testSentinelResource(@RequestParam(required = false) String a) {
if (StringUtils.isBlank(a)) {
throw new IllegalArgumentException("a cannot be blank.");
}
return a;
}
/**
* 1.5 处理降级
* - sentinel 1.6 可以处理Throwable(抛出的异常)
*
* @param a
* @return
*/
public String fallback(String a) {
return "限流,或者降级了 fallback";
}
TestControllerBlockHandlerClass.class
@Slf4j
public class TestControllerBlockHandlerClass {
/**
* 处理限流或者降级
*
* @param a
* @param e
* @return
*/
public static String block(String a, BlockException e) {
log.warn("限流,或者降级了 block", e);
return "限流,或者降级了 block";
}
}
在RestTemplateConfig添加 @SentinelRestTemplate注解
@GetMapping("/test-rest-template-sentinel/{userId}")
public UserDTO test(@PathVariable Integer userId) {
return this.restTemplate
.getForObject(
"http://user-center/users/{userId}",
UserDTO.class, userId);
}
调用一次该接口后便能在sentinel控制台看到对应资源,对其进行限流。
再调用便能看到限流成功
@FeignClient(name = "user-center",
fallbackFactory = UserCenterFeignClientFallbackFactory.class
)
public interface UserCenterFeignClient {
/**
* http://user-center/users/{id}
*
* @param id
* @return
*/
@GetMapping("/users/{id}")
UserDTO findById(@PathVariable Integer id);
}
UserCenterFeignClientFallbackFactory.class
@Component
@Slf4j
public class UserCenterFeignClientFallbackFactory implements FallbackFactory {
@Override
public UserCenterFeignClient create(Throwable cause) {
return new UserCenterFeignClient() {
@Override
public UserDTO findById(Integer id) {
log.warn("远程调用被限流/降级了", cause);
UserDTO userDTO = new UserDTO();
userDTO.setWxNickname("流控/降级返回的用户");
return userDTO;
}
};
}
}
思想:将规则持久化到本地,每次服务启动先读取JSON文件
一、FileRefreshableDataSource定时从指定文件中读取json文件,如果发现文件发生变化,更新规则缓存;
二、FileWritableDataSource接受控制台规则推送,并根据配置,修改json规则文件
步骤:
1、加依赖
com.alibaba.csp
sentinel-datasource-extension
2、添加类FileDataSourceInit
public class FileDataSourceInit implements InitFunc {
@Override
public void init() throws Exception {
// TIPS: 如果你对这个路径不喜欢,可修改为你喜欢的路径
String ruleDir = System.getProperty("user.home") + "/sentinel/rules";
String flowRulePath = ruleDir + "/flow-rule.json";
String degradeRulePath = ruleDir + "/degrade-rule.json";
String systemRulePath = ruleDir + "/system-rule.json";
String authorityRulePath = ruleDir + "/authority-rule.json";
String paramFlowRulePath = ruleDir + "/param-flow-rule.json";
this.mkdirIfNotExits(ruleDir);
this.createFileIfNotExits(flowRulePath);
this.createFileIfNotExits(degradeRulePath);
this.createFileIfNotExits(systemRulePath);
this.createFileIfNotExits(authorityRulePath);
this.createFileIfNotExits(paramFlowRulePath);
// 流控规则
ReadableDataSource> flowRuleRDS = new FileRefreshableDataSource<>(
flowRulePath,
flowRuleListParser
);
// 将可读数据源注册至FlowRuleManager
// 这样当规则文件发生变化时,就会更新规则到内存
FlowRuleManager.register2Property(flowRuleRDS.getProperty());
WritableDataSource> flowRuleWDS = new FileWritableDataSource<>(
flowRulePath,
this::encodeJson
);
// 将可写数据源注册至transport模块的WritableDataSourceRegistry中
// 这样收到控制台推送的规则时,Sentinel会先更新到内存,然后将规则写入到文件中
WritableDataSourceRegistry.registerFlowDataSource(flowRuleWDS);
// 降级规则
ReadableDataSource> degradeRuleRDS = new FileRefreshableDataSource<>(
degradeRulePath,
degradeRuleListParser
);
DegradeRuleManager.register2Property(degradeRuleRDS.getProperty());
WritableDataSource> degradeRuleWDS = new FileWritableDataSource<>(
degradeRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerDegradeDataSource(degradeRuleWDS);
// 系统规则
ReadableDataSource> systemRuleRDS = new FileRefreshableDataSource<>(
systemRulePath,
systemRuleListParser
);
SystemRuleManager.register2Property(systemRuleRDS.getProperty());
WritableDataSource> systemRuleWDS = new FileWritableDataSource<>(
systemRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerSystemDataSource(systemRuleWDS);
// 授权规则
ReadableDataSource> authorityRuleRDS = new FileRefreshableDataSource<>(
flowRulePath,
authorityRuleListParser
);
AuthorityRuleManager.register2Property(authorityRuleRDS.getProperty());
WritableDataSource> authorityRuleWDS = new FileWritableDataSource<>(
authorityRulePath,
this::encodeJson
);
WritableDataSourceRegistry.registerAuthorityDataSource(authorityRuleWDS);
// 热点参数规则
ReadableDataSource> paramFlowRuleRDS = new FileRefreshableDataSource<>(
paramFlowRulePath,
paramFlowRuleListParser
);
ParamFlowRuleManager.register2Property(paramFlowRuleRDS.getProperty());
WritableDataSource> paramFlowRuleWDS = new FileWritableDataSource<>(
paramFlowRulePath,
this::encodeJson
);
ModifyParamFlowRulesCommandHandler.setWritableDataSource(paramFlowRuleWDS);
}
private Converter> flowRuleListParser = source -> JSON.parseObject(
source,
new TypeReference>() {
}
);
private Converter> degradeRuleListParser = source -> JSON.parseObject(
source,
new TypeReference>() {
}
);
private Converter> systemRuleListParser = source -> JSON.parseObject(
source,
new TypeReference>() {
}
);
private Converter> authorityRuleListParser = source -> JSON.parseObject(
source,
new TypeReference>() {
}
);
private Converter> paramFlowRuleListParser = source -> JSON.parseObject(
source,
new TypeReference>() {
}
);
private void mkdirIfNotExits(String filePath) throws IOException {
File file = new File(filePath);
if (!file.exists()) {
file.mkdirs();
}
}
private void createFileIfNotExits(String filePath) throws IOException {
File file = new File(filePath);
if (!file.exists()) {
file.createNewFile();
}
}
private String encodeJson(T t) {
return JSON.toJSONString(t);
}
}
3、在项目resource/META-INF/servies目录出刚健文件,名为com.alibaba.csp.sentinel.init.InitFunc,内容
#改成上面init类的全包名
com.jaychan.contentcenter.sentineltest.FileDataSourceInit
4、该方式的优缺点
优点:简单易懂,没有多余依赖
缺点:
1、因为规则是定时更新的,所以规则有延迟。如果定时时间设置过大,长时间会有延迟,如果时间设置过小,又影响性能
2、规则存储在本地文件,如果有一天需要迁移微服务,需要吧规则文件一起迁移,否则会丢失
通过Nacos进行规则的同步,同时也会监听nacos里面的规则,实时更新
改动比较大,具体操作参考官方文档
在生产环境中使用 Sentinel · alibaba/Sentinel Wiki · GitHub
懒人包:
GitHub - eacdy/Sentinel-Dashboard-Nacos: Description Sentinel Dashboard使用NACOS作为数据源持久化规则。【仅用于教学,如用于生产,请务必做好测试!】