前言java
前几天公司生产环境一个服务因为流量上升触发了 Sentinel 的流控机制,而后用户反馈访问慢,定位发现是 task 定时任务致使,后面 task 优化以后发布,流量恢复正常。
这是一个再正常不过的生产问题,可能大部分同窗都经历过,经历过的大多数是解决问题以后就不了了之,致使事故还有再次发生的可能,最终对用户形成了很差的体验。因此我以为全部的生产问题都须要进行复盘,固然复盘的目的不是为了追责,而是防止下次再发生一样的错误。那咱们就简单分析一下这个问题,首先确定是业务层面的疏漏致使 task 发出不合理的大量请求,其二咱们的流控只是简单粗暴的流控,没有更好的预警措施,致使影响到用户以后咱们才知晓(即流控或熔断已经触发)。
那咱们的解决方案呢?首先确定是业务层面的预防,但这不是本文要说的重点,这里不展开讨论了。其次就是预警,就是咱们可否在快要触发流控以前知晓,而后报警到相关负责人提早介入处理,防止触发流控熔断。固然也不能彻底避免,可是总比流控或熔断触发以后在报警要好得多。
因为以前流控用的阿里的 Sentinel,因此本文介绍的具体实现是用 Sentinel 的自定义 slot 功能,这个自定义 slot 卡槽在 Sentinel 官方文档里面就一句话带过,而后加上一个 demo 代码,我在使用的过程当中也遇到过很多坑,因此分享一下结果给你们。
若是你们对 Sentinel 不是很了解,能够先去 github 先了解简单试用一下在阅读本文。github 地址:https://github.com/alibaba/Sentinel[1]
若是想熟悉自定义 slot 功能建议了解一下 Sentinel 的工做原理:https://github.com/alibaba/Sentinel/wiki/Sentinel%E5%B7%A5%E4%BD%9C%E4%B8%BB%E6%B5%81%E7%A8%8B[2]
还有源码中的 demo 对于自定义 slot 的写法:https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-slot-chain-spi[3]
具体实现node
下面介绍下 Sentinel 预警功能的相关实现,使用的前提是你的系统已经在用 Sentinel 的流控或熔断等功能。
自定义 CustomSlotChainBuilder 实现 SlotChainBuilder 接口,这里主要是把咱们自定义的 Slot 加到 SlotChain 这个链中git
import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain; import com.alibaba.csp.sentinel.slotchain.SlotChainBuilder; import com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder; import com.qiaofang.tortoise.gateway.component.ApplicationContextUtil; import com.qiaofang.tortoise.gateway.config.SentinelProperties; import org.springframework.stereotype.Component; import javax.annotation.Resource; /** * 自定义slot * * @author chenhao */ public class CustomSlotChainBuilder implements SlotChainBuilder { @Override public ProcessorSlotChain build() { ProcessorSlotChain chain = new DefaultSlotChainBuilder().build(); SentinelProperties sentinelProperties = (SentinelProperties) ApplicationContextUtil.getContext().getBean("sentinelProperties"); chain.addLast(new FlowEarlyWarningSlot(sentinelProperties)); chain.addLast(new DegradeEarlyWarningSlot(sentinelProperties)); return chain; } }
2.自定义 FlowEarlyWarningSlot、DegradeEarlyWarningSlot 流控熔断 2 个预警 slot
自定义 FlowEarlyWarningSlotgithub
import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleChecker; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleUtil; import com.alibaba.csp.sentinel.util.AssertUtil; import com.google.common.collect.Lists; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import org.springframework.util.CollectionUtils; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * 流控预警slot * * @author chenhao */ public class FlowEarlyWarningSlot2 extends AbstractLinkedProcessorSlot<DefaultNode> { /** * log */ private Logger logger = LoggerFactory.getLogger(this.getClass()); private final FlowRuleChecker checker; public FlowEarlyWarningSlot2() { this(new FlowRuleChecker()); } /** * Package-private for test. * * @param checker flow rule checker * @since 1.6.1 */ FlowEarlyWarningSlot2(FlowRuleChecker checker) { AssertUtil.notNull(checker, "flow checker should not be null"); this.checker = checker; } private List<FlowRule> getRuleProvider(String resource) { // Flow rule map should not be null. List<FlowRule> rules = FlowRuleManager.getRules(); List<FlowRule> earlyWarningRuleList = Lists.newArrayList(); for (FlowRule rule : rules) { FlowRule earlyWarningRule = new FlowRule(); BeanUtils.copyProperties(rule, earlyWarningRule); /** * 这里是至关于把规则阈值改为原来的80%,达到提早预警的效果, * 这里建议把0.8作成配置 */ earlyWarningRule.setCount(rule.getCount() * 0.8); earlyWarningRuleList.add(earlyWarningRule); } Map<String, List<FlowRule>> flowRules = FlowRuleUtil.buildFlowRuleMap(earlyWarningRuleList); return flowRules.get(resource); } /** * get origin rule * * @param resource * @return */ private FlowRule getOriginRule(String resource) { List<FlowRule> originRule = FlowRuleManager.getRules().stream().filter(flowRule -> flowRule.getResource().equals(resource)).collect(Collectors.toList()); if (CollectionUtils.isEmpty(originRule)) { return null; } return originRule.get(0); } @Override public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable { String resource = context.getCurEntry().getResourceWrapper().getName(); List<FlowRule> rules = getRuleProvider(resource); if (rules != null) { for (FlowRule rule : rules) { //这里取到的规则都是配置阈值的80%,这里若是检查到阈值了,说明就是到了真实阈值的80%,既能够发报警给对应负责人了 if (!checker.canPassCheck(rule, context, node, count, prioritized)) { FlowRule originRule = getOriginRule(resource); String originRuleCount = originRule == null ? "未知" : String.valueOf(originRule.getCount()); logger.info("FlowEarlyWarning:服务{}目前的流量指标已经超过{},接近配置的流控阈值:{},", resource, rule.getCount(), originRuleCount); //TODO 报警功能自行实现 break; } } } fireEntry(context, resourceWrapper, node, count, prioritized, args); } @Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { fireExit(context, resourceWrapper, count, args); } }
DegradeEarlyWarningSlotspring
import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.util.AssertUtil; import com.google.common.collect.Lists; import com.qiaofang.tortoise.gateway.config.SentinelProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.BeanUtils; import org.springframework.util.CollectionUtils; import java.util.List; import java.util.stream.Collectors; /** * 熔断预警slot * * @author chenhao */ public class DegradeEarlyWarningSlot2 extends AbstractLinkedProcessorSlot<DefaultNode> { /** * log */ private Logger logger = LoggerFactory.getLogger(this.getClass()); /** * 与流控基本一致 就是取原规则的方式不同 * @param resource * @return */ private List<DegradeRule> getRuleProvider(String resource) { // Flow rule map should not be null. List<DegradeRule> rules = DegradeRuleManager.getRules(); List<DegradeRule> earlyWarningRuleList = Lists.newArrayList(); for (DegradeRule rule : rules) { DegradeRule earlyWarningRule = new DegradeRule(); BeanUtils.copyProperties(rule, earlyWarningRule); earlyWarningRule.setCount(rule.getCount() * 0.8); earlyWarningRuleList.add(earlyWarningRule); } return earlyWarningRuleList.stream().filter(rule -> resource.equals(rule.getResource())).collect(Collectors.toList()); } /** * get origin rule * * @param resource * @return */ private DegradeRule getOriginRule(String resource) { List<DegradeRule> originRule = DegradeRuleManager.getRules().stream().filter(rule -> rule.getResource().equals(resource)).collect(Collectors.toList()); if (CollectionUtils.isEmpty(originRule)) { return null; } return originRule.get(0); } @Override public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) throws Throwable { String resource = context.getCurEntry().getResourceWrapper().getName(); List<DegradeRule> rules = getRuleProvider(resource); if (rules != null) { for (DegradeRule rule : rules) { if (!rule.passCheck(context, node, count)) { DegradeRule originRule = getOriginRule(resource); String originRuleCount = originRule == null ? "未知" : String.valueOf(originRule.getCount()); logger.info("DegradeEarlyWarning:服务{}目前的熔断指标已经超过{},接近配置的熔断阈值:{},", resource, rule.getCount(), originRuleCount); break; } } } fireEntry(context, resourceWrapper, node, count, prioritized, args); } @Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { fireExit(context, resourceWrapper, count, args); } }
3.在 resources 文件夹下面新增 META-INF.services 文件夹,新增文件 com.alibaba.csp.sentinel.slotchain.SlotChainBuilder(文件名无所谓) 内容以下缓存
# 这里写你CustomSlotChainBuilder的完整包路径 com.xxx.sentinel.CustomSlotChainBuilder
到这里基本上就能够了,用的过程当中仍是遇到挺多坑的,简单列举几个吧app
本人不多写这种技术博客,因此有什么问题,或者不严谨的地方,你们能够提出来,求轻点喷我哈哈哈
参考资料ide
[1]
https://github.com/alibaba/Sentinel: https://github.com/alibaba/Sentinel
[2]
https://github.com/alibaba/Sentinel/wiki/Sentinel%E5%B7%A5%E4%BD%9C%E4%B8%BB%E6%B5%81%E7%A8%8B: https://github.com/alibaba/Sentinel/wiki/Sentinel%E5%B7%A5%E4%BD%9C%E4%B8%BB%E6%B5%81%E7%A8%8B
[3]
https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-slot-chain-spi: https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-slot-chain-spi优化
PS:本文是个人一个朋友写的,你们有好的文章欢迎投稿ui
热文推荐
得亏了它,我才把潜藏那么深的Bug挖出来
惊讶!缓存刚Put再Get竟然获取不到?
好机会,我要帮女同事解决Maven冲突问题
上线前一个小时,dubbo这个问题可把我折腾惨了
为了控制Bean的加载我使出了这些杀手锏
若有收获,点个在看,诚挚感谢