Alibaba Sentinel熔断降级/限流框架不完全解析

关于Alibaba Sentinel熔断降级/限流框架 阿里在集群防御、熔断限流方向所做可圈可点。 其中 github: Sentinel 在其中扮演着很重要的角色。 相对于类似的框架如 Hystrix(多得多的文档), 更加符合中国互联网工程师的思维习惯。

接下来的内容如果觉得啰嗦可以直接跳到Sentinel架构介绍小节

类似的防御体系:

美团的github: OCTO更像是集全公司的力量, 由上而下的要求全公司接入的、集中治理的平台。 滴滴对熔断、限流上更多的每个独立的团队自行发展, 各成体系。 以及其他企业的预案方案,想要轻松接入都不容易。 想要最低成本的接入熔断、限流,Sentinel更合适。

本文将持续迭代。

这里有对熔断、限流的包含源码解析的详解: Alibaba Sentinel 限流、熔断实现详解

一段话的介绍 预案

对可预期的问题, 例如 依赖的某服务不可用、网络可能不稳定、DB服务器不稳定、下星期要进行大规模抢购、可能有人恶意爬取 等这些不符合当前业务运行规则的。 设计一个处理方案, 来规避这些问题。

一般处理方案(简单列举):

两地三中心 服务器紧急扩容(参考微博) 代码中添加if else语句切换请求来源 ··· 通过建立一些针对可预期的问题的处理方案, 最低限度的保证自己系统的主体服务可用性。

限流

通过压测测试出当前系统的最大负载能力,然后配置超过这个负载能力的请求全部丢弃、或者排队等待。 在长链路调用中,以最低承受单点为基准, 超过这个单点的负载能力的请求全部丢弃、或者排队等待。

通过限制调用方对自己的调用,起到保护自己系统的效果。

熔断 在Sentinel中, 熔断往往跟降级靠在一起(我更加认为降级本就是预案的一部分,限流是预案、熔断也是预案,熔断却不一定都是降级)。

所有的对外部的调用都认为是不可控的。 其中部分调用是必须依赖的, 部分调用是可以被替代的(如查缓存改查DB)。

当我们对外部的请求发生异常, 例如超时、抛出异常, 且超过一定的阈值。这个时候我们可以理解为外部依赖不可用,多次请求也只是在浪费时间。这个时候可以禁止访问外部、或者直接返回默认值。

通过限制自己对外部系统的调用, 起到节约响应时间、维护链路稳定的作用。

Alibaba Sentinel建立的用处就是在针对服务上的熔断与限流。此Sentinel不同于Redis的Sentinel。 它提供一个客户端SDK, 通过编写熔断、限流规则, 起到熔断、限流的作用。 同时它提供一个服务端(非必须), 能够更好的监控客户端运行状态,同时免编码的直接下发规则给客户端。

我基于Sentinel做的一个对Hello World的限流(看不到文件可直接下载):

Sentinel使用方式 部署Sentinel的客户端 参考Github介绍 Sentinel新手指南 以及 如何使用

大致这么几部:

引入依赖(pom/sentinel-core.jar) 代码编写植入 定义规则(支持本地配置规则, 使用Sentinel Dashboard配置规则)。 相对来说Sentinel的接入非常轻便简单了。但是遇到每一段代码都植入Sentinel给定的代码, 也是很麻烦, 因此可以参考小节 基于Sentinel所做易用性拓展

参考官方demo:

image-20200417105804649

部署Sentinel的服务端 服务端非必须, 主要提供规则配置和下发。 对简化编码有很大的帮助。

参考Github介绍 Sentinel 控制台

服务端UI功能:

image-20200417105831568 Sentinel架构介绍 对Sentinel的介绍, 仅限于客户端(服务端并不是必须的产品)。参考源码 Sentinel 的sentinel-core Sentinel中的核心概念 resource(资源), 定义为被Sentinel保护的所有的事物。 所有的预案操作都是依据资源而定的。

sentinel-core直观代码分层:

image-20200417110845948

如上树形包结构, 重点关注这几个包:

image-20200417110925012 Sentinel Chain Sentinel 以插件的形式,将功能打包在客户端SDK中。 插件可以构成一条单链(责任链模式)。限流、熔断、日志等功能, 都被打包在链条中。

其中的每一个节点, 就是 solt 。 详见com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot。 通过next指向下一个需要处理的节点。

通常用到的solt:

image-20200417110953492 Node 看接口定义, 它就是一个存储程序运行时的节点。 com.alibaba.csp.sentinel.node.Node

image-20200417111018391 Sentinel执行流程图(我是重点,点我) 针对 Sentinel 所推荐的基础语法, 对其实现细致做解剖分析:

try {
    entry = SphU.entry("HelloWorld");
    // 资源中的逻辑.
    System.out.println("hello world");
} catch (BlockException e1) {
    System.out.println("blocked!");
} finally {
   if (entry != null) {
       entry.exit();
   }
}

进入被包围代码块 image-20200417111108541

image-20200417111132087

整体的逻辑相对简单。是对一个责任链的调用 重点在于整体数据链路的串联, 以及每个Solt的处理细则。

功能实现解析 限流功能 Sentinel的限流实现比较简单, 支持集群限流(单讲)和单机限流。实现方式委托给了包com.alibaba.csp.sentinel.slots.block.flow.controller 下的实现。 分别支持快速失败(DefaultController)、Warm up(热加载限流, WarmUpController)、排队等待(RateLimiterController)三种模式。 . 它隶属于FlowSlot插件

快速失败模式下的限流实现很简单, 通过获取ClusterNode中记录的当前每秒钟通过的请求数(clusterNode), 加上当前请求需要的请求数(一般是1), 如果大于规则的设定值, 直接抛出异常,请求丢弃。 被限流。

Warm up 下的实现类似于Guava的限流实现, 采取 令牌桶 的方式。

从配置好规则开始, 限流大小从0逐步增长至预设限流大小。 每次执行限流判断的时候, 从令牌桶中重新判断是否需要灌入令牌。 和快速失败模式一样, 如果已经消费的令牌数加上需要的令牌数多于可取令牌数。 不被限流。 排队等待 模式很显然, 一般情况都会想到漏桶算法。 参见 Github 匀速器。

排队等待采取了一种猜测等待时间的方法。 如果一个请求来了, 需要排队, 若预计等待时间过了依旧不满足限流条件, 自然放行。 而预计等待时间在限流条件下, 还会等待超时, 则自然直接拒绝掉。否则休眠之后放行。 排队等待的将支持后续排队, 被限流的程序则不再会被执行。 想看详细的介绍? 这里有包含源码解析的详解: Alibaba Sentinel 限流、熔断实现详解

熔断功能 熔断功能的实现就更简单了。 熔断提供三种熔断模式:RT(超时时间), 异常比例(异常数在所有请求数中的占比),异常数(具体阈值) . 它隶属于DegradeSlot插件 它也都是依据于 ClusterNode 提供的信息做的熔断。

RT 模式 还在写。

想看详细的介绍? 这里有包含源码解析的详解: Alibaba Sentinel 限流、熔断实现详解

为什么用时间窗口(Node计算规则实现) 这段主要描述的内容是Sentinel怎么精确的控制每一秒的。 此处 https://juejin.im/post/5c3607b5e51d4542253faec3 有一个非常详细的介绍, 欢迎参阅。 如下简单介绍:

滑动窗口 Sentinel 的时间统计, 全放在了滑动窗口中去执行。依次委托步骤为:

Node(StatisticNode) -> Metric(ArrayMetric) -> WindowWrap(BucketLeapArray) -> MetricBucket -> LongAdder 1 滑动窗口如下解释:

按秒滑动: 每秒长1000ms, 分两个窗口, 则每个窗口500ms。 按分滑动: 每分长60000ms, 分六十个窗口, 则每个窗口1000ms。 仅以每秒为例: 任意一个时间点, 一定落在一秒的前500ms, 或者落在后500ms (当面毫秒 - 当前毫秒 % 500)。 滑动窗口, 通过当前时间点,一定知道自己位于哪个窗口中(LeapArray#currentWindow(long)),起始时间点是多少。 . 由上, 所有的统计(加、减)都可以基于时间窗口。

具体case: 后面计算限流的时候, 限定每秒QPS=N。 则只需要知道前500ms加上后500ms的请求数M, 加上当前这次请求需要的请求数X(每个窗口中的LongAdder, 数据, 以空间换时间, CAS累加成本太高), 是不是大于预设值N就好了。

而每次做QPS累加, 是StatisticSlot在后续Slot计算未抛出异常之后才执行的。每次获取QPS,都需要得到当前的时间窗口(需要得到锁)

那回归正题, 为什么使用时间窗口呢, 秒级的时间窗口又为什么是2个呢?以我看来, 原因如下:

请求的到来并不是均匀的, 可能在一秒的前500ms根本没有请求。这个时候计算每秒的QPS需要由后500ms + 下一秒的前500ms。 时间窗口的数量起码得能被时间长度(1s=1000ms/1m=60000ms)整除吧。 并发环境中,时间窗口间隔越小, 当前线程位于窗口分界点的概率就越高。 并发环境中,改修当前时间点所处的窗口,是需要加锁的,窗口越多,加锁越频繁。 窗口多到一定程度,所有线程全花在计算时间窗口上去了。 客户端与服务端通讯 可靠性分析 本文对可靠性着手的点有可用性、稳定性、高能效。所有的CASE都基于单台机器承受QPS100上限、单次任务执行200ms计算(模拟我部门生产环境) 点会很多, 遇见一个提一个。

可靠性:

Sentinel能精准的限流么? 不可以(详见Sentinel高并发下限流不准) Sentinel在限流上可能错误阻断流量么? 会 (详见Sentinel线程池高并发下阻断流量) 高能效:

Sentinel是有性能损失的。 甚至在高负载环境下, 可能达到5ms(数据基于生产环境混布,同机器有吃CPU的服务) 这个小结的目的是分析出在使用Sentinel上的时候遇见的坑。 实际生产活动中, 一般的企业维持100QPS的可能性并不高。还是并发环境的可能性就更低了。 Sentinel依然是一个非常优秀的, 可以服务于生产环境的预案工具。

集群限流

基于Sentinel所做易用性拓展

版权声明:本文为CSDN博主「平菇虾饺」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。 原文链接:https://blog.csdn.net/qyp199312/java/article/details/97244694

这些信息有用吗?
Do you have any suggestions for improvement?

Thanks for your feedback!