这几天公司有需求要实现sentinel告警通知,大概的要求是这样,当某些接口请求异常次数达到某一阈值,或者qps达到某个阈值,把接口熔断。当然,我们原计划是自己写方法来实现的,但是后来还是采用了sentinel。因为作为微服务架构的主流的熔断解决方案,我们倾向于sentinel。但是看接下来的需求,我们也遇到了问题,因为我们需要对熔断的接口进行告警通知。

需求来源:针对目前日益增多的第三方接口问题导致的服务缓慢及中断,需要对所有现有第三方接口进行监测、通知、及熔断处理。需求描述:1、在后台里新增”后台接口服务“功能模块2、接口健康监测:持续对第三方提供的系统接口服务进行检测,当出现接口超时等异常情况时将进行系统预警(短信通知,可设置多个接收人手机),以便甲方协同解决问题。3、服务熔断:因第三方接口质量、性能问题导致接口访问达到熔断标准时(1分钟内连续30次以上接口访问失败或者超时),为不影响甲方用户体验,乙方有权进行服务熔断,关闭不可用接口4、第三方接口调用需要记录详细的接口日志,如果调用失败,错误信息需要有“第三方”的标识,明确显示是哪方的错误。

第一、通过修改sentinel源码,实现告警

通过修改sentinel源码的方式,当然是可行的。只是设计到的内容比较多。第一种方法的内容,可以具体参考:https://blog.csdn.net/qq_42019951/article/details/127136222

我们今天主要讲第二种方法。

如下是在Sentinel源码中主要使用了责任链设计模式,责任链中最重要的统计流量的功能便是在 StatisticSlot中实现的,看下它的源码:

@Spi(order = Constants.ORDER_STATISTIC_SLOT)
public class StatisticSlot extends AbstractLinkedProcessorSlot<DefaultNode> {

    @Override
    public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count,
                      boolean prioritized, Object... args) throws Throwable {
        try {
            //关注点1: 执行后续校验逻辑,如果触发流控熔断则会抛出异常
            fireEntry(context, resourceWrapper, node, count, prioritized, args);

            //关注点2: 对通过的QPS及线程数进行统计
            node.increaseThreadNum();
            node.addPassRequest(count);

            if (context.getCurEntry().getOriginNode() != null) {
                // Add count for origin node.
                context.getCurEntry().getOriginNode().increaseThreadNum();
                context.getCurEntry().getOriginNode().addPassRequest(count);
            }

            if (resourceWrapper.getEntryType() == EntryType.IN) {
                // Add count for global inbound entry node for global statistics.
                Constants.ENTRY_NODE.increaseThreadNum();
                Constants.ENTRY_NODE.addPassRequest(count);
            }

            // 扩展点1:成功回调
            for (ProcessorSlotEntryCallback<DefaultNode> handler : 
                 StatisticSlotCallbackRegistry.getEntryCallbacks()) {
                handler.onPass(context, resourceWrapper, node, count, args);
            }
        } catch (PriorityWaitException ex) {
            node.increaseThreadNum();
            if (context.getCurEntry().getOriginNode() != null) {
                // Add count for origin node.
                context.getCurEntry().getOriginNode().increaseThreadNum();
            }

            if (resourceWrapper.getEntryType() == EntryType.IN) {
                // Add count for global inbound entry node for global statistics.
                Constants.ENTRY_NODE.increaseThreadNum();
            }
            // Handle pass event with registered entry callback handlers.
            for (ProcessorSlotEntryCallback<DefaultNode> handler : 
                 StatisticSlotCallbackRegistry.getEntryCallbacks()) {
                handler.onPass(context, resourceWrapper, node, count, args);
            }
        } catch (BlockException e) {
            //关注点3:触发流控
            // Blocked, set block exception to current entry.
            context.getCurEntry().setBlockError(e);

            //统计流控指标
            node.increaseBlockQps(count);
            if (context.getCurEntry().getOriginNode() != null) {
                context.getCurEntry().getOriginNode().increaseBlockQps(count);
            }

            if (resourceWrapper.getEntryType() == EntryType.IN) {
                // Add count for global inbound entry node for global statistics.
                Constants.ENTRY_NODE.increaseBlockQps(count);
            }

            // 扩展点2:流控回调,所以如果增加流控告警功能,需要对此处的handler进行扩展
            for (ProcessorSlotEntryCallback<DefaultNode> handler : 
                 StatisticSlotCallbackRegistry.getEntryCallbacks()) {
                handler.onBlocked(e, context, resourceWrapper, node, count, args);
            }

            throw e;
        } catch (Throwable e) {
            // Unexpected internal error, set error to current entry.
            context.getCurEntry().setError(e);

            throw e;
        }
    }
}   

第二、我们自己写具体的告警通知

在我们调试通sentinel之后,我们简单测试几个接口,通过jmeter压测,配合具体的策略,可以实现熔断。当接口请求触发了熔断策略时,被熔断的接口会返回 “limited by sentinel”。我们需要的,不是这个,而是返回这个之前,我们就进行拦截,返回更友好的提示信息。

1、如果我们限流的接口是通过feign调用的的接口,比如我们现在的需求描述一样。这种时候怎么做比较好呢,这种时候,我么只要在熔断的fallback里写具体的警告就可以了。

2、如果我们限流的是最外层,面向用户的接口,那么我们要拦截的是sentinel抛出的异常。因为sentinel在被熔断后,会抛出BlockException异常。我们进行异常捕获,然后发送告警通知就可以了。具体发放什么通知,这个是另一个业务了。

以上就是关于sentinel告警通知的我们的实践。