Soul网关学习Sentinel插件原理解析

概述

在业务网关中熔断和流量控制都是很是必要的功能。soul在实现这部分功能时使用了不一样的成熟组件,用户能够根据本身的喜爱选择。本文将介绍如何在soul中使用阿里的Sentinel组件实现熔断及流控功能。本文首先会介绍熔断和流控的场景及意义。而后介绍如何在soul上配置使用sentinel插件作流控和熔断。最后从源码的层面简略分析soul是如何使用Sentinel组件的。nginx

熔断和流量控制

场景描述

业务网关做为流量的入口,有保护后继服务的职责。如下两个对服务有严重危害的场景在生产中常常会遇到,也是业务网关必需要关注处理的问题。一种状况是在好比双11或双12这些大型促销时,接口的请求量是平时是数倍,若是没有评估好容量,这种激增的请求很容易致使整个服务彻底不可用。这种宕机每每不是由于业务逻辑的漏洞而是由于请求过多资源不够致使的。另外一种状况是在整个服务体系中有一些核心服务,多个业务流程都依赖该服务。然而是服务都有出现处理不稳定或者服务损坏的状况,致使请求处理时间长或者总是频繁抛出异常。排除业务BUG的状况,可能就是突发的很是随机的阻塞,通常减缓请求量就会自动修复,可是若是不加保护就有出现多米诺效应致使整个服务不可用。此场景和第一种场景有略微不一样,第一种场景是实际流量确实出现了不可处理的峰值,而第二种场景主要考虑的是服务自己出现了不可避免、不可预测的抖动而引起的连锁反应。后端

流量控制

针对第一种场景咱们一般的作法是进行流量控制,核心思路是业务网关保证打到后面的请求是业务能够承受的量,多余的请求直接拒绝或者加入等待队列,保证服务不会宕掉,大部分请求仍是能够正常处理。在考虑流量控制的策略时,咱们应该主要思考如下几个问题:markdown

  1. 经过什么角度控制流量?
  2. 阈值是多少?
  3. 流量控制的策略是什么?

对于第一个问题,正常思路是经过QPS来监控流量,即每秒钟请求的数量超过某限额时进行流控。但其实还有一种思路是从并发数来监控流量。这种控制场景也是很是有意义的,例如当下游应用因为某种缘由致使服务不稳定、响应延迟增长,对于网关来讲,意味着吞吐量降低和更多的线程数占用,极端状况下甚至致使线程池耗尽。从某种意义上讲经过并发进行流控能够必定程度上保护网关服务自己。对于第二个问题阈值来讲比较好理解,就是触发流控的边界,若是从QPS来考虑就是每秒达到多少时开始流控,从并发数来考量的话就是请求上下文的线程数目超过多少进行流控。对于第三个问题,咱们通常有如下3中处理方案:架构

  1. 直接拒绝,这种策略很是好理解就是当QPS高于阈值时直接拒接服务,不把请求传输到后面的服务中。
  2. 预热启动,这个策略所针对的场景是系统长期处于低水位的状况下,可能出现流量忽然增长时,而直接把系统拉升到高水位可能瞬间把系统压垮。预热启动的方式是让阈值缓慢增长,在必定时间内逐渐增长阈值直至达到设置,给冷系统一个预热的时间,避免冷系统被压垮。对于超出阈值的请求也是触发拒绝。
  3. 匀速排队,此策略核心思路是以固定间隔时间让请求经过。当请求到来的时候,若是当前请求距离上个经过的请求经过的时间间隔不小于预设值,则让当前请求经过;不然,计算当前请求的预期经过时间,若是该请求的预期经过时间小于规则预设的 timeout 时间,则该请求会等待直到预设时间到来经过(排队等待处理);若预期的经过时间超出最大排队时长,则直接拒接这个请求。

熔断

针对第二种场景一般的处理方式是设置服务熔断。简单的说就是当咱们探测的一个服务出现了异常,则再也不访问它以避免更多的请求对它形成更大的压力。一段时间后若是探测到服务恢复了再将流量发送过去。咱们首先须要判断出这个服务是否出现了不稳定\抖动的状况。而后思考若是发现了抖动的服务咱们应该怎么办。如何判断服务是否恢复正常了。对于服务是否不稳定这一点咱们通常能够经过一下3个方式进行判断。并发

  1. 慢调用比例:当单位统计时长内请求数目大于设置的最小请求数目,而且超过最大忍受时间的请求大于阈值,则判断服务异常,触发熔断;
  2. 异常比例:当单位统计时长内异常请求的比例大于阈值则咱们断定服务异常,触发熔断;
  3. 异常数:当单位时长内出现异常的请求的数量的达到阈值则断定服务异常,触发熔断;

当咱们经过以上3个指标判断服务为异常并熔断服务后,对于必定时间内(熔断时长内)的请求咱们能够选择直接报错,不阻塞上游服务,让请求方来自行决定如何处理。或者直接触发服务降级。服务降级粗略的能够理解为请求此业务的简版,该简版省掉了不少非核心流程,而且只是最终保证流程处理完(最终一致性)。和现实中的熔断同样服务熔断是会自动恢复的。通常是触发熔断后的一段时间内服务处于熔断状态不提供服务,而后进入半开状态,若接下来的少许请求没有报错且响应时间合理则服务恢复,若是仍是异常则继续熔断。分布式

soul中的Sentinel插件

Sentinel是阿里开源的面向分布式服务架构的流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统自适应保护等多个维度来帮助您保障微服务的稳定性。Soul做为国内优秀的开源网关,将Sentinel整合为插件融入了本身的体系中,使用户经过简单的配置就可使用Sentinel提供的流量控制和服务熔断功能。下面将简要介绍在soul中如何配置使用sentinel插件。ide

首先登录soul管理平台在”插件列表” –> “sentinel”中配置插件。其中”选择器”的配置不是本文的重点再也不介绍,点击”增长规则”来进行具体设置以下图。微服务

在这个配置页面中”名称”、”匹配方式”、”条件”、”日志打印”、”是否开启”、”执行顺序”属于soul插件的常规配置这里也再也不赘述。咱们重点须要关注的是”处理”中的配置项。这些配置项主要能够分为2组,前4个选项是关于熔断的配置,后4个选项是关于流量控制的配置。在soul中咱们能够针对某一组请求同时设置它的流量控制和熔断策略。下面来重点分析下各个配置项如何使用。this

熔断

首先来看熔断相关的配置,它有四个配置项”熔断阈值”、”是否开启熔断”、”熔断窗口大小”以及没有注名字的是服务异常判断方式。熔断开关表示是否开启熔断(1开\0不开)。熔断窗口大小指的是触发熔断后通过多少秒后进入半开状态,在半开状态若是请求正常则会进入正常状态若是请求依然不正常则继续熔断。熔断断定方式和熔断阈值须要结合来看。soul中使用了sentinel的3种服务异常断定方式。分别是:spa

  1. 慢调用比例,在此模式下阈值指的是断定为慢调用的毫秒数。慢调用的比例默认是1不能更改即单位统计时长内所有超过阈值则触发熔断。该模式是sentinel的默认模式。
  2. 异常比例,在此模式下阈值指的是单位统计时长内异常请求的比例上限,须要填写1个[0.0, 1.0]的数,表示0%-100%
  3. 异常数策略,在该模式下阈值指的是单位统计时间内异常请求个数的上限。

须要注意的是soul对于单位统计时长(statIntervalMs)和熔断最小请求数(minRequestAmount)使用的是sentinel的默认参数。分别是1秒钟和5次。单位时长指定的是异常判断以是1秒钟为统计范围,下一秒从新开始计数。最小请求数指的是1秒钟内若是请求的次数少于5那么即便达到阈值也不会触发熔断。

如上图配置表示的意思是,开启熔断配置,若是此服务在1秒钟内有5个请求都出现了异常那么则熔断10秒,10秒后进入如半开状态,若是请求都正常则变为正常状态,若是还不正常则继续熔断。熔断期间若是请求该服务则soul网关会直接返回请求错误,保护后端服务不会再接到请求。

流量控制

流量控制的相关配置有5个,从上到下从左到右分别是”流控效果”,”限流阈值”,”流控开关”,”限流阈值类型”。首先是限流类型,咱们能够选择”QPS”或”并发线程数”,这个参数规定了咱们从哪一个角度来设置限流的阈值。阈值则是QPS的上限或者是线程数量,达到此阈值则会启动限流策略。具体的限流策略在”流控效果”中配置,流控策略里咱们能够选择”直接拒绝”、”warm up(预热)”、”匀速排队”、”预热+匀速排队”。直接拒绝比较好理解,就是QPS或线程数达到阈值后,多余的请求直接报错返回。预热指的是在10秒钟内阈值逐步增加到指定阈值,即头2-3秒的阈值是低于设置阈值的,但阈值是逐步增加的,10秒后达到指定阈值,这样可使系统有个预热过程。超过阈值的请求soul网关会直接报错返回。匀速队列这种模式会严格控制每一个请求的时间间隔,若是流控类型是QPS阈值是10,那么soul会控制每100ms将1个请求传导到后端服务上。多余的请求首先会进入等待队列,每一个请求最多等待500ms,若是请求预计等待时间超过500ms则直接报错返回。须要注意的是若是限流类型是并发线程数,那么流控效果只能是”直接拒绝”。以下图所示该配置表示的是soul网关会保证该服务的QPS不超过10,多余的请求将会直接报错。

须要注意的是Sentinel组件独立运行于soul的每一个网关中,若是网关是集群,那么在作流控时,实际传到后面服务中的量是须要乘上soul网关服务的数量的。即若是咱们的soul网关部署了3个节点,经过nginx将全部请求平均负载到了每一个节点上。对应1个接口咱们配置的流控是10 qps,那么实际后向服务须要处理的QPS是10*3。熔断一样须要考虑这种状况,只有3个节点上某个服务都触发熔断时,那么该服务才不会再收到任何请求。

Sentinel插件源码阅读

soul中Sentinel插件的源码主要有3块,”SentinelRuleHandle”负责处理当有Sentinel规则从管理节点同步过来时的处理逻辑,”SentinelPlugin”插件的处理逻辑,”SentinelFallbackHandler”对于触发了流控或熔断的处理逻辑。下面我一个个来看一下。首先是”SentinelRuleHandle”,源码以下:

public class SentinelRuleHandle implements PluginDataHandler {
    
    @Override
    public void handlerRule(final RuleData ruleData) {
        // 处理新的sentinel配置
        SentinelHandle sentinelHandle = GsonUtils.getInstance().fromJson(ruleData.getHandle(), SentinelHandle.class);
        sentinelHandle.checkData(sentinelHandle);
        // 获取全部现有流控配置,删除与新配置同resourceName的配置
        List<FlowRule> flowRules = FlowRuleManager.getRules()
                .stream()
                .filter(r -> !r.getResource().equals(getResourceName(ruleData)))
                .collect(Collectors.toList());
        if (sentinelHandle.getFlowRuleEnable() == Constants.SENTINEL_ENABLE_FLOW_RULE) {
            // 若是开启了流控
            // 根据配置设置sentinel流控规则
            FlowRule rule = new FlowRule(getResourceName(ruleData));
            // 配置阈值
            rule.setCount(sentinelHandle.getFlowRuleCount());
            // 流控方式 QPS or 线程
            rule.setGrade(sentinelHandle.getFlowRuleGrade());
            // 流控行为: 0. default(reject directly), 1. warm up, 2. rate limiter, 3. warm up + rate limiter
            rule.setControlBehavior(sentinelHandle.getFlowRuleControlBehavior());
            flowRules.add(rule);
        }
        // 更新所有流控配置
        FlowRuleManager.loadRules(flowRules);
        // 获取全部现有熔断配置,删除与新配置同resourceName的配置
        List<DegradeRule> degradeRules = DegradeRuleManager.getRules()
                .stream()
                .filter(r -> !r.getResource().equals(getResourceName(ruleData)))
                .collect(Collectors.toList());
        if (sentinelHandle.getDegradeRuleEnable() == Constants.SENTINEL_ENABLE_DEGRADE_RULE) {
            // 若是开启了流控
            // 根据配置设置sentinel熔断规则
            DegradeRule rule = new DegradeRule(getResourceName(ruleData));
            // 熔断阈值
            rule.setCount(sentinelHandle.getDegradeRuleCount());
            // 熔断判断的依据 0: average RT, 1: exception ratio, 2: exception count
            rule.setGrade(sentinelHandle.getDegradeRuleGrade());
            // 熔断时间窗口
            rule.setTimeWindow(sentinelHandle.getDegradeRuleTimeWindow());
            degradeRules.add(rule);
        }
        // 更新所有熔断配置
        DegradeRuleManager.loadRules(degradeRules);
    }
    
    @Override
    public void removeRule(final RuleData ruleData) {
        // 删除指定规则
        FlowRuleManager.loadRules(FlowRuleManager.getRules()
                .stream()
                .filter(r -> !r.getResource().equals(getResourceName(ruleData)))
                .collect(Collectors.toList()));
        DegradeRuleManager.loadRules(DegradeRuleManager.getRules()
                .stream()
                .filter(r -> !r.getResource().equals(getResourceName(ruleData)))
                .collect(Collectors.toList()));
    }
    
    @Override
    public String pluginNamed() {
        return PluginEnum.SENTINEL.getName();
    }
    
    /**
     * return sentinel resource name.
     *
     * @param ruleData ruleData
     * @return string string
     */
    public static String getResourceName(final RuleData ruleData) {
        return ruleData.getSelectorId() + "_" + ruleData.getName();
    }
    
}

复制代码

插件执行逻辑代码”SentinelPlugin”以下

public class SentinelPlugin extends AbstractSoulPlugin {
    // 异常处理的handler
    private final SentinelFallbackHandler sentinelFallbackHandler;
    
    public SentinelPlugin(final SentinelFallbackHandler sentinelFallbackHandler) {
        this.sentinelFallbackHandler = sentinelFallbackHandler;
    }
    
    @Override
    protected Mono<Void> doExecute(final ServerWebExchange exchange, final SoulPluginChain chain, final SelectorData selector, final RuleData rule) {
        final SoulContext soulContext = exchange.getAttribute(Constants.CONTEXT);
        assert soulContext != null;
        // 从插件配置中生成sentinel使用的资源名称,该名称对应1个流控或熔断策略
        String resourceName = SentinelRuleHandle.getResourceName(rule);
        // 验证sentinel插件的配置信息
        SentinelHandle sentinelHandle = GsonUtils.getInstance().fromJson(rule.getHandle(), SentinelHandle.class);
        sentinelHandle.checkData(sentinelHandle);
        // 引入sentinel官方的Transformer,将请求交给sentinel处理
        return chain.execute(exchange).transform(new SentinelReactorTransformer<>(resourceName))
                .doOnSuccess(v -> {
                    HttpStatus status = exchange.getResponse().getStatusCode();
                    if (status == null || !status.is2xxSuccessful()) {
                        exchange.getResponse().setStatusCode(null);
                        throw new SentinelFallbackException(status == null ? HttpStatus.INTERNAL_SERVER_ERROR : status);
                    }
                })
                //sentinel 触发了流控或熔断而报错调用sentinelFallbackHandler返回错误信息
                .onErrorResume(throwable -> sentinelFallbackHandler.fallback(exchange, UriUtils.createUri(sentinelHandle.getFallbackUri()), throwable));
    }
    // 插件名sentinel
    @Override
    public String named() {
        return PluginEnum.SENTINEL.getName();
    }
    // 顺序 45 
    @Override
    public int getOrder() {
        return PluginEnum.SENTINEL.getCode();
    }
    
    public static class SentinelFallbackException extends HttpStatusCodeException {
        public SentinelFallbackException(final HttpStatus statusCode) {
            super(statusCode);
        }
    }
}

复制代码

异常处理”SentinelFallbackHandler”,在soul中不论是熔断后请求的处理仍是被流控的请求,都是有soul直接返回报错

public class SentinelFallbackHandler implements FallbackHandler {
    
    @Override
    public Mono<Void> generateError(final ServerWebExchange exchange, final Throwable throwable) {
        Object error;
        
        if (throwable instanceof DegradeException) {
            // 触发熔断
            // http status 设为500
            exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
            // request body 设置
            error = SoulResultWrap.error(SoulResultEnum.SERVICE_RESULT_ERROR.getCode(), SoulResultEnum.SERVICE_RESULT_ERROR.getMsg(), null);
        } else if (throwable instanceof FlowException) {
            // 流控报错 该错提示客户端再次尝试
            //  http status 设为429
            exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
            // request body 设置
            error = SoulResultWrap.error(SoulResultEnum.TOO_MANY_REQUESTS.getCode(), SoulResultEnum.TOO_MANY_REQUESTS.getMsg(), null);
        } else if (throwable instanceof BlockException) {
            // FlowException的父类 该错提示服务已阻塞
            // http status 设为429
            exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
            // request body 设置
            error = SoulResultWrap.error(SoulResultEnum.SENTINEL_BLOCK_ERROR.getCode(), SoulResultEnum.SENTINEL_BLOCK_ERROR.getMsg(), null);
        } else {
            return Mono.error(throwable);
        }
        return WebFluxResultUtils.result(exchange, error);
    }
}

复制代码

总结

soul网关封装了优秀的流控组件——sentinel,为用户提供了好用的流量控制和熔断功能。须要注意的是soul在使用sentinel时部分参数是默认配置,若是有修改的需求则须要自行调整源码。其次soul网关能够分布式部署,可是使用sentinel时并无用分布式流控,每一个soul网关节点对于同一个资源的流控是独立但相同的。

相关文章
相关标签/搜索