1.常見(jiàn)的處理服務(wù)雪崩的方式
- 超時(shí)時(shí)間
- 限流
- 倉(cāng)壁模式
- 斷路器模式
2.Sentinel流控方式
流控模式:
- 直接:根據(jù)
QPS或者線程數(shù)直接關(guān)聯(lián),超過(guò)閾值直接失敗 - 關(guān)聯(lián):關(guān)聯(lián)資源超過(guò)閾值,則失敗
- 鏈路:綁定某入口資源的閾值,超時(shí)則失敗
流控策略:
- 快速失敗:直接失敗拋出異常,源碼位置
com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController - Warm Up : 預(yù)熱模式,設(shè)置初始流量為閾值/codeFactor(默認(rèn)為3),經(jīng)過(guò)設(shè)置的默認(rèn)時(shí)長(zhǎng)才達(dá)到閾值,源碼位置:
com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController,適用于大流量,避免一次性流量過(guò)大導(dǎo)致服務(wù)直接崩潰。 - 排隊(duì)等待:請(qǐng)求勻速通過(guò),閾值必須設(shè)置成
QPS,否則無(wú)效。適用于流量激增。
3.降級(jí)策略
- RT(秒級(jí)統(tǒng)計(jì)),在時(shí)間窗口內(nèi)QPS>=5次,并且平均響應(yīng)時(shí)間超出閾值,則觸發(fā)降級(jí),在時(shí)間窗口結(jié)束后關(guān)閉降級(jí),RT的最大響應(yīng)設(shè)置時(shí)間為
4900ms,若需要指定更大,則需要通過(guò)-Dcsp.sentinel.statistic.max.rt=xx
- 異常比例(秒級(jí)統(tǒng)計(jì)),QPS>=5并且異常比例超過(guò)閾值,則會(huì)進(jìn)入熔斷
- 異常數(shù)(分鐘統(tǒng)計(jì)),異常數(shù)超過(guò)閾值,則觸發(fā)降級(jí),若時(shí)間窗設(shè)置小于60S,那么可能在降級(jí)時(shí)間窗結(jié)束后在此進(jìn)入降級(jí)
4.熱點(diǎn)策略
- 針對(duì)api接口參數(shù)特定的值進(jìn)行限流,其中配置參數(shù)索引的特點(diǎn)熱點(diǎn)值數(shù)據(jù)只對(duì)基本數(shù)據(jù)類(lèi)型和String有效
- 可參考相關(guān)源碼
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.授權(quán)規(guī)則
針對(duì)微服務(wù)名稱(chēng)進(jìn)行黑白名單設(shè)置,可以通過(guò)定義RequestOriginParser來(lái)設(shè)置名稱(chēng)規(guī)則
7.代碼配置方式配置Sentinel容錯(cuò)策略
ex : 初始化一個(gè)簡(jiǎn)單的流控規(guī)則,在資源執(zhí)行前調(diào)用即可
private void initFlowQpsRule() {
List<FlowRule> ruleList = new ArrayList<>();
FlowRule rule = new FlowRule("/c");
//設(shè)置閾值
rule.setCount(1);
//設(shè)置流控閾值類(lèi)型
rule.setGrade(RuleConstant.FLOW_GRADE_QPS);
//設(shè)置流控模式
rule.setStrategy(RuleConstant.STRATEGY_DIRECT);
//設(shè)置流控策略
rule.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
rule.setLimitApp("default");
ruleList.add(rule);
FlowRuleManager.loadRules(ruleList);
}
詳細(xì)所有的配置信息參考 - > 大佬Sentinel博客
8.Sentinel Dashboard 是如何知道微服務(wù)的信息?如何通信?
- Sentinel實(shí)現(xiàn)了服務(wù)注冊(cè)和發(fā)現(xiàn)
- 通過(guò)配置的通信端口+服務(wù)ip,sentinel可以使用http調(diào)用其api來(lái)操作設(shè)置容錯(cuò)規(guī)則和獲取監(jiān)控信息
源碼解析:
注冊(cè)/心跳發(fā)送:com.alibaba.csp.sentinel.transport.heardheat.SimpleHttpHeartbeatSender
通信api : com.alibaba.csp.sentinel.commonand.CommandHandler相關(guān)實(shí)現(xiàn)類(lèi)
9.Sentinel Api的簡(jiǎn)單使用
- ContextUtil 定義入口資源
- Sphu 定義資源
- Tracer.trace() 統(tǒng)計(jì)資源 , 在非
BlockException的異常時(shí)候需要顯示的使用來(lái)統(tǒng)計(jì),不然不會(huì)自動(dòng)統(tǒng)計(jì)
@GetMapping("/test-sentinel-api")
public String testSentinelApi() {
String resourceName = "test-sentinel-api";
ContextUtil.enter(resourceName, "test-micro-service");
Entry entry = null;
try {
//定義一個(gè)資源
entry = SphU.entry(resourceName);
//執(zhí)行業(yè)務(wù)邏輯
if (!StringUtils.isNotBlank("")) {
throw new IllegalAccessException("參數(shù)非法");
}
return "success";
} catch (BlockException e) {
return "容錯(cuò)觸發(fā)!!";
} catch (IllegalAccessException e) {
//默認(rèn)除了BlockException及其子類(lèi)會(huì)計(jì)算容錯(cuò)次數(shù),異常數(shù)等等。。
//其他異常需要使用Tracer.trace()進(jìn)行統(tǒng)計(jì)
Tracer.trace(e);
return "非法參數(shù)";
} finally {
if (entry != null) {
entry.exit();
}
ContextUtil.exit();
}
}
10.sentinel整合RestTemplate和feign
- 直接在注入的
RestTemplate上面加入@SentinelRestTemplate注解即可
核心處理原理見(jiàn) : SentinelBeanPostProcessor
- 整合
feign,直接在配置文件中指定feing.sentinel.enabled=true即可。
11.持久化Sentinel規(guī)則
- 默認(rèn)不持久化,保存在內(nèi)存中
- pull模式,將sentinel規(guī)則保存在文件或者數(shù)據(jù)庫(kù)中
- push模式(生成環(huán)境使用),將sentinel的持久化規(guī)則同步到nacos/zk/Apollo等配置中心上
push模式的集成
改造sentinel-dashboard控制臺(tái)
因?yàn)?code>push模式是sentinel-dashboard直接和遠(yuǎn)程配置中心直接交互,而不用sentinel客戶(hù)端去參加
修改步驟:
- 將sentinel-datasource-nacos依賴(lài)的范圍變成compile
<!-- for Nacos rule publisher sample -->
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
<!-- <scope>test</scope>-->
</dependency>
- 修改拉取和推送的策略
具體實(shí)現(xiàn)見(jiàn) github
微服務(wù)集成sentinel持久化規(guī)則
- 加依賴(lài)
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
- 配置
spring:
cloud:
sentinel:
transport:
dashboard: localhost:8080
port: 8820
datasource:
# 名稱(chēng)隨意
flow:
nacos:
server-addr: 192.168.18.91:8848
dataId: ${spring.application.name}-flow-rules
groupId: SENTINEL_GROUP
# 規(guī)則類(lèi)型,取值見(jiàn):
# 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在默認(rèn)是會(huì)攔截所有的controller請(qǐng)求
可配置排除
spring:
cloud:
sentinel:
filter:
enabled: false
如何定制默認(rèn)的controller請(qǐng)求錯(cuò)誤攔截頁(yè)面?實(shí)現(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ù)異常類(lèi)型的不同判斷是發(fā)生了限流還是降級(jí)
if (e instanceof FlowException) {
msg = ErrorMsg.builder()
.msg("被限流了")
.status(101)
.build();
} else if (e instanceof DegradeException) {
msg = ErrorMsg.builder()
.msg("被降級(jí)了")
.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的來(lái)源應(yīng)用配置
- 可以通過(guò)不同的來(lái)源來(lái)配置匹配的策略。
- 可以通過(guò)授權(quán)規(guī)則來(lái)添加黑白名單。
通過(guò)實(shí)現(xiàn)RequestOriginParser來(lái)實(shí)現(xiàn),該方法的返回值就是應(yīng)用的名稱(chēng)。
/**
* @author hj
* 2019-08-15 13:55
* 自定義區(qū)分來(lái)源解釋器,通過(guò)origin請(qǐng)求參數(shù)來(lái)區(qū)分來(lái)源應(yīng)用
*/
@Component
public class MyRequestOriginParser implements RequestOriginParser {
@Override
public String parseOrigin(HttpServletRequest httpServletRequest) {
//如果參數(shù)中有origin參數(shù)那么獲取并獲取值作為來(lái)源
String origin = httpServletRequest.getParameter("origin");
if (StringUtils.isBlank(origin)) {
throw new IllegalArgumentException("origin must not be null");
}
//無(wú)則拋出異常
return origin;
}
}
14.sentinel對(duì)Restful-url的支持
sentinel默認(rèn)對(duì)/a/{id}這種格式url會(huì)隨著參數(shù)變化而產(chǎn)生多個(gè)資源,那么如何讓這些資源共享一個(gè)sentinel規(guī)則? 通過(guò)UrlCleaner實(shí)現(xiàn)類(lèi)將這種rest風(fēng)格轉(zhuǎn)化成相同的url
實(shí)現(xiàn)UrlCleaner接口
/**
* @author hj
* 2019-08-15 14:24
* 擴(kuò)展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總結(jié)擴(kuò)展表格
其實(shí)上面的擴(kuò)展點(diǎn)都是來(lái)源于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)) {
//處理來(lái)源的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;
//處理錯(cuò)誤頁(yè)面返回的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();
}
| 核心接口 | 處理問(wèn)題 |
|---|---|
| UrlBlockHandler | 錯(cuò)誤處理 |
| UrlCleaner | 合并rest apis |
| RequestOriginParser | 來(lái)源控制ContextUtil |