[TOC]
*****
# 1. 雪崩效应
```
服务雪崩效应是一种因“服务提供者的不可用”(原因)导致“服务调用者不可用”(结果),并将不可用逐渐放大的现象
```
```
形成原因
服务雪崩的过程可以分为三个阶段:
服务提供者不可用;
重试加大请求流量;
服务调用者不可用;
服务雪崩的每个阶段都可能由不同的原因造成,总结如下:
```
![](https://img.kancloud.cn/e4/e8/e4e8fd226368a0a3d9e2b08d4738b385_1048x424.png)
**应对策略**
![](https://img.kancloud.cn/0c/ba/0cbaa264138e77b150d4be1277ac9e71_739x560.png)
# 2. 常见容错方案
```
1. 超时: 释放够快就不会挂掉
2. 限流: 只有一碗的饭量,给我三碗我吃不下
3. 仓壁模式: 如每个controller都有自己比较小的线程池,当线程池满了就会拒绝请求(线程池本身有拒绝机制),不把鸡蛋放在一个篮子里
4. 断路器模式: 监控+开关, 如对API进行监控,5s以内错误率/错误次数达到一定的阈值,就跳闸,不去调用其他服务,并且有半开机制
```
**断路器模式:**
![](https://img.kancloud.cn/a3/a1/a3a16d612ed87c387e8ef0af999dc47c_742x432.png)
# 3. 使用Sentinel实现容错
```
1. Sentinel是什么?
参考 [http://blog.didispace.com/spring-cloud-alibaba-sentinel-1/]
```
```
2. 应用整合Sentinel
(熔断/ 降级)
加依赖 compile("org.springframework.cloud:spring-cloud-starter-alibaba-sentinel")
```
# 4. Sentinel控制台
```
下载sentinel, 1.6.0版本之后才有登录页面
启动 java -jar sentinel-dashboard-1.6.0.jar
登录 http://localhost:8080 用户名/密码=[sentinel/sentinel]
```
![](https://img.kancloud.cn/3c/0a/3c0a129c01b42c0f4e0d4109b091bfb1_1083x521.png)
```
添加配置:
spring:
cloud:
sentinel:
transport:
#指定sentinel 控制台地址
dashboard: localhost:8080
```
![](https://img.kancloud.cn/3a/b5/3ab54d6c7a4abebe0e6afbfeae97ab66_828x482.png)
## 4.1 流控规则
```
资源名: 控制台自动填写我们请求的路径(唯一的名称)
针对来源: 比如有俩个服务(A / B)调用用户中心,可以为A设置200QPS, B设置300QPS,针对来源设置不同值,(default默认表示不区分来源)
阈值类型: QPS表示当达到QPS阈值时就是限流; 线程数表示当达到线程数的阈值时就是限流
是否集群: 表示是否支持集群模式
流控模式:
直接模式: 如把QPS设置成1, 当请求达到1次就限流,会报Blocked by sentinel (flow limiting)的异常
关联模式: [关联资源:当关联资源达到一定值时就限流自己],比如关联资源设置为/users/getUerInfo,请求路径达到阈值就限流当前资源
使用场景: 有俩个API(查询/修改),衡量优先读还是优先修改,如优先修改,关联资源处设置成[修改的API],资源名处设置成[查询的API]
链路模式: [只记录指定链路上的流量]API级别
流控效果:
快速失败: 直接抛异常
WarmUp: 预热, 根据codefactor(冷加载因子|默认3)的值,从阈值/codeFactor,经过预热时长,才达到设置的QPS阈值
如 阈值设置为100,预热时长设置为10s, 那么就会 100/3 作为最初阈值,经过10s后才达到100限流
场景: 如秒杀服务平时流量不会很高,但有活动的某一瞬间流量激增,不处理服务会挂掉,预热可以让流量缓慢的增加
排队等待: 匀速排队,让请求以均匀的速度通过,阈值类型必须设置成QPS,否则无效
原理: 如阈值类型设置QPS,单机阈值设置1,超时时间设置为200s;相当于1s请求1次,当时间超过200s后才会被丢弃
场景: (应对突发流量)一会流量很大,一会很空闲,希望应用能够在空闲时处理请求而不是直接拒绝请求
```
![](https://img.kancloud.cn/44/d7/44d741e756d3f28eea592f7ea02b5c74_829x440.png)
## 4.2 降级规则详解 (断路器)
```
资源名: 当前资源路径
降级策略:
RT: 若持续进入 5 个请求,它们资源的平均响应时间都超过阈值(秒级平均 RT,以 ms 为单位),资源调用会被熔断。
在接下的降级时间窗口(在降级规则中配置,以s为单位)之内,对这个方法的调用都会自动地返回(抛出 DegradeException)
注意点: RT默认最大4900ms -- 通过 -Dcsp.sentinel.statistic.max.rt=xxx 修改
异常比例模式: 当资源的每秒异常数占通过量的比值超过阈值之后,资源进入降级状态,即在接下的降级时间窗口(在降级规则中配置,以s为单位)之内,
对这个方法的调用都会自动地返回.异常比率的阈值范围是 [0.0, 1.0],代表 0% - 100%.
异常数模式: 当资源最近 1 分钟的异常数目超过阈值之后会进行熔断. 注意: 时间窗口<60s可能会出现问题
注意:
断路器有三种状态: 打开 | 关闭 | 半开, 但目前sentinel没有设置半开状态.
```
![](https://img.kancloud.cn/ab/0b/ab0b364e8e80d2aa9eeed0eccf8f3601_866x414.png)
## 4.3 热点规则详解
```
** 热点规则就是对指定的参数或者参数值限流;
sentinel默认显示的端点是不支持热点规则的,需要自定义代码实现
@GetMapping("test-hot")
@SentinelResource("hot") //使用该注解,请求刷新Sentinel控制台,才会出现热点规则配置
public String testHost(@RequestParam(required = false) String a,
@RequestParam(required = false) String b) {
return a + ":" + b;
}
```
```
资源名: @SentinelResource中定义的值即hot
参数索引: 参数a的索引就是0, 参数b的索引就是1, 若设置为0代表限流API带参数a的请求,若不带a,则不会被限流.
在时间窗口以内指定参数的QPS达到一定的阈值就会触发限流
高级选项: 如参数类型设置String, 参数值设置5, 限流阈值设置1000ms, 代表参数值是不是5时阈值就是1ms; 参数值是5时阈值就是1000ms
场景: API的某参数的QPS很高时, 可以使用热点规则,提高API的可用性;即对传递该参数或参数值的API限流,不影响调用该API传递其他参数或参数值
注意: 参数索引中配置的参数必须是[基本类型或者String,否则不会生效]
```
![](https://img.kancloud.cn/f9/7b/f97b79c6a0dc3938e45c63b09b911042_905x443.png)
## 4.4 系统规则详解
```
阈值类型:
LOAD(负载): 当系统load1(1分钟的负载)超过阈值,且并发线程数超过系统容量时触发,建议设置为CPU核心数*2.5(仅对linux/unix-like机器生效)
命令查看load: uptime
RT: 所有入口流量的平均RT达到阈值触发
线程数: 所有入口流量的并发线程数达到阈值触发
入口QPS: 所有入口流量的QPS达到阈值触发
```
![](https://img.kancloud.cn/9d/8c/9d8cbce837d42c75c30f2079f7d8e7fe_766x106.png)
![](https://img.kancloud.cn/76/78/7678254ce0a71b1b80fbfd6d33e57419_913x394.png)
## 4.5 授权规则详解
![](https://img.kancloud.cn/62/2e/622e08250ffc0ad85a7d5e71c081b7d2_912x443.png)
# 5. 代码配置规则
```
参考: https://www.imooc.com/article/289345
```
# 6. Sentinel与控制台通信原理剖析
```
控制台是如何获取到微服务的监控信息的? 用控制台配置规则时,控制台是如何将规则发送到各个微服务的呢?
http://ip:sentinel端口/api
源码:
注册/心跳发送的API: com.alibaba.csp.sentinel.transport.heartbeat.SimpleHttpHeartbeatSender
通信的API: com.alibaba.csp.sentinel.command.CommandHandler的实现类, 如: ApiCommandHandler
```
![](https://img.kancloud.cn/18/f2/18f2364a9a70cb774865ea2a912afdee_816x461.png)
# 7. 控制台相关配置项
**7-1. 应用端连接控制台配置项**
![](https://img.kancloud.cn/6f/ac/6facc05fb8295f2375a38101701814f4_571x305.png)
**7-2. 控制台的配置项**
![](https://img.kancloud.cn/97/cb/97cb8528af0abc102384a2047bfea138_855x367.png)
**7-3. 修改控制台的配置项操作 **
```
如:修改控制台的用户名和密码,如下图
```
![](https://img.kancloud.cn/4a/42/4a4245b645df02aecb28cc1426acb4fa_925x73.png)
# 8. SentinelAPI详解
```
核心API:
1. SphU: 定义资源,让资源受到监控并且可以保护资源
2. Tracer: 对我们想要的异常进行统计
3. ContextUtil: 可以实现调用来源,标记调用
```
```
spring:
cloud:
sentinel:
transport:
#指定sentinel 控制台地址
dashboard: localhost:8080
filter:
#关闭掉spring MVC端点的保护,测试为了防止干扰,正常要为true(或者默认)
enabled: false
```
```
//代码实现降级及限流及来源功能
String resourceName = "test-sentinel-api";
ContextUtil.enter(resourceName, "test-wfw");
//定义一个Sentinel保护的资源,名称可以任意,唯一即可
Entry entry = null;
try {
entry = SphU.entry(resourceName);
if(StringUtils.isEmpty(a)) {
throw new IllegalArgumentException("参数不合法");
}
// 被保护的业务逻辑
//TODO:
return a;
} catch (BlockException e) {
//如果被保护的资源被限流或者降级会抛出BlockException
} catch (IllegalArgumentException e) {
//统计IllegalArgumentException[发生的次数,发生占比...]
Tracer.trace(e);
return "参数不合法";
} finally {
if(entry != null) {
entry.exit();
}
ContextUtil.exit();
}
```
# 9. @SentinelResource注解详解
```
参考: https://www.imooc.com/article/289384
```
```
重写上面代码:
@GetMapping("test-sentinel-api")
@SentinelResource(value = "test-sentinel-api", blockHandler = "block", fallback = "fallback")
public String testSentinelAPI(@RequestParam(required = false) String a) {
if(StringUtils.isEmpty(a)) {
throw new IllegalArgumentException("参数不合法");
}
return a;
}
//处理限流或者降级
public String block(String a, BlockException e) {
return "限流或者降级了 block";
}
//1.5处理降级 sentinel 1.6可以处理Throwable
public String fallback(String a) {
return "限流或者降级了 fallback";
}
```
```
源码:
com.alibaba.csp.sentinel.annotation.aspectj.SentinelResourceAspect
com.alibaba.csp.sentinel.annotation.aspectj.AbstractSentinelAspectSupport
```
# 10. RestTemplate整合Sentinel
```
源码: org.springframework.cloud.alibaba.sentinel.custom.SentinelBeanPostProcessor
```
```
@Bean
@LoadBalanced
@SentinelRestTemplate
public RestTemplate restTemplate() {
return new RestTemplate();
}
配置开关:
resttemplate:
sentinel:
# 关闭@SentinelRestTemplate注解
enabled: false
```
# 11. Feign整合Sentinel
```
源码: org.springframework.cloud.alibaba.sentinel.feign.SentinelFeign
```
```
1. 只需要添加下面的配置:
feign:
sentinel:
#为feign整合sentinel
enabled: true
```
```
2. 限流降级发生时,如何定制自己的处理逻辑?
@FeignClient(name = "ali-pay-service",
configuration = UserCenterFeignConfiguration.class,
fallback = UserCenterFeginClientFallback.class)
public interface UserCenterFeginClient {
/**
* http://ali-pay-service/users/{id}
* @param id
* @return
*/
@GetMapping("/users/{id}")
String findById(@PathVariable Integer id);
}
@Component
public class UserCenterFeginClientFallback implements UserCenterFeginClient {
@Override
public String findById(Integer id) {
return null;
}
}
```
```
3. 进入到Fallback中是异常情况,如何获取到异常?
/**
* name: 请求服务的名称
* fallback与fallbackFactory不可同时用
* fallbackFactory可以获取到异常
*/
@FeignClient(name = "ali-pay-service",
configuration = UserCenterFeignConfiguration.class,
// fallback = UserCenterFeginClientFallback.class,
fallbackFactory = UserCenterFeginClientFallbackFactory.class
)
public interface UserCenterFeginClient {
/**
* http://ali-pay-service/users/{id}
* @param id
* @return
*/
@GetMapping("/users/{id}")
String findById(@PathVariable Integer id);
}
@Component
public class UserCenterFeginClientFallbackFactory implements FallbackFactory<UserCenterFeginClient> {
@Override
public UserCenterFeginClient create(Throwable cause) {
return new UserCenterFeginClient() {
@Override
public String findById(Integer id) {
return null;
}
};
}
}
```
# 12. 规则持久化
```
1. 规则持久化-拉模式
参考: https://www.imooc.com/article/289402
2. 规则持久化-推模式
参考: https://www.imooc.com/article/289464
3. 阿里云上提供免费的Sentinel的使用(AHAS)
开通地址: https://ahas.console.aliyun.com/
开通说明: https://help.aliyun.com/document_detail/90323.html
```
# 13. 集群流控
```
配置规则时设置集群模式,如控制台配置:
```
![](https://img.kancloud.cn/f1/92/f19254c0bcfd40729d111f9effc57339_887x469.png)
```
要想集群模式需要引入Token Client组件去和Token Server通信,
如何部署Token Server? 微服务如何继承Token Client?
参考官方https://github.com/alibaba/Sentinel/wiki
注: 目前Token Server还不能用在生产环境,很多功能需要自己实现,集群流控搭建参考:https://www.jianshu.com/p/bb198c08b418
但目前[实现集群的流控方案可以在网关处进行设置]更加简单,实用.
下图为集群流控模式的架构图:
```
![](https://img.kancloud.cn/ea/cc/eacc11adc16a14b52b975304e14c4b5b_732x359.png)
# 14. 扩展Sentinel
```
UrlBlockHandler : 提供Sentinel异常处理
RequestOriginParser : 区分来源
UrlCleaner : 重新定义资源名称
本质都是通过CommonFilter过滤器调用的,源码参照: com.alibaba.csp.sentinel.adapter.servlet.CommonFilter
```
```
1. 扩展Sentinel-错误页优化
@Component
public class MyUrlBlockHandler implements UrlBlockHandler {
@Override
public void blocked(HttpServletRequest request, HttpServletResponse response, BlockException ex) throws IOException {
ErrorMsg message = null;
//限流异常
if(ex instanceof FlowException) {
message = ErrorMsg.builder().code(100).msg("限流异常").build();
}
//降级异常
else if(ex instanceof DegradeException) {
message = ErrorMsg.builder().code(101).msg("降级异常").build();
}
//系统规则异常
else if(ex instanceof SystemBlockException) {
message = ErrorMsg.builder().code(102).msg("系统规则异常").build();
}
//参数热点规则异常
else if(ex instanceof ParamFlowException) {
message = ErrorMsg.builder().code(103).msg("参数热点规则异常").build();
}
//授权规则异常
else if(ex instanceof AuthorityException) {
message = ErrorMsg.builder().code(104).msg("授权规则异常").build();
}
//TODO: 通过response将异常信息写出去
}
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
class ErrorMsg {
private Integer code;
private String msg;
}
```
```
2. 扩展Sentinel-实现区分来源
@Component
public class MyRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest request) {
//来源的参数在实际应用中可以放在Header中
//从请求参数中过去名为 origin 的参数并返回
//如果获取不到 origin 参数,那么就抛出异常
String origin = request.getParameter("origin");
//String origin = request.getHeader("origin");
if (StringUtils.isEmpty(origin)) {
throw new IllegalArgumentException("origin must be set");
}
return origin;
}
}
```
```
3. 扩展Sentinel-RESTfulURL支持(针对所有资源设置相同的流控规则)
@Component
public class MyUrlCleaner implements UrlCleaner {
@Override
public String clean(String originUrl) {
//目标:path/1和path/2的返回值相同,返回/path/{number}
String[] split = originUrl.split("/");
return Arrays.stream(split).map(string -> {
if (NumberUtils.isNumber(string)) {
return "{number}";
}
return string;
}).reduce((a,b) -> a+"/"+b).orElse("");
}
}
```
# 15. Sentinel全部配置项
```
参照: https://www.imooc.com/article/289562
```