Sentinel笔记

1.常见的处理服务雪崩的方式

  • 超时时间
  • 限流
  • 仓壁模式
  • 断路器模式

2.Sentinel流控方式

流控模式:

  • 直接:根据QPS或者线程数直接关联,超过阈值直接失败
  • 关联:关联资源超过阈值,则失败
  • 链路:绑定某入口资源的阈值,超时则失败

流控策略:

  • 快速失败:直接失败抛出异常,源码位置com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController
  • Warm Up : 预热模式,设置初始流量为阈值/codeFactor(默认为3),经过设置的默认时长才达到阈值,源码位置:com.alibaba.csp.sentinel.slots.block.flow.controller.WarmUpController,适用于大流量,避免一次性流量过大导致服务直接崩溃。
  • 排队等待:请求匀速通过,阈值必须设置成QPS,否则无效。适用于流量激增。

3.降级策略

  • RT(秒级统计),在时间窗口内QPS>=5次,并且平均响应时间超出阈值,则触发降级,在时间窗口结束后关闭降级,RT的最大响应设置时间为4900ms,若需要指定更大,则需要通过-Dcsp.sentinel.statistic.max.rt=xx
  • 异常比例(秒级统计),QPS>=5并且异常比例超过阈值,则会进入熔断
  • 异常数(分钟统计),异常数超过阈值,则触发降级,若时间窗设置小于60S,那么可能在降级时间窗结束后在此进入降级

4.热点策略

  • 针对api接口参数特定的值进行限流,其中配置参数索引的特点热点值数据只对基本数据类型和String有效
  • 可参考相关源码com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowChecker#passCheck

[图片上传失败...(image-87caf8-1565855752793)]

5.系统规则(load1,cpu,..)

load1 = cpu核心数 * 2.5

6.授权规则

针对微服务名称进行黑白名单设置,可以通过定义RequestOriginParser来设置名称规则

7.代码配置方式配置Sentinel容错策略

ex : 初始化一个简单的流控规则,在资源执行前调用即可

    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实现了服务注册和发现
  • 通过配置的通信端口+服务ip,sentinel可以使用http调用其api来操作设置容错规则和获取监控信息

源码解析:

注册/心跳发送:com.alibaba.csp.sentinel.transport.heardheat.SimpleHttpHeartbeatSender

通信api : com.alibaba.csp.sentinel.commonand.CommandHandler相关实现类

9.Sentinel Api的简单使用

  • ContextUtil 定义入口资源
  • Sphu 定义资源
  • Tracer.trace() 统计资源 , 在非BlockException的异常时候需要显示的使用来统计,不然不会自动统计
   @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);
            //执行业务逻辑
            if (!StringUtils.isNotBlank("")) {
                throw new IllegalAccessException("参数非法");
            }
            return "success";
        } catch (BlockException e) {
            return "容错触发!!";
        } catch (IllegalAccessException e) {
            //默认除了BlockException及其子类会计算容错次数,异常数等等。。
            //其他异常需要使用Tracer.trace()进行统计
            Tracer.trace(e);
            return "非法参数";
        } finally {
            if (entry != null) {
                entry.exit();
            }
            ContextUtil.exit();
        }
    }

10.sentinel整合RestTemplate和feign

  • 直接在注入的RestTemplate上面加入@SentinelRestTemplate注解即可

核心处理原理见 : SentinelBeanPostProcessor

  • 整合feign,直接在配置文件中指定feing.sentinel.enabled=true即可。

11.持久化Sentinel规则

  • 默认不持久化,保存在内存中
  • pull模式,将sentinel规则保存在文件或者数据库中
  • push模式(生成环境使用),将sentinel的持久化规则同步到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>
  • 修改拉取和推送的策略

具体实现见 github

微服务集成sentinel持久化规则
  • 加依赖
        <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
            # 规则类型,取值见:
            # 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请求错误拦截页面?实现UrlBlockHandler即可

@Component
public class MyUrlBlockHandler implements UrlBlockHandler {
    @Override
    public void blocked(HttpServletRequest httpServletRequest, HttpServletResponse response, BlockException e) throws IOException {
        ErrorMsg msg = null;
      //根据异常类型的不同判断是发生了限流还是降级
        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的来源应用配置

  • 可以通过不同的来源来配置匹配的策略。
  • 可以通过授权规则来添加黑白名单。

通过实现RequestOriginParser来实现,该方法的返回值就是应用的名称。

/**
 * @author hj
 * 2019-08-15 13:55
 * 自定义区分来源解释器,通过origin请求参数来区分来源应用
 */
@Component
public class MyRequestOriginParser implements RequestOriginParser {
    @Override
    public String parseOrigin(HttpServletRequest httpServletRequest) {
        //如果参数中有origin参数那么获取并获取值作为来源
        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会随着参数变化而产生多个资源,那么如何让这些资源共享一个sentinel规则? 通过UrlCleaner实现类将这种rest风格转化成相同的url

实现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

更多配置和详细,见Sentinel GitHub

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容