前言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的加載我使出了這些殺手鐗
若有收穫,點個在看,誠摯感謝