分布式限流Sentinel

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了分布式限流Sentinel相关的知识,希望对你有一定的参考价值。

参考技术A

众所周知,互联网电商的各类活动是越来越多,例如削减男同胞钱包厚度的双十一、618、双十二、各类秒杀活动等,几乎所有的互联网电商企业都会参与其中,冲击GMV,会电商平台带来巨大的流量与可观的利润。

作为互联网电商中的一员,我自己所属的公司虽然远比不上淘宝、京东等,但作为社交电商领域的领头羊,我们在上述对于电商企业及其特殊的日子,流量也是不容小觑的。

好了,让我们进入这期的主题。例如在双十一、或者周年庆等这种特殊的日子,当12点刚到那一刻,巨大的用户流量涌入你们的系统,访问量突然剧增时,我们是如何保证系统的可用性、稳定性。我们的解决方案主要是通过Sentinel的限流、降级、熔断(增加服务器数量就不说了)以及消息中间件的削峰(我会专门写一期关于消息中间件的文章,到时候大家可以看看)。没错,本期的主角出现了,他就是 Sentinel ,阿里开源的面向分布式服务框架的轻量级流量控制框架。官网如下: https://github.com/alibaba/Sentinel

以下是另一个开源的流量控制框架 hystrix 与Sentinel的对比

分布式系统中,限流的资源可以是一个http接口,也可使是某个分布式应用中的API;一般我们针对C端的http接口进行限流,针对API进行熔断降级。

限制请求的数量,限制某段时间内的请求总量对于超出的总量的请求,可以直接拒绝,也可以在请求的时候对请求分组,允许特殊请求进来,剩下的拒绝,也可以放入消息队列,削峰填谷。
限流的实现方式:

服务降级是从整个系统的负荷情况出发和考虑的,对某些负荷会比较高的情况,为了预防某些功能(业务场景)出现负荷过载或者响应慢的情况,在其内部暂时舍弃对一些非核心的接口和数据的请求,而直接返回一个提前准备好的fallback(退路)错误处理信息。这样,虽然提供的是一个有损的服务,但却保证了整个系统的稳定性和可用性。例如:当双11活动时,把无关交易的服务统统降级,如查看历史订单、工单等等。

在微服务架构中,微服务是完成一个单一的业务功能,这样做的好处是可以做到解耦,每个微服务可以独立演进。但是,一个应用可能会有多个微服务组成,微服务之间的数据交互通过远程过程调用完成。这就带来一个问题,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务。如果调用链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“ 雪崩效应 ”。
熔断机制是应对雪崩效应的一种微服务链路保护机制 。服务熔断的作用类似于我们家用的保险丝,当某服务出现不可用或响应超时的情况时,为了防止整个系统出现雪崩,暂时停止对该服务的调用。熔段解决如下几个问题:

本源码解析以限流为例,降级具体实现可自行参考源码 Sentinel采用滑动窗口算法来实现限流的。限流的直接表现是在执行 Entry nodeA = SphU.entry(资源名字) 的时候抛出 FlowException 异常。FlowException 是BlockException 的子类,您可以捕捉 BlockException 来自定义被限流之后的处理逻辑。

由上可知,会先初始化一个限流规则,initFlowRule方法中将创建一个限流规则FlowRule对象,主要限流参数如下

并设置其相应的限流规则属性,最后通过FlowRuleManager.loadRules(rules)加载限流规则。

限流规则初始化之后,通过entry= SphU.entry(resource)触发内部初始化。
从 SphU.entry() 方法往下执行会进入到 Sph.entry() ,Sph的默认实现类是 CtSph,而最终会进入CtSph 的entry 方法

通过我们给定的资源去封装了一个 StringResourceWrapper ,然后传入自己的重载方法,继而调用 entryWithPriority(resourceWrapper, count, false, args):

由上述方法可知,主要是为了获取该资源对应的资源处理链,让我们来看下slotChain是如何获取的

当Map缓存中不存在ProcessorSlotChain实例,则具体通过 SlotChainProvider 去构造处理链

继续让我们来看下slotChainBuilder的build方法中做了些什么

我们可以看出上述底层源码是一个标准的责任链设计模式,通过查看ProcessorSlot的具体实现类,我们可以知道该责任链中的具体节点如图所示

执行对应的这些节点,具有有不同的职责,例如:

下图所示是各个slot对应的entry方法的具体实现

我们以StatisticSlot为例,来看看这些具体实现类内部的逻辑是怎样的。

请求通过了sentinel的流控等规则,再通过node.addPassRequest() 将当次请求记录下来

addPassRequest方法如下

addPass方法如下

WindowWrap主要属性如下

我们再看看获取当前窗口的方法 data.currentWindow()

我们再回到

获取到窗口以后通过 wrap.value().addPass(count)增加统计的 QPS。而这里的 wrap.value() 得到的是之前提到的 MetricBucket ,在 Sentinel 中QPS相关数据的统计结果是维护在这个类的 LongAdder[] 中,最终由这个指标来与我们实现设置好的规则进行匹配,查看是否限流,也就是 StatisticSlot的entry 方法中的。在执行StatisticSlot的entry前都要先进入到FlowSlot的entry方法进行限流过滤:

让我们进入checkFlow的内部

再此处我们拿到了设置的 FlowRule ,循环匹配资源进行限流过滤。这就是Sentinel 能做到限流的原因。

我们可以通过Sentinel的客户端查看接入了sentinel的各个系统。可针对系统中的各个资源设置相应的限流规则,如QPS或者线程数;或者设置相应的降级规则,如平均RT,异常比例以及异常数。

sentinel限流二开(2)—可插拔的分布式存储

参考技术A

不同的物理存储中心,例如consul、nacos、redis、mongo都是no-sql存储,即存储k-v形式的数据。甚至mysql中也可以存储json格式的数据。虽然有各种各样形式的client实现。但若是想嵌入到咱sentinel作为存储中心,必须满足咱定义的“接口”规范。通过“组合”的形式将存储中心实际client嵌入到接口子类中,对外提供服务。

接口如下:

没错,只需要简单的实现put、get、remove等方法,就可以无痛的替换存储中心。

配置文件中修改: sentinel.dashboard.store 属性完成动态切换。

当value是map类型时,就出现了一个问题:

接口关系如下所示: 实现如下接口满足map的扁平化操作。

存储实体的基本操作

map扁平化接口:

规则的分布式存储实现类——可以存储allRules规则以及appRules规则:

对应FastJson来说,在反序列中传入type是可以得到泛型对象的。

但实际上得到的却是JsonObject对象。无法转换为 RuleEntity 对象。

使用Jackson也遇见了此种情况:

自定义Jackson的ObjectMapper对象,在序列化的时候将属性的类型也序列化即可:

使用的锁机制,实现了consul分布式锁以及默认的内存锁。保证“扁平化”操作时的线程安全性。

借助ConcurrentHashMap将ReentrantLock存储起来。
——“这么骚的操作是在seata源码中借鉴的。”

在1.3小节的“动态切换”中,可以根据配置文件来动态切换锁。当然以后去除consul使用redis等存储中心,可以实现定义的Lock接口,实现Redis的分布式锁。

以上是关于分布式限流Sentinel的主要内容,如果未能解决你的问题,请参考以下文章

Sentinel:服务限流

阿里开源分布式限流框架 -Sentinel Go 0.3.0 发布,支持熔断降级能力

阿里开源分布式限流框架 - Sentinel Go 0.3.0 发布,支持熔断降级能力

阿里开源分布式限流框架 -Sentinel Go 0.3.0 发布,支持熔断降级能力

阿里开源分布式限流框架 -Sentinel Go 0.3.0 发布,支持熔断降级能力

阿里开源分布式限流框架 - Sentinel Go 0.3.0 发布,支持熔断降级能力