开源分析和落地方案—Sentinel 篇
作者:京东物流 刘达
一、Sentinel 是什么?
Sentinel 是从阿里技术体系内诞生并由相关社区从微服务到云原生阶段持续孵化的流量治理组件,在服务熔断限流以及秒级/分钟级监控方面提供了开箱即用的解决方案,此外作为支持云原生的重要探索,还提供了 GO 语言实现。Sentinel 目前拥有着活跃的开源社区,从 1.8.x 版本开始,通过深度参与到 SpringCloudAlibaba 套件的支持,实现与 Java 主流开发框架 SpringBoot 及 SpringCloud 的兼容,进而成为了 Java 技术栈开源熔断限流的事实标准。虽然其 2.x 版本持续难产(可能是适配 OpenSergo 规范的影响),不过好在 1.8.x 并没停止迭代,今年以来已经在 1.8.7(JDK8)的基础之上做了 1.8.8(JDK17)的升级,完成了主流 JDK 版本的支持,基本能够满足 Javaer 的开发需求。
考虑到一般部署环境以 JDK8 居多,而且 1.8.8 除了 JDK 的升级外,没有做实质的功能改造,所以本篇文章主要基于 1.8.7 版本展开,即在 JDK8 的环境下对 Sentinel 的能力进行讨论。
二、Sentinel 能带来什么?
就如前文所介绍的,Sentinel 提供了完善的熔断、限流以及秒级/分钟级监控方案。最核心的一点,就是它把应用规则的目标统一抽象成了一个概念——资源。按照官方说明,资源可以是 Java 应用程序中的任何内容,比如服务、接口方法或者一段代码。只要用 Sentinel 提供的资源创建及释放 API 包裹起来,就可以称之为一个资源。资源的抽象为 Sentinel 的使用提供了极大的灵活性,这让开发者可以把代码链路中的任何一个部分都做到流量治理规则中,当然为了提高代码的可读性以及规范性,我们还是推荐以服务入口(Http\RPC)、接口或者接口的实现方法作为最小资源单元。
同时,Sentinel 提供了丰富的控制规则:
▪流量控制(FlowRule)
区分来源、限流类型(QPS/线程数)、支持上下游链路、多种流控效果(拒绝、排队、慢启动)
▪热点参数限流(ParamFlowRule)
热点限流是对流量控制的补充,对 Top-K 的热点参数进行限流处理
▪系统负载限流(SystemRule)
可以理解为应用级的流量控制策略,包含负载、单机平均 RT,单机 QPS 等。不过,因为粒度较粗且部分指标的获取在容器环境下存在适配问题,所以社区中大部分人对这个的理解是还有待完善,成熟的案例比较少。
▪熔断降级(DegradeRule)
在熔断降级方面,则是直接对标的 Hystrix 框架,一是在实现机制上做了优化,相对 Hystrix 基于线程池的隔离控制策略,通过并发控制机制减少了线程池的创建和占用。另外,在配置上也更加简化,相信有过 Hystrix 使用经验,都会对其繁杂的注解配置项感到困惑,而当引入 Sentinel 时则无需担心这些。
如果仅仅是这些,Sentinel 的定位也仅限于一个组件包,类似 Hystrix 一样。那为何本文开篇说 Sentinel 是一套解决方案呢?因为它还做到了另外的三点,接下来继续进行说明:
第一点:包容性
Sentinel 通过对 Adapter 模块组持续迭代,目前已经兼容了主流的一系列调用协议及组件,比如 dubbo2,dubbo3,httpclient,okhttp,grpc,sofa,quarkus,webflux,webmvc 等。此外还完成了对 Java 开源网关的支持,包括 zuul1,zuul2,springcloud-gateway。并且作为 java 应用流量控制的标准框架,很多其他开源框架也在项目内部做了相关的兼容。
第二点:界面化
Sentinel 提供了方便易用的 Dashboard,支持常用的一系列操作。比如应用的注册发现,规则参数的配置、查看,接口簇点链路的自发现和树状展示,秒级的监控视图等等,虽然开源方案展示的数据以内存或者对应用节点信息的拉取为依托,但这已经提供了非常强的指导性。其对应的商业化版本 AHAS 已经在阿里云中得到广泛应用。
第三点:扩展性
虽然 core 包提供的是基于应用内存的数据存取策略,但是 sentinel 同时对控制规则数据的拉取链路做了通用的抽象,并衍生出了一系列 datasource 包。包含 apollo、consul、etcd、nacos、redis、zk 等,他们具备的共同点就是客户端能够及时的获取到配置数据变更事件,而 Sentinel 依赖的控制规则就是以配置数据的形式维护在这些中间件中,并实现对客户端的分发。
前文介绍了这么多,那引入 Sentinel 能带来什么改变呢?
我觉得,首先,限流策略更加丰富,能够更好的应对日趋复杂的限流需求;其次,对于资源的流量管理实现了统一,从单一入口维护应用的所有流量控制规则,无需从网关、JSF 等分别对应用的入口进行配置;限流的维度更加细化,从入口级下沉到了应用内资源;在熔断的支持上,除了 JSF 客户端调用外,应用内部或作为客户端对外部的任何协议调用都可以增加熔断的管理,服务的稳定性也会随之提升。
三、Sentinel 的重点实现
概念之后再来说说 Sentinel 中的关键实现。这一部分准备分三块来说明。第一是它的核心规则控制链路,第二它的 datasource 实现结构,第三 Dashboard 与应用的交互逻辑。此处主要是专注在架构实现上,像业内熟知的一些控制算法比如令牌桶等,不再赘述。
1、核心规则控制链路
为了更好的理解这条链路的结构,此处引入一下官方图
上图即 Sentinel 的默认 ProcessorSlotChain 所配置的链路结构,默认实现依托 DefaultProcessorSlotChain。
从代码上看是一个标准的链表结构,链表中装配的节点对象类型为 AbstractLinkedProcessorSlot,也就是图中这些 Slot 的父类,我们或可以称之为 Slot 模版类。通过代码的查看,会发现它声明了两个方法,entry、exit 分别作为 Slot 的入口和出口,并且在方法入参中携带了当前调用链路的上下文信息。通过官方图例能够看出,请求在进入处理链条后,按先后顺序会经过三类 Slot,前置的 NodeSelectorSlot、ClusterBuilderSlot 用于调用链路的提取,中间 StatisticSlot 是通用的滑动窗口计数功能,比如 RT、通过数、拒绝数、异常数等,后置的 ParamFlowSlot、SystemSlot 等则为真正的规则验证链条。如下图所示,
在上图中,会发现在模版类中有额外的两个方法,fireEntry 和 fireExit,这两个方法在 AbstractLinkedProcessSlot 中均有默认的实现,主要是用于在当前节点 entry 或 exit 方法执行过程中触发对 next 节点的调度,这就使得 Slot 链表触发顺序和完成顺序并不一定相同。这样的好处是在基础链路上提供了充足的灵活性,上游 Slot 可以按需触发下游 Slot 并获取执行结果。例如此处用于统计计数的 StatisticSlot 和用于规则验证的一系列 Slot 列表之前的关系,正常通过和规则拦截的调用都能被 StatisticSlot 抓取到进行统计。
2、Datasource 实现
作为配置文件的读取来源,Sentinel 提供了标准实现框架,并以此为基础,对主流的开源分布式配置中心也做了充分的适配,还是以类结构图作为切入点
可以看到,Sentinel 提供的 Datasource 规范中,通过 loadConfig,readSource,getProperty,close 四个方法,覆盖了数据源的读取、加载、查询和释放整个流程。落地到具体实现方案,它提供了两个大的方向,前者是支持热更新,后者是不可变的配置来源,如 jar 包中的文件。
热更新模式下,基于数据源的特性,又细分了两种,我把它总结为轮询模式和订阅模式。像 Eureka、本地文件这种,不支持数据变更事件的订阅,Sentinel 提供的方案是按照一定的时间间隔,定期的去拉取最新数据比对是否需要更新,这种就是前面讲的轮询模式。还有一种,依托于越来越成熟的各种开源分布式配置中间件,应用通过 SDK 集成,能够方便的订阅指定的配置项,并实时接收数据变更事件进行处理逻辑,相对于轮询模式配置生效的更加及时,是目前线上案例中最常用的模式。
对于不可变模式,Sentinel 也仅提供了一种实现,仅限于无配置变更需求的场景,支持一次性加载,好处是轻量依赖项少,但更新需要重启。
3、Dashboard 与应用的交互
核心链路和数据来源之后,再来说说 Sentinel 关于控制面的实现。
首先,要说明的是,即便是到最新的版本,Sentinel Dashboard 也不是必须的。尤其是在 1.6.x 以前,datasource 和 dashboard 这些模块都还未完善,项目引用 Sentinel 的方式主要是以硬编码为主,控制规则是借助 RuleManager 的静态方法进行初始化,调用链路中通过 SphU 或 SphO 实现资源的创建,使用 Tracer 去显式的记录异常(@SentinelResource 是在 1.8.x 才趋于完善)。现在,官方文档在大部分基础用例中也是以这些形式为主。那 Dashboard 的作用是什么?
Dashboard 为我们提供了相对完整的控制面,基本涵盖了 Sentinel 的所有功能,包含各种规则的查看和配置、节点的自动注册发现以及健康状态监控、簇点链路的采集等,在可观测方面提供了秒级的监控视图,而在后台为这些功能的数据提供了基于内存的实现,为我们实现硬编码到控制台的转变提供了明确的方向。接下来从三个角度说明 Dashboard 的重点实现,分别是节点信息的发现、规则数据的维护、秒级监控的采集。逻辑链路如下所示:
这张链路图基本把 Sentinel 从客户端到 Dashboard 服务端的交互过程做了明确的展示。
A. 首先,Sentinel 控制台对客户端的发现机制并不依赖于其他中间件,是独立的相对轻量的实现。逻辑上很简单,即在控制台暴露了一个 MachineRegistryController,客户端配置 Dashboard 地址后,定时的上报心跳数据。
B. Sentinel 对应用的 Metrics 数据采集是以定时拉取的方式,应用 SDK 会暴露 http 入口,用于提供给 Dashboard 服务侧进行调用。Dashboard 中相关的规则配置也是以类似的方式进行,额外增加了数据的写入动作用于更新规则。
C. sentinel-core 本身对 Dashboard 侧的交互没有依赖,而是通过 transport 包中以集成 Spiloader 的方式追加相关的初始化动作,实现像注册心跳、远端控制等附加能力,即 sentinel-core 仍然是独立可用的。
SlotChain、Dashboard 和 Datasource 是一套组合拳,共同构成了 Sentinel 的流控解决方案。SlotChain 完成了核心的规则控制逻辑,而 Datasource 则是解决了配置数据怎么更新怎么同步的问题,最后 Dashboard 则是将配置数据的界面化维护以及运行时数据的展示入口提供给了开发者,让线上的落地成为了可能。
四、Sentinel 源码包结构
从工程结构上看,Sentinel 以 sentinel-core 为基础,衍生了一系列的模块:
1、sentinel-dashboard:sentinel 控制台,提供简易的登录方式,功能上支持节点注册展示,限流、熔断等流控规则的查看与下发,节点 Metric 信息的采集和聚合展示等。
2、sentinel-transport:内部通信模块,实现客户端节点的心跳注册以及消息接收功能。
3、sentinel-logging:sentinel 为了和应用日志做隔离,单独指定的日志输出问题,这个模块提供了基于 slf4j 的默认实现,同时可以作为参考实现,以 SPI 的方式定制其他日志输出通道。
4、sentinel-adapter:协议适配模块,基本兼容了所有开源的协议,一是在资源生成时指定 prefix,另外就是依赖调用链路中的 FiIter 做协议解析,以支持抽取流控规则依赖的参数,比如 origin 等。
5、sentinel-extension:这个模块组则是作为一版扩展实现,不过大部分主要是依赖各种分布式配置型中间件实现的 datasource 模块,实现规则数据的存取。
6、sentinel-cluster:主要是提供集群限流的部分简易实现,目前看这种基于 token 分发中心的集群限流方案在性能上损耗相对单机型还是要大一些。
五、Sentinel 的落地问题及改造方案
在上文中,对 Sentinel 的逻辑架构以及源码已经做了详细介绍。如果在我们生产环境中落地,有哪些问题?
我们知道,Sentinel 提供的默认实现,数据的读取以内存为主要媒介。Dashboard 基于节点上报的心跳信息做汇总,收集到 AppManagement 中,用于左侧菜单栏的应用展示,并开始根据心跳上传时间判断各应用内节点的在线离线状态,当单个应用内所有节点都离线就对该应用进行隐藏。规则数据的读取以及 Metric 统计信息是通过远程调用节点的通信入口进行。由此可以衍生出下面四个场景的落地方案:
1、规则数据的读写
在第三部分中也介绍到,Sentinel 对于规则数据的同步机制,提供了一组 datasource 模块,主要以各分布式配置中间件为中心,实现数据的写入和变更监听,达到动态变更规则数据的目的。如果开发者并不想引入这些额外的中间件,那么就需要根据自己的需求来扩展 datasource 模块。
接入新的配置源,首先从 Dashboard 开始,我们可以参考 FlowControllerV2 中提供的 Nacos 配置样例,对应于各个规则,实现基于 appName 的 DynamicRuleProvider<T>和 DynamicRulePublisher<T>的读写入口类,将规则数据以 Json 的形式写入新的配置源中,当然为了更大的扩展性和业务隔离性,我们可以在 appName 的参数基础上,再追加类似 Datacenter 这种参数,以区分不同的业务群,避免单个 Dashboard 中应用过量造成处理压力。
另外,就要考虑 sentinel-datasource-{CustomizedDatasource}的开发,基于 sentinel-datasource-extension 这个数据源监听模版,参考 sentinel-datasource-nacos 中 NacosDataSource 实现对应的 AbstractDataSource<String, T>,到此为止只是实现了 Datasource 类的定义和实现。而真正打通 Dashboard 到节点侧的数据链路,还需要对 Datasource 的调用和初始化流程。Sentinel 包中的 demo 工程,有针对于各个 datasource 的初始化样例。
而我们如果想真正落地到项目中,仅仅这些还是不够的,而是需要把 Datasource 对各个规则的加载和监听封装成独立的 Starter,这样才能最大限度降低 springboot 集成的复杂度。对于这个想法,我们则需要转向另一个开源项目:spring-cloud-alibaba-sentinel-datasource 和 spring-cloud-starter-alibaba-sentinel。
前者将不同 Datasource 和对应的配置节点做了关联,如下
①DatasourceFactoryBean 的声明
②Properties 初始化参数中携带对应的 FactoryBean
后者,则是在初始化 SentinelDataSourceHandler 的过程中,根据 SentinelProperties 中各 Datasource 子节点配置与否决定是否初始化到 Bean 实例,如下
通过这几部分的构建,我们就可以实现自己的 Sentinel-Starter,实现基于自定义数据源做规则数据的运维。
2、Metric 统计数据及日志的托管
目前 Metric 信息是采用从 Dashboard 定时拉取再从应用维度聚合的方式进行。节点中的 Metrics 信息是相对完整的,我们可以从两方面来进行收集:
第一种方式,节点主动上报,收集至统一的存储,再以 Grafana 的形式做数据展示。具体方式的话,可以选用直接上传到定制的 OAP 服务,也可以扩展 sentinel-log 模块的实现做基于日志的上传(因为 sentinel 的设计机制中,自身的状态日志和业务日志是分离的)。不过,这会造成一定的性能损耗,落地到项目中需要进行详细的压测和评估。
第二种方式,由中控进行定时采样,时间间隔可以自定义,也就是以拉的方式进行,采样率可以在中控进行统一的动态控制,合理配置能够更容易达到性能损耗和观测性的平衡。
此处不再细说,方案比较多,见仁见智。
3、Dashboard 的适配
Dashboard 面向线上业务场景,需要解决的有以下三点:
①数据存储问题:上文中规则数据的 Datasource 扩展不再讨论,除此以外,Sentinel 中节点、应用、簇点链路都是基于内存存储的,这就有一个问题——重启丢失。因为这些信息都是基于节点心跳的,数据丢失后意味着重新收集,那么之前在这些数据上做的规则配置就会丢失查看的入口。另外,应用的展示是以应用内节点的在线状态为准的,当应用内所有节点都离线,也会造成应用列表中缺失。所以解决方案就是需要将应用、节点保存至持久化存储中,例如 Mysql,而簇点链路信息由于树状结构的特殊性,可以直接放到 Redis 中存储以及更新。
②权限问题:Dashboard 通过轻量的自定义 Filter 实现的最简单的登录控制。落地的话就需要首先接入 ERP 统一登录,另外还需要做应用权限的管理,避免权限穿透和误操作问题。
③业务划分:需要按照业务进行应用层面的划分。实现多数据中心的支持,避免中心节点集成应用过多。
4、协议的适配
相比前几点,此处的改动是最轻量的。当开发使用的调用协议不在 Sentinel 官方兼容,那么就需要对协议做适配性开发。按照 sentinel-adapter 中对其他协议的适配方式,我们需要改造的大体可以总结为两点,第一,常规调用上下文参数的解析,比如调用来源等;第二,定义协议入口的资源前缀,与其他资源做区分。
六、小结
本文围绕 Sentinel 展开,从其基本概念到核心实现,再到探讨开源方案的挑战和如何在实际项目中应用和改进 Sentinel。Sentinel 以其高度的灵活性和可扩展性赢得了广大开发者的青睐,不论是新系统的开发还是现有系统的维护,Sentinel 都能为我们提供极具价值的支持。
参考内容:
版权声明: 本文为 InfoQ 作者【京东科技开发者】的原创文章。
原文链接:【http://xie.infoq.cn/article/d28996947ca5b6411b232f9e4】。文章转载请联系作者。
评论