유래
눈사태 효과 :
微服务架构:
例如:现在有个高并发的服务系统. 开始每个微服务都是正常的. 现在A服务挂了. 而这时B还在疯狂调用A服务
每个调用都是一个线程. 如果挂了,线程就会等待. 时间下去,请求会积累,资源会被耗尽. 比如:内存,CPU.
基础服务故障,并且导致上层服务故障. 故障在不断的方法.称为雪崩效应
也称为级联失效,级联故障
결함 허용 기법
시간 제한
每次请求,设置超时时间,比如:1 000ms. 也就是1 秒钟. 如果请求没响应,则释放掉这个线程
제한
根据每个微服务承受的QPS,设置请求数量的阈值. 超过这个阈值,再有请求,直接拒绝.
모드 사일로 벽
例如,在泰坦尼克号当中. 每个船仓用钢板焊死. 这样的话.进水的船舱不会影响其他的船舱. 当初泰坦尼克号
最大能够容忍俩个船舱进水,而不影响行驶.
在软件中. 每个Controller 有一个独立的线程池. THREAD-POOL-1: coreThread:10.
而第二个 Controller也有个线程池
차단기
电闸
监控某个微服务的请求流量. 某个API进行监控. 5 秒 内的错误次数. 达到一定的阈值.
那么,认为这个API出错了. 就不调用这个API了
1. 某个服务错误次数过多. 打开断路器.
2. 打开一定时间. 之后再次测试一次.
3. 这次调用通过,则断路器关闭.
4. 否则,断路器 继续打开
요약 :
类比:
1. 超时: 释放够快,就不会死了
2. 限流: 只有一碗的饭量.给再多也不吃
3. 仓壁模式: 不把鸡蛋放在一个篮子里
4. 断路器: 监控 + 开关
보초
轻量级流量控制,熔断降级 Java库
➕ 의존
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba.sentinel</artifactId>
</dependency>
注解:没有
配置:没有
확인 :
添加actuator报错
添加 actuator 依赖. 验证
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
management:
endpoint:
web:
exposure:
include: '*'
启动内容中心. 查看 Sentinel 端点... ...
下载sentinel.jar验证
공식 웹 사이트
这是我使用的版本. 和 项目的Maven依赖版本一致
시작 프로세스
java -jar sentinel-dashboard-1.6.3.jar
这里下载的是sentinel的jar. 默认使用的是 8080端口. 自己项目需要指定别的端口
这时候 sentinel-dashboard还是没有数据. 是因为 sentinel是懒加载的.
需要访问过一次API之后才能在sentinel-dashboard看到数据
흐름 제어 규칙
sentinel 也是饥饿加载. 需要访问一次之后.才能够在sentinel-dashboard看到 资源. 之后点击簇点链路.
就可以看到
资源名:
控制台自动填写我们API的路径
针对来源:
sentinel针对调用者进行限流. 针对不同的微服务进行不同的限流规则. 一般为default.不区分来源.
如果区分来源,是需要扩展支持
阈值:
当QPS达到阈值的时候,进行限流操作
流控模式:
1. 直接:访问该API 达到一定次数就限流
2. 关联: 当关联资源达到一定QPS 就限流
当/ actuator/sentinel/ 达到阈值 1时,就限流 /shares/1 这个API
3. 链路: 只记录链路上的流量,当指定链路上流量达到之后,开始限流
@Slf4j
@Controller
@RequestMapping("/userCenter")
public class TestController {
@GetMapping("test-a")
public String testA(){
log.info("test-a,go to common");
return userService.common();
}
@GetMapping("test-b")
public String testB(){
log.info("test-b,go to common");
return userService.common();
}
}
@Slf4j
@Service
public class UserService {
@SentinelResource("common")
public String common(){
log.info("common 访问到了");
return "common";
}
}
在 sentinel中的簇点链路能够看到对应的访问路径.
这里为 test-a 做阈值限定. 如果test-a 达到阈值之后. 则限流
流控效果:
4. 快速失败: 直接抛出异常
5. Warm up: 预热. 又一个默认预热因子(3). 比如: 设置的预热时长为10.
6. 那么, 100/3. 经过10s之后才达到阈值.
3. 排队等待:均速排队. 让请求以均匀的速度通过. 那么此类型的阈值类型必须设为QPS
다운 그레이드 규칙
降级策略:
1. RT
平均响应时间. /shares/1 的API 秒级统计是1ms. 并且在5s内通过的请求大于等于 5次.触发降级
RT 的最大时间为4900ms. 可以通过 配置进行修改
2. 异常比例
3. 异常数
分钟级别的. 在1分钟内的异常数 大于10 .触发降级. 那么10s之后降级关闭.
问题在于,如果时间窗口太小. 那么会在此触发降级.
핫 규칙
热点参数限流规则. sentinel 默认是不支持这些规则的. 需要进行代码修改
TestController.java
@GetMapping("/test-hot")
@ResponseBody
@SentinelResource("hot")
public String testHot(
@RequestParam(required = false) String a,
@RequestParam(required = false) String b
){
log.info("test-Hot,Hot Hot Hot");
return "Hot Hot Hot";
}
第一个参数,访问在1s内的次数超过1次,则限流. 否则不限流
参数例外项:
参数不是5的话,阈值时1 . 如果是5的话,阈值时1000
热点规则支持对指定的参数限流,指定的参数值限流. 注意: 参数类型必须是基本类型或String.否则不会生效
시스템 규칙
支持LOAD,RT,线程数,QPS四种
LOAD: 当load1超过阈值时.且并发线程数超过系统容量时. 触发. 建议设置为 CPU核数*2.5
load1 也就是 1分钟的load. load支队Unix系统生效.window配置无效
有三档. load1,load5,load15
系统容量: maxQPS * minRt
maxQPS: 秒级统计出来的最大QPS
minRt: 秒级统计的最小响应时间
권한 부여 규칙
/shares/1 这个API,只允许 test 这个微服务访问
코드 구성 규칙
콘솔과 통신 감시
콘솔 구성 항목
应用连接控制台配置项:
控制台配置项:
자세한 SentinelAPI
保护的资源:
@GetMapping("/testSentinelApi")
public String testSentinelApi(@RequestParam(required = false)String a){
Entry testSentinelApi = null;
try {
testSentinelApi = SphU.entry("testSentinelApi");
// 被保护的业务逻辑
// xxx
}
// 如果保护的资源被限流或降级,就会排除 BlockException
catch (BlockException e) {
log.warn("降级或限流了");
return "限流或降级了";
}finally {
if (testSentinelApi != null) {
testSentinelApi.exit();
}
}
return a;
}
用Spu定义资源之后.Sentinel进行监控. 计算QPS,RT,线程数,等.出现错误.就会抛出BlockException
在代码中抛出的BlockException是因为,一旦该服务被降级或限流,则会抛出BlockException异常.
而之所以限流或降级,则是因为请求次数超过了阈值.
流控-针对来源:
@GetMapping("/testSentinelApi")
public String testSentinelApi(@RequestParam(required = false)String a){
Entry testSentinelApi = null;
String resourceName = "testSentinelApi";
// 第一个参数: 资源名称,第二个参数:来源:test-xxx 的微服务
ContextUtil.enter(resourceName,"test-xxx");
try {
testSentinelApi = SphU.entry(resourceName);
// 被保护的业务逻辑
// xxx
}
// 如果保护的资源被限流或降级,就会排除 BlockException
catch (BlockException e) {
log.warn("降级或限流了");
return "限流或降级了";
}finally {
if (testSentinelApi != null) {
testSentinelApi.exit();
}
ContextUtil.exit();
}
return a;
}
代码和sentinel控制台配合,指定流控规则
核心API:
Sphu: 定义资源,保护资源
Tracer: 想要的异常,进行统计
ContextUtil: 调用来源,.进行统计
@SentinelSource 코멘트
可以区分出是降级还是限流 .fallback和blockHandler.其中fallback是降级. Block是限流
@GetMapping("/testSentinelApi2")
@SentinelResource(value = "testSentinelApi2",blockHandler = "testSentinelApiBlock")
public String testSentinelApi2(@RequestParam(required = false)String a){
// 被保护的业务逻辑
// xxx
Entry testSentinelApi = null;
String resourceName = "testSentinelApi";
try {
testSentinelApi = SphU.entry(resourceName);
}
// 如果保护的资源被限流或降级,就会排除 BlockException
catch (BlockException e) {
log.warn("降级或限流了");
return "限流或降级了";
}finally {
if (testSentinelApi != null) {
testSentinelApi.exit();
}
ContextUtil.exit();
}
return a;
}
public String testSentinelApiBlock(@RequestParam(required = false)String a){
return "限流或降级了";
}
@SentinelSource 不支持来源. 即 ContextUtil 进行来源设置.
被限流或降级,则是在blockHandler 填写方法名.BlockHandler 所填写的方法.
必须有个 被保护资源方法的参数和相同的返回值.
修改之后的代码:
@GetMapping("/testSentinelApi2")
@SentinelResource(value = "testSentinelApi2",blockHandler = "testSentinelApiBlock",fallback = "fallback")
public String testSentinelApi2(@RequestParam(required = false)String a){
// 被保护的业务逻辑
// xxx
Entry testSentinelApi = null;
String resourceName = "testSentinelApi";
try {
testSentinelApi = SphU.entry(resourceName);
}
// 如果保护的资源被限流或降级,就会排除 BlockException
catch (BlockException e) {
log.warn("降级或限流了");
return "限流或降级了";
}finally {
if (testSentinelApi != null) {
testSentinelApi.exit();
}
ContextUtil.exit();
}
return a;
}
public String testSentinelApiBlock(@RequestParam(required = false)String a){
return "限流或降级了";
}
public String fallback(@RequestParam(required = false)String a){
return "限流或降级了";
}
修正:
限流和降级方法在同一个类中. 可以使用blockHandlerClass 更改,
将 block方法修正为 statistical方法. 使用该属性配置即可实现
fallbackClass 同理
RestTemplate 센티넬 통합
@Bean
@LoadBalanced
@SentinelRestTemplate
public RestTemplate restTemplate(){
return new RestTemplate();
}
스위치 구성
# 关闭 @SentinelRestTemplate 注解
resttemplate.sentinel.enabled: false
该配置,是使Sentinel的规则不生效. 在代码功能没有最终版之前,不被干扰
관련 항목
@SentinelRestTemplate 注解提供了blockHandler fallback 和对应的class属性.可以指定
对应的异常处理类和异常处理方法.
척하기 센티넬 통합
# 为feign 整合 sentinel
fiegn.sentinel.enabled: true
异常应对:
发生了错误,降级或限流. 想要自己处理.做一些逻辑
1. 修正UserCenterFeignClient 接口
2. 在注解添加属性 fallback = X.class
3. X.class Implements UserCenterClient
4. 重写调用的方法即可
获得异常:
调用了fallback是一种异常状态. 怎么才能获取到异常的信息??
fallback 和fallbackFactory是俩种状态.二选一
5. 在@FeignClient注解行添加属性:fallbackFactory = X.class
6. 实现 FallbackFactory
7. 重写 create 方法即可
센티넬 사용 요약
센티넬 풀 모드
每次在服务重启时,规则就消失了. 这次修复这个缺点
주의 사항
센티넬 푸시 모드
주의 사항
原理简述:
将规则推送到 Nacos或其他配置中心.
Sentinel连接Nacos.获取规则配置.
센티넬 생산 환경
1. 规则持久化
1.1 推模式更佳
2. AHAS: 阿里云推出一款在线生产的控制台.
2.1 开通地址: https://ahas.console.aliyun.com
2.2 开通说明: https://help.aliyun.com/document_detail/90323.html
뱀
实现了规则持久化. 适合在生产环境中使用. 依照 AHAS 开通提示使用
클러스터 흐름 제어
在流控和热点规则里.新增规则,有集群选项.
集群之后,限流降级架构演变:
TokenServer 处理 来自TokenClient的请求. 根据集群流控规则.判断是否要限流
TokenServer 모드
1. 独立模式:
独立部署TokenServer
2. 嵌入模式:
将TokenServer集成在某个微服务上
확장 센티넬 - 오류 페이지 최적화
Sentinel接口提供了URLBlockHandler 进行错误也的扩展
구별 소스를 달성 센티넬
Sentinel提供了处理来源的接口: RequestOriginParse
@Component
public class MyRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
// 从 请求中获取到 origin 参数并返回,
// origin 就是来源的参数,找不到 就抛异常
String origin = httpServletRequest.getParameter("origin");
if (StringUtils.isNotBlank(origin)) {
throw new IllegalArgumentException(" origin must not blank");
}
return origin;
}
}
将来源,origin 的值拼接在参数中不太合适.这种类型的数据适合放在Header中.
편안하고 URL 지원
골
访问 shares/1,shares/2 使用相同的规则. 其中一个流控,另一个也流控. 因为他们都是用了 shares/ 开头的API
Sentinel提供了 UrlCleaner 接口.用来实现
@Slf4j
@Component
public class MyUrlCleaner implements UrlCleaner {
@Override
public String clean(String originUrl) {
log.info("originUrl = {}",originUrl);
String[] split = originUrl.split("/");
return Arrays.stream(split)
.map(string -> {
if(NumberUtils.isNumber(string)){
return "{number}";
}
return string;
})
.reduce((a,b) -> a+"/"+b)
.orElse("");
}
}
Sentinel- 넘어 봐
当我们默认访问Servlet(每个API,Controller的路径时).
doFilter(req,resp);
CommonFilter 实现了Filter.对每个请求做操作