[TOC]
*****
# 1. 雪崩效應
```
服務雪崩效應是一種因“服務提供者的不可用”(原因)導致“服務調用者不可用”(結果),并將不可用逐漸放大的現象
```
```
形成原因
服務雪崩的過程可以分為三個階段:
服務提供者不可用;
重試加大請求流量;
服務調用者不可用;
服務雪崩的每個階段都可能由不同的原因造成,總結如下:
```

**應對策略**

# 2. 常見容錯方案
```
1. 超時: 釋放夠快就不會掛掉
2. 限流: 只有一碗的飯量,給我三碗我吃不下
3. 倉壁模式: 如每個controller都有自己比較小的線程池,當線程池滿了就會拒絕請求(線程池本身有拒絕機制),不把雞蛋放在一個籃子里
4. 斷路器模式: 監控+開關, 如對API進行監控,5s以內錯誤率/錯誤次數達到一定的閾值,就跳閘,不去調用其他服務,并且有半開機制
```
**斷路器模式:**

# 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]
```

```
添加配置:
spring:
cloud:
sentinel:
transport:
#指定sentinel 控制臺地址
dashboard: localhost:8080
```

## 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后才會被丟棄
場景: (應對突發流量)一會流量很大,一會很空閑,希望應用能夠在空閑時處理請求而不是直接拒絕請求
```

## 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沒有設置半開狀態.
```

## 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,否則不會生效]
```

## 4.4 系統規則詳解
```
閾值類型:
LOAD(負載): 當系統load1(1分鐘的負載)超過閾值,且并發線程數超過系統容量時觸發,建議設置為CPU核心數*2.5(僅對linux/unix-like機器生效)
命令查看load: uptime
RT: 所有入口流量的平均RT達到閾值觸發
線程數: 所有入口流量的并發線程數達到閾值觸發
入口QPS: 所有入口流量的QPS達到閾值觸發
```


## 4.5 授權規則詳解

# 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
```

# 7. 控制臺相關配置項
**7-1. 應用端連接控制臺配置項**

**7-2. 控制臺的配置項**

**7-3. 修改控制臺的配置項操作 **
```
如:修改控制臺的用戶名和密碼,如下圖
```

# 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. 集群流控
```
配置規則時設置集群模式,如控制臺配置:
```

```
要想集群模式需要引入Token Client組件去和Token Server通信,
如何部署Token Server? 微服務如何繼承Token Client?
參考官方https://github.com/alibaba/Sentinel/wiki
注: 目前Token Server還不能用在生產環境,很多功能需要自己實現,集群流控搭建參考:https://www.jianshu.com/p/bb198c08b418
但目前[實現集群的流控方案可以在網關處進行設置]更加簡單,實用.
下圖為集群流控模式的架構圖:
```

# 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
```