前言

前几天公司生产环境一个服务由于流量上升触发了 Sentinel 的流控机制,然后用户反馈访问慢,定位发现是 task 定时任务导致,后面 task 优化之后发布,流量恢复正常。

这是一个再正常不过的生产问题,可能大部分同学都经历过,经历过的大多数是解决问题之后就不了了之,导致事故还有再次发生的可能,最终对用户造成了不好的体验。所以我觉得所有的生产问题都需要进行复盘,当然复盘的目的不是为了追责,而是防止下次再发生同样的错误。那我们就简单分析一下这个问题,首先肯定是业务层面的疏漏导致 task 发出不合理的大量请求,其二我们的流控只是简单粗暴的流控,没有更好的预警措施,导致影响到用户之后我们才知晓(即流控或熔断已经触发)。

那我们的解决方案呢?首先肯定是业务层面的预防,但这不是本文要说的重点,这里不展开讨论了。其次就是预警,就是我们能否在快要触发流控之前知晓,然后报警到相关负责人提前介入处理,防止触发流控熔断。当然也不能完全避免,但是总比流控或熔断触发之后在报警要好得多。

由于之前流控用的阿里的 Sentinel,所以本文介绍的具体实现是用 Sentinel 的自定义 slot 功能,这个自定义 slot 卡槽在 Sentinel 官方文档里面就一句话带过,然后加上一个 demo 代码,我在使用的过程中也遇到过不少坑,所以分享一下结果给大家。

如果大家对 Sentinel 不是很了解,可以先去 github 先了解简单试用一下在阅读本文。github 地址:https://github.com/alibaba/Sentinel

如果想熟悉自定义 slot 功能建议了解一下 Sentinel 的工作原理:https://github.com/alibaba/Sentinel/wiki/Sentinel%E5%B7%A5%E4%BD%9C%E4%B8%BB%E6%B5%81%E7%A8%8B

还有源码中的 demo 对于自定义 slot 的写法:https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-slot-chain-spi

具体实现

下面介绍下 Sentinel 预警功能的相关实现,使用的前提是你的系统已经在用 Sentinel 的流控或熔断等功能。

  1. 自定义 CustomSlotChainBuilder 实现 SlotChainBuilder 接口,这里主要是把我们自定义的 Slot 加到 SlotChain 这个链中
  1. import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain;
  2. import com.alibaba.csp.sentinel.slotchain.SlotChainBuilder;
  3. import com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder;
  4. import com.qiaofang.tortoise.gateway.component.ApplicationContextUtil;
  5. import com.qiaofang.tortoise.gateway.config.SentinelProperties;
  6. import org.springframework.stereotype.Component;
  7. import javax.annotation.Resource;
  8. /**
  9. * 自定义slot
  10. *
  11. * @author chenhao
  12. */
  13. public class CustomSlotChainBuilder implements SlotChainBuilder {
  14. @Override
  15. public ProcessorSlotChain build() {
  16. ProcessorSlotChain chain = new DefaultSlotChainBuilder().build();
  17. SentinelProperties sentinelProperties = (SentinelProperties) ApplicationContextUtil.getContext().getBean("sentinelProperties");
  18. chain.addLast(new FlowEarlyWarningSlot(sentinelProperties));
  19. chain.addLast(new DegradeEarlyWarningSlot(sentinelProperties));
  20. return chain;
  21. }
  22. }

2.自定义 FlowEarlyWarningSlot、DegradeEarlyWarningSlot 流控熔断 2 个预警 slot

自定义 FlowEarlyWarningSlot

  1. import com.alibaba.csp.sentinel.context.Context;
  2. import com.alibaba.csp.sentinel.node.DefaultNode;
  3. import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot;
  4. import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
  5. import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
  6. import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleChecker;
  7. import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
  8. import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleUtil;
  9. import com.alibaba.csp.sentinel.util.AssertUtil;
  10. import com.google.common.collect.Lists;
  11. import org.slf4j.Logger;
  12. import org.slf4j.LoggerFactory;
  13. import org.springframework.beans.BeanUtils;
  14. import org.springframework.util.CollectionUtils;
  15. import java.util.List;
  16. import java.util.Map;
  17. import java.util.stream.Collectors;
  18. /**
  19. * 流控预警slot
  20. *
  21. * @author chenhao
  22. */
  23. public class FlowEarlyWarningSlot2 extends AbstractLinkedProcessorSlot<DefaultNode> {
  24. /**
  25. * log
  26. */
  27. private Logger logger = LoggerFactory.getLogger(this.getClass());
  28. private final FlowRuleChecker checker;
  29. public FlowEarlyWarningSlot2() {
  30. this(new FlowRuleChecker());
  31. }
  32. /**
  33. * Package-private for test.
  34. *
  35. * @param checker flow rule checker
  36. * @since 1.6.1
  37. */
  38. FlowEarlyWarningSlot2(FlowRuleChecker checker) {
  39. AssertUtil.notNull(checker, "flow checker should not be null");
  40. this.checker = checker;
  41. }
  42. private List<FlowRule> getRuleProvider(String resource) {
  43. // Flow rule map should not be null.
  44. List<FlowRule> rules = FlowRuleManager.getRules();
  45. List<FlowRule> earlyWarningRuleList = Lists.newArrayList();
  46. for (FlowRule rule : rules) {
  47. FlowRule earlyWarningRule = new FlowRule();
  48. BeanUtils.copyProperties(rule, earlyWarningRule);
  49. /**
  50. * 这里是相当于把规则阈值改成原来的80%,达到提前预警的效果,
  51. * 这里建议把0.8做成配置
  52. */
  53. earlyWarningRule.setCount(rule.getCount() * 0.8);
  54. earlyWarningRuleList.add(earlyWarningRule);
  55. }
  56. Map<String, List<FlowRule>> flowRules = FlowRuleUtil.buildFlowRuleMap(earlyWarningRuleList);
  57. return flowRules.get(resource);
  58. }
  59. /**
  60. * get origin rule
  61. *
  62. * @param resource
  63. * @return
  64. */
  65. private FlowRule getOriginRule(String resource) {
  66. List<FlowRule> originRule = FlowRuleManager.getRules().stream().filter(flowRule -> flowRule.getResource().equals(resource)).collect(Collectors.toList());
  67. if (CollectionUtils.isEmpty(originRule)) {
  68. return null;
  69. }
  70. return originRule.get(0);
  71. }
  72. @Override
  73. public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args)
  74. throws Throwable {
  75. String resource = context.getCurEntry().getResourceWrapper().getName();
  76. List<FlowRule> rules = getRuleProvider(resource);
  77. if (rules != null) {
  78. for (FlowRule rule : rules) {
  79. //这里取到的规则都是配置阈值的80%,这里如果检查到阈值了,说明就是到了真实阈值的80%,既可以发报警给对应负责人了
  80. if (!checker.canPassCheck(rule, context, node, count, prioritized)) {
  81. FlowRule originRule = getOriginRule(resource);
  82. String originRuleCount = originRule == null ? "未知" : String.valueOf(originRule.getCount());
  83. logger.info("FlowEarlyWarning:服务{}目前的流量指标已经超过{},接近配置的流控阈值:{},", resource, rule.getCount(), originRuleCount);
  84. //TODO 报警功能自行实现
  85. break;
  86. }
  87. }
  88. }
  89. fireEntry(context, resourceWrapper, node, count, prioritized, args);
  90. }
  91. @Override
  92. public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
  93. fireExit(context, resourceWrapper, count, args);
  94. }
  95. }

DegradeEarlyWarningSlot

  1. import com.alibaba.csp.sentinel.context.Context;
  2. import com.alibaba.csp.sentinel.node.DefaultNode;
  3. import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot;
  4. import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
  5. import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
  6. import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
  7. import com.alibaba.csp.sentinel.util.AssertUtil;
  8. import com.google.common.collect.Lists;
  9. import com.qiaofang.tortoise.gateway.config.SentinelProperties;
  10. import org.slf4j.Logger;
  11. import org.slf4j.LoggerFactory;
  12. import org.springframework.beans.BeanUtils;
  13. import org.springframework.util.CollectionUtils;
  14. import java.util.List;
  15. import java.util.stream.Collectors;
  16. /**
  17. * 熔断预警slot
  18. *
  19. * @author chenhao
  20. */
  21. public class DegradeEarlyWarningSlot2 extends AbstractLinkedProcessorSlot<DefaultNode> {
  22. /**
  23. * log
  24. */
  25. private Logger logger = LoggerFactory.getLogger(this.getClass());
  26. /**
  27. * 与流控基本一致 就是取原规则的方式不一样
  28. * @param resource
  29. * @return
  30. */
  31. private List<DegradeRule> getRuleProvider(String resource) {
  32. // Flow rule map should not be null.
  33. List<DegradeRule> rules = DegradeRuleManager.getRules();
  34. List<DegradeRule> earlyWarningRuleList = Lists.newArrayList();
  35. for (DegradeRule rule : rules) {
  36. DegradeRule earlyWarningRule = new DegradeRule();
  37. BeanUtils.copyProperties(rule, earlyWarningRule);
  38. earlyWarningRule.setCount(rule.getCount() * 0.8);
  39. earlyWarningRuleList.add(earlyWarningRule);
  40. }
  41. return earlyWarningRuleList.stream().filter(rule -> resource.equals(rule.getResource())).collect(Collectors.toList());
  42. }
  43. /**
  44. * get origin rule
  45. *
  46. * @param resource
  47. * @return
  48. */
  49. private DegradeRule getOriginRule(String resource) {
  50. List<DegradeRule> originRule = DegradeRuleManager.getRules().stream().filter(rule -> rule.getResource().equals(resource)).collect(Collectors.toList());
  51. if (CollectionUtils.isEmpty(originRule)) {
  52. return null;
  53. }
  54. return originRule.get(0);
  55. }
  56. @Override
  57. public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args)
  58. throws Throwable {
  59. String resource = context.getCurEntry().getResourceWrapper().getName();
  60. List<DegradeRule> rules = getRuleProvider(resource);
  61. if (rules != null) {
  62. for (DegradeRule rule : rules) {
  63. if (!rule.passCheck(context, node, count)) {
  64. DegradeRule originRule = getOriginRule(resource);
  65. String originRuleCount = originRule == null ? "未知" : String.valueOf(originRule.getCount());
  66. logger.info("DegradeEarlyWarning:服务{}目前的熔断指标已经超过{},接近配置的熔断阈值:{},", resource, rule.getCount(), originRuleCount);
  67. break;
  68. }
  69. }
  70. }
  71. fireEntry(context, resourceWrapper, node, count, prioritized, args);
  72. }
  73. @Override
  74. public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) {
  75. fireExit(context, resourceWrapper, count, args);
  76. }

3.在 resources 文件夹下面新增 META-INF.services 文件夹,新增文件 com.alibaba.csp.sentinel.slotchain.SlotChainBuilder(文件名无所谓) 内容如下

  1. # 这里写你CustomSlotChainBuilder的完整包路径
  2. com.xxx.sentinel.CustomSlotChainBuilder

到这里基本上就可以了,用的过程中还是遇到挺多坑的,简单列举几个吧

  • 直接改 FlowRule 的 count 属性是不行的,因为底层验证规则的时候用的是 FlowRule 的 controller 属性,这个属性又是私有的,所以直接先拿到原始的配置后通过 FlowRuleUtil 重新生成
  • 调试过程中,DefaultNode 里面很多方法的值是都是 1s 内有效,从方法 A debug 到方法 B 可能值就没了,当时一脸懵逼

写在最后

本人很少写这种技术博客,所以有什么问题,或者不严谨的地方,大家可以提出来,求轻点喷我哈哈哈

PS:本文是我的一个朋友写的,大家有好的文章欢迎投稿

Sentinel Slot扩展实践-流控熔断预警实现的更多相关文章

  1. 一个名叫Sentinel-Rules-SDK的组件,使得Sentinel的流控&熔断规则的配置更加方便

    原文链接:一个名叫Sentinel-Rules-SDK的组件,使得Sentinel的流控&熔断规则的配置更加方便 1 Sentinel 是什么? 随着微服务的流行,服务和服务之间的稳定性变得越 ...

  2. 微服务架构 | 5.2 基于 Sentinel 的服务限流及熔断

    目录 前言 1. Sentinel 基础知识 1.1 Sentinel 的特性 1.2 Sentinel 的组成 1.3 Sentinel 控制台上的 9 个功能 1.4 Sentinel 工作原理 ...

  3. sentinel的四种流控规则介绍

    sentinel的四种流控规则介绍 今天的内容我们主要围绕四个点进行展开介绍. 流控模式 :关联.链路 流控效果 :Warm Up.排队等待 这四点具体是什么意思呢? 首先启动项目:cloud-ali ...

  4. zuul集成Sentinel最新的网关流控组件

    一.说明 Sentinel 网关流控支持针对不同的路由和自定义的 API 分组进行流控,支持针对请求属性(如 URL 参数,Client IP,Header 等)进行流控.Sentinel 1.6.3 ...

  5. 线上应用接入sentinel的第一个流控规则

    sentinel接入第1个应用A以及控制台,已经上线一段时间了,本周接入了第2个应用B: 因为测试同学只有几个,没有压测团队.测试平台.. 各接口能承载的最大qps不确定 ,接入的应用暂时都没有配置规 ...

  6. 微服务架构 | 5.4 Sentinel 流控、统计和熔断的源码分析

    目录 前言 1. Sentinel 的自动装配 1.2 依赖引入 1.3 SentinelWebAutoConfiguration 配置类 1.4 CommonFilter 过滤器 1.5 小结 2. ...

  7. Sentinel Dashboard(基于1.8.1)流控规则持久化到Nacos——涉及部分Sentinel Dashboard源码改造

    前言 之前虽然也一直在使用sentinel实现限流熔断功能,但却没有好好整理之前看的源码与资料,今天有时间将之前自己整理过的资料写成一篇博文,或者是是一篇关于Sentinel(基于目前最近版本1.8, ...

  8. Spring Cloud微服务Sentinel+Apollo限流、熔断实战总结

    在Spring Cloud微服务体系中,由于限流熔断组件Hystrix开源版本不在维护,因此国内不少有类似需求的公司已经将眼光转向阿里开源的Sentinel框架.而以下要介绍的正是作者最近两个月的真实 ...

  9. Sentinel 发布里程碑版本,添加集群流控功能

    自去年10月底发布GA版本后,Sentinel在近期发布了另一个里程碑版本v1.4(最新的版本号是v1.4.1),加入了开发者关注的集群流控功能. 集群流控简介 为什么要使用集群流控呢?假设我们希望给 ...

随机推荐

  1. BinarySearchTree(二叉搜索树)原理及C++代码实现

    BST是一类用途极广的数据结构.它有如下性质:设x是二叉搜索树内的一个结点.如果y是x左子树中的一个结点,那么y.key<=x.key.如果y是x右子树中的一个结点,那么y.key>=x. ...

  2. mysql自定义函数统计订单状态:GET_ORDER_STATUS()

    DELIMITER $$ USE `local_hnyz`$$ DROP FUNCTION IF EXISTS `GET_ORDER_STATUS`$$ CREATE DEFINER=`root`@` ...

  3. 修改mysql密码报错: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '

    使用这种格式报错: 格式:mysql> set password for 用户名@localhost = password('新密码'); 找到另一种方法解决: ALTER USER 'root ...

  4. ORs-2-Genome Coverage and the OR Subgenome

    Genome Coverage and the OR Subgenome 因为: 爬行类动物的的gene numbers比较大,而birds 的 gene numbers 处于(182-688) 其中 ...

  5. 初始化方法,init,构造器

    1.继承于NSObject class student: NSObject { var name : String? var age : Int = var friend : Int = init(n ...

  6. Spring Security Config : 注解 EnableWebSecurity 启用Web安全

    https://blog.csdn.net/andy_zhang2007/article/details/90023901

  7. Java中的基本运算符

    一.算术运算符运算符:对常量或者变量进行操作的符号表达式:用运算符把常量或者变量连接起来符合java语法的式子就可以称为表达式.注意:不同运算符连接的表达式体现的是不同类型的表达式. + 加法运算,字 ...

  8. servlet简单概括总结

    最近在看java web的相关内容,不管是整体还是细节,要学习的知识有很多,所以有一个好的学习体系非常重要.在阅读学习一些博客和教程中关于servlet的内容后,现将知识体系和自己的总结体会进行梳理, ...

  9. html和jsp页面中把文本框禁用,只能读不能写的方法

    方法常用有三种: 第一种,使用   onfocus="this.blur()" <input name="deptno" type="text& ...

  10. radar chart

    多变量数据 雷达图radar chart 如上图可知,雷达图的缺点是看不清,此时可采用线性变换(相差小)or对数变换(相差大)的方法使得图像展开. 但是第一幅图用于比价种类比较鲜明,而第二幅图虽然比较 ...