应用Sentinel集群限流的,如果应用嵌入模式,在异地多活专线抖动状况下会呈现服务调用超时的状况,本文从限流概念和集群限流的实现形式登程整顿了该知识点,特地是网络抖动状况下,对服务造成影响状况进行具体阐明。

集群限流原理

Sentinel是一个系统性的高可用保障工具,提供了限流、降级、熔断等一系列的能力,基于这些能力做了语意化概念形象,这些概念对于了解实现机制特地有帮忙,所以这里也复述一下。

对于流量管制,有个一个模型:

流量管制有以下几个角度:

  • 资源的调用关系,例如资源的调用链路,资源和资源之间的关系;
  • 运行指标,例如 QPS、线程池、零碎负载等;
  • 管制的成果,例如间接限流、冷启动、排队等。

Sentinel 的设计理念是让您自由选择管制的角度,并进行灵便组合,从而达到想要的成果。

那么,集群限流只是管制限流外面的一个属性,在单机限流的根底上进行增强,对于须要准确管制qps阈值的场景特地实用。

言归正传,Sentinel的集群限流也没那么神秘,外围设计就是采纳一个中心化的Token Server来调配令牌来履行,每次申请都会通过实例的Token Client申请Token Server获取token并调用。如下图所示:

留神这里Client和Server是通过tcp长连贯的形式通信的,须要有reconnect的机制,比方这里Client连贯不上Server,会期待 n * 2000ms 的形式始终尝试连贯。

Sentinel 集群限流服务端有两种启动形式:

  • 嵌入模式(Embedded)适宜利用级别的限流,部署简略,但对利用性能有影响;
  • 独立模式(Alone)适宜全局限流,须要独立部署。

集群限流应用场景

场景一、qps量小比机器数还少

假如咱们心愿给某个用户限度调用某个 API 的总 QPS 为 50,但机器数可能很多(比方有 100 台)。这时候咱们很天然地就想到,找一个 server 来专门来统计总的调用量,其它的实例都与这台 server 通信来判断是否能够调用。

场景二、机器弹性伸缩、数目变动频繁

假如一个服务访问量呈锯齿状,开启了弹性伸缩,机器数目不通明,那么这时候通过预估单实例qps * 机器数的形式计算就会不精确。

场景三、机器配置不一样,不同实例须要依据整体qps和机器水位综合限流

这种场景比拟非凡,个别业务上不会遇到,不做展开讨论。

集群限流可用性诊断

在生产实践配置集群限流过程中,如果遇到TokenClient和TokenServer网络抖动的状况下,会呈现什么问题呢?状况一:网络问题;状况二:TokenServer外部异样;咱们先看状况一。

TokenServer通信网络问题

假如是网络问题,那么不必去关注TokenServer返回的TokenResult具体信息了,只须要看下对超时的判断逻辑了。

FlowRuleChecker

private static boolean passClusterCheck(FlowRule rule, Context context, DefaultNode node, int acquireCount,                                        boolean prioritized) {    try {        TokenService clusterService = pickClusterService();        if (clusterService == null) {            return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized);        }        long flowId = rule.getClusterConfig().getFlowId();        TokenResult result = clusterService.requestToken(flowId, acquireCount, prioritized);        return applyTokenResult(result, rule, context, node, acquireCount, prioritized);    } catch (Throwable ex) {        RecordLog.warn("[FlowRuleChecker] Request cluster token unexpected failed", ex);    }    // 如果failback不可用间接paass    return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized);}private static boolean applyTokenResult(/*@NonNull*/ TokenResult result, FlowRule rule, Context context,                                                         DefaultNode node,                                                         int acquireCount, boolean prioritized) {        switch (result.getStatus()) {            case TokenResultStatus.OK:                return true;            case TokenResultStatus.SHOULD_WAIT:                // 没有达到采样数量,期待1000/SampleCount                try {                    Thread.sleep(result.getWaitInMs());                } catch (InterruptedException e) {                    e.printStackTrace();                }                return true;            case TokenResultStatus.NO_RULE_EXISTS:            case TokenResultStatus.BAD_REQUEST:            case TokenResultStatus.FAIL:            case TokenResultStatus.TOO_MANY_REQUEST:                return fallbackToLocalOrPass(rule, context, node, acquireCount, prioritized);            case TokenResultStatus.BLOCKED:            default:                return false;        }    }private static boolean fallbackToLocalOrPass(FlowRule rule, Context context, DefaultNode node, int acquireCount,                                             boolean prioritized) {    if (rule.getClusterConfig().isFallbackToLocalWhenFail()) {        return passLocalCheck(rule, context, node, acquireCount, prioritized);    } else {        // 不是配置了降级到本地限流,就间接pass        return true;    }}

DefaultClusterTokenClient

public TokenResult requestToken(Long flowId, int acquireCount, boolean prioritized) {    if (notValidRequest(flowId, acquireCount)) {        return badRequest();    }    FlowRequestData data = new FlowRequestData().setCount(acquireCount)        .setFlowId(flowId).setPriority(prioritized);    ClusterRequest<FlowRequestData> request = new ClusterRequest<>(ClusterConstants.MSG_TYPE_FLOW, data);    try {        TokenResult result = sendTokenRequest(request);        logForResult(result);        return result;    } catch (Exception ex) {        //异常情况下会间接返回TokenResultStatus.FAIL        ClusterClientStatLogUtil.log(ex.getMessage());        return new TokenResult(TokenResultStatus.FAIL);    }}

在requestToken超时失败后,会返回TokenResultStatus.FAIL。咱们晓得默认TokenServer申请的超时工夫是20ms,那么网络出问题,此处会阻塞20ms,而后降级到单机限流规定限流,而且每次都会申请TokenServer拿token。

如果网络连接断了的状况呢?

NettyTransportClient

@Overridepublic ClusterResponse sendRequest(ClusterRequest request) throws Exception {    //如果连贯状态异样,则TokenClient端疾速返回异样    if (!isReady()) {        throw new SentinelClusterException(ClusterErrorMessages.CLIENT_NOT_READY);    }  ...}@Overridepublic boolean isReady() {    return channel != null && clientHandler != null && clientHandler.hasStarted();}

剖析能够看进去,第一种状况,如果网络异常情况,会间接返回异样,那么最外层还是会走到本机限流的逻辑,不会增加调用耗时。如果网络慢,则期待超时工夫达到后返回。

如果是第二种状况TokenServer返回不失常状态,当TokenResultStatus.SHOULD_WAIT,则期待返回的工夫,当TokenResultStatus.BLOCKED,间接返回false,被限流,当TokenResultStatus.TOO_MANY_REQUEST,也是降级到本机限流。

总结

在集群限流的时候,如果是嵌入模式TokenServer切换的霎时不会造成拜访报错,如果拜访网络超时,然而TokenClient和TokenServer网络未中断,还是会期待拜访后果,所以集群Server拜访超时工夫肯定不要设置太长,比方设置成3s,那么在网络抖动状况下,每个申请都会加上这个3s,造成大量超时。

这里给出下,如何配置集群限流超时工夫:

ClusterClientConfig clusterClientConfig = new ClusterClientConfig();clusterClientConfig.setRequestTimeout(20);ClusterClientConfigManager.applyNewConfig(clusterClientConfig);

(本文作者:朱云辉)

本文系哈啰技术团队出品,未经许可,不得进行商业性转载或者应用。非商业目标转载或应用本文内容,敬请注明“内容转载自哈啰技术团队”。