1.常見的處理服務雪崩的方式
- 超時時間
- 限流
- 倉壁模式
- 斷路器模式
2.Sentinel流控方式
流控模式:
- 直接:根據(jù)
QPS或者線程數(shù)直接關聯(lián),超過閾值直接失敗 - 關聯(lián):關聯(lián)資源超過閾值,則失敗
- 鏈路:綁定某入口資源的閾值,超時則失敗
流控策略:
- 快速失?。褐苯邮伋霎惓?,源碼位置
com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController - Warm Up : 預熱模式,設置初始流量為閾值/codeFactor(默認為3),經(jīng)過設置的默認時長才達到閾值,源碼位置:
com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController,適用于大流量,避免一次性流量過大導致服務直接崩潰。 - 排隊等待:請求勻速通過,閾值必須設置成
QPS,否則無效。適用于流量激增。
3.降級策略
- RT(秒級統(tǒng)計),在時間窗口內QPS>=5次,并且平均響應時間超出閾值,則觸發(fā)降級,在時間窗口結束后關閉降級,RT的最大響應設置時間為
4900ms,若需要指定更大,則需要通過-Dcsp.sentinel.statistic.max.rt=xx
- 異常比例(秒級統(tǒng)計),QPS>=5并且異常比例超過閾值,則會進入熔斷
- 異常數(shù)(分鐘統(tǒng)計),異常數(shù)超過閾值,則觸發(fā)降級,若時間窗設置小于60S,那么可能在降級時間窗結束后在此進入降級
4.熱點策略
- 針對api接口參數(shù)特定的值進行限流,其中配置參數(shù)索引的特點熱點值數(shù)據(jù)只對基本數(shù)據(jù)類型和String有效
- 可參考相關源碼
com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowChecker#passCheck
[圖片上傳失敗...(image-87caf8-1565855752793)]
5.系統(tǒng)規(guī)則(load1,cpu,..)
load1 = cpu核心數(shù) * 2.5
6.授權規(guī)則
針對微服務名稱進行黑白名單設置,可以通過定義RequestOriginParser來設置名稱規(guī)則
7.代碼配置方式配置Sentinel容錯策略
ex : 初始化一個簡單的流控規(guī)則,在資源執(zhí)行前調用即可
private void initFlowQpsRule() {
List<FlowRule> ruleList = new ArrayList<>();
FlowRule rule = new FlowRule("/c");
//設置閾值
rule.setCount(1);
//設置流控閾值類型
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
//設置流控模式
rule.setStrategy(RuleConstant.STRATEGY_DIRECT);
//設置流控策略
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
rule.setLimitApp("default");
ruleList.add(rule);
FlowRuleManager.loadRules(ruleList);
}
詳細所有的配置信息參考 - > 大佬Sentinel博客
8.Sentinel Dashboard 是如何知道微服務的信息?如何通信?
- Sentinel實現(xiàn)了服務注冊和發(fā)現(xiàn)
- 通過配置的通信端口+服務ip,sentinel可以使用http調用其api來操作設置容錯規(guī)則和獲取監(jiān)控信息
源碼解析:
注冊/心跳發(fā)送:com.alibaba.csp.sentinel.transport.heardheat.SimpleHttpHeartbeatSender
通信api : com.alibaba.csp.sentinel.commonand.CommandHandler相關實現(xiàn)類
9.Sentinel Api的簡單使用
- ContextUtil 定義入口資源
- Sphu 定義資源
- Tracer.trace() 統(tǒng)計資源 , 在非
BlockException的異常時候需要顯示的使用來統(tǒng)計,不然不會自動統(tǒng)計
@GetMapping("/test-sentinel-api")
public String testSentinelApi() {
String resourceName = "test-sentinel-api";
ContextUtil.enter(resourceName, "test-micro-service");
Entry entry = null;
try {
//定義一個資源
entry = SphU.entry(resourceName);
//執(zhí)行業(yè)務邏輯
if (!StringUtils.isNotBlank("")) {
throw new IllegalAccessException("參數(shù)非法");
}
return "success";
} catch (BlockException e) {
return "容錯觸發(fā)?。?;
} catch (IllegalAccessException e) {
//默認除了BlockException及其子類會計算容錯次數(shù),異常數(shù)等等。。
//其他異常需要使用Tracer.trace()進行統(tǒng)計
Tracer.trace(e);
return "非法參數(shù)";
} finally {
if (entry != null) {
entry.exit();
}
ContextUtil.exit();
}
}
10.sentinel整合RestTemplate和feign
- 直接在注入的
RestTemplate上面加入@SentinelRestTemplate注解即可
核心處理原理見 : SentinelBeanPostProcessor
- 整合
feign,直接在配置文件中指定feing.sentinel.enabled=true即可。
11.持久化Sentinel規(guī)則
- 默認不持久化,保存在內存中
- pull模式,將sentinel規(guī)則保存在文件或者數(shù)據(jù)庫中
- push模式(生成環(huán)境使用),將sentinel的持久化規(guī)則同步到nacos/zk/Apollo等配置中心上
push模式的集成
改造sentinel-dashboard控制臺
因為push模式是sentinel-dashboard直接和遠程配置中心直接交互,而不用sentinel客戶端去參加
修改步驟:
- 將sentinel-datasource-nacos依賴的范圍變成compile
<!-- for Nacos rule publisher sample -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<!-- <scope>test</scope>-->
</dependency>
- 修改拉取和推送的策略
具體實現(xiàn)見 github
微服務集成sentinel持久化規(guī)則
- 加依賴
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
- 配置
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
port: 8820
datasource:
# 名稱隨意
flow:
nacos:
server-addr: 192.168.18.91:8848
dataId: ${spring.application.name}-flow-rules
groupId: SENTINEL_GROUP
# 規(guī)則類型,取值見:
# org.springframework.cloud.alibaba.sentinel.datasource.RuleType
rule-type: flow
degrade:
nacos:
server-addr: 192.168.18.91:8848
dataId: ${spring.application.name}-degrade-rules
groupId: SENTINEL_GROUP
rule-type: degrade
param-flow:
nacos:
server-addr: 192.168.18.91:8848
dataId: ${spring.application.name}-param-flow-rules
groupId: SENTINEL_GROUP
rule-type: param-flow
12.sentinel在默認是會攔截所有的controller請求
可配置排除
spring:
cloud:
sentinel:
filter:
enabled: false
如何定制默認的controller請求錯誤攔截頁面?實現(xiàn)UrlBlockHandler即可
@Component
public class MyUrlBlockHandler implements UrlBlockHandler {
@Override
public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse response, BlockException e) throws IOException {
ErrorMsg msg = null;
//根據(jù)異常類型的不同判斷是發(fā)生了限流還是降級
if (e instanceof FlowException) {
msg = ErrorMsg.builder()
.msg("被限流了")
.status(101)
.build();
} else if (e instanceof DegradeException) {
msg = ErrorMsg.builder()
.msg("被降級了")
.status(101)
.build();
}
response.setContentType("application/json;charset=utf-8");
response.setCharacterEncoding("utf-8");
response.setStatus(500);
new ObjectMapper()
.writeValue(
response.getWriter(),
msg
);
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
private static class ErrorMsg {
private String msg;
private Integer status;
}
}
13.Sentinel的來源應用配置
- 可以通過不同的來源來配置匹配的策略。
- 可以通過授權規(guī)則來添加黑白名單。
通過實現(xiàn)RequestOriginParser來實現(xiàn),該方法的返回值就是應用的名稱。
/**
* @author hj
* 2019-08-15 13:55
* 自定義區(qū)分來源解釋器,通過origin請求參數(shù)來區(qū)分來源應用
*/
@Component
public class MyRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
//如果參數(shù)中有origin參數(shù)那么獲取并獲取值作為來源
String origin = httpServletRequest.getParameter("origin");
if (StringUtils.isBlank(origin)) {
throw new IllegalArgumentException("origin must not be null");
}
//無則拋出異常
return origin;
}
}
14.sentinel對Restful-url的支持
sentinel默認對/a/{id}這種格式url會隨著參數(shù)變化而產生多個資源,那么如何讓這些資源共享一個sentinel規(guī)則? 通過UrlCleaner實現(xiàn)類將這種rest風格轉化成相同的url
實現(xiàn)UrlCleaner接口
/**
* @author hj
* 2019-08-15 14:24
* 擴展restFul-Url,讓/shares/* 使用相同的邏輯
*/
@Component
public class MyUrlCleaner implements UrlCleaner {
@Override
public String clean(String url) {
String[] split = url.split("/");
return Arrays.stream(split)
.map(a -> {
if (NumberUtils.isNumber(a)) {
return "{number}";
}
return a;
})
.reduce((a, b) -> a + "/" + b)
.orElse("");
}
}
15總結擴展表格
其實上面的擴展點都是來源于CommonFilter
public class CommonFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest sRequest = (HttpServletRequest)request;
Entry urlEntry = null;
try {
String target = FilterUtil.filterTarget(sRequest);
//處理 REST APIS 的UrlCleaner
UrlCleaner urlCleaner = WebCallbackManager.getUrlCleaner();
if (urlCleaner != null) {
target = urlCleaner.clean(target);
}
if (!StringUtil.isEmpty(target)) {
//處理來源的RequestOriginParser
String origin = parseOrigin(sRequest);
ContextUtil.enter(WebServletConfig.WEB_SERVLET_CONTEXT_NAME, origin);
if (httpMethodSpecify) {
// Add HTTP method prefix if necessary.
String pathWithHttpMethod = sRequest.getMethod().toUpperCase() + COLON + target;
urlEntry = SphU.entry(pathWithHttpMethod, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
} else {
urlEntry = SphU.entry(target, ResourceTypeConstants.COMMON_WEB, EntryType.IN);
}
}
chain.doFilter(request, response);
} catch (BlockException e) {
HttpServletResponse sResponse = (HttpServletResponse)response;
//處理錯誤頁面返回的UrlBlockHandler
WebCallbackManager.getUrlBlockHandler().blocked(sRequest, sResponse, e);
} catch (IOException | ServletException | RuntimeException e2) {
Tracer.traceEntry(e2, urlEntry);
throw e2;
} finally {
if (urlEntry != null) {
urlEntry.exit();
}
ContextUtil.exit();
}
| 核心接口 | 處理問題 |
|---|---|
| UrlBlockHandler | 錯誤處理 |
| UrlCleaner | 合并rest apis |
| RequestOriginParser | 來源控制ContextUtil |