Sentinel筆記

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

更多配置和詳細(xì),見(jiàn)Sentinel GitHub

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容