在复杂的生产环境下可能部署着成千上万的服务实例,当流量持续不断地涌入,服务之间相互调用频率陡增时,会产生系统负载过高、网络延迟等一系列问题,从而导致某些服务不可用。如果不进行相应的流量控制,可能会导致级联故障,并影响到服务的可用性,因此如何对高流量进行合理控制,成为保障服务稳定性的关键。
Sentinel是面向分布式服务架构的轻量级限流降级框架,以流量为切入点,从流量控制、熔断降级和系统负载保护等多个维度来帮助用户保障服务的稳定性。
Sentinel 意为哨兵,这个命名形象的诠释了Sentinel在分布式系统中的工作角色和重要性。 以 Sentinel 在Dubbo 生态系统中的作用为例,Dubbo服务框架的核心模块包括注册中心、服务提供方、服务消费方(服务调用方)和监控4个模块。Sentinel通过对服务提供方和服务消费方的限流来进一步提升服务的可用性。接下来我们看看Sentinel对服务提供方和服务消费方限流的技术实现方式。
Sentinel 提供了与 Dubbo 适配的模块 – Sentinel Dubbo Adapter,包括针对服务提供方的过滤器和服务消费方的过滤器。使用时用户只需引入相关模块,Dubbo的服务接口和方法(包括调用端和服务端)就会成为 Sentinel 中的资源,在配置了规则后就可以自动享受到 Sentinel 的防护能力。同时提供了灵活的配置选项,例如若不希望开启Sentinel Dubbo Adapter中的某个Filter,可以手动关闭对应的Filter。
流量具有很强的实时性,之所以需要限流,是因为我们无法对流量的到来作出精确的预判,不然的话我们完全可以通过弹性的计算资源来处理,所以这时候为了保证限流的准确性,限流框架的监控功能就非常重要了。
Sentinel的控制台(Dashboard)是流量控制、熔断降级规则统一配置和管理的入口,同时它为用户提供了多个维度的监控功能。在Sentinel控制台上,我们可以配置规则并实时查看流量控制效果。
单台设备监控 - 当在机器列表中看到您的机器,就代表着已经成功接入控制台,可以查看单台设备的设备名称、IP地址、端口号、健康状态和心跳时间等信息。
链路监控 - 簇点链路实时的去拉取指定客户端资源的运行情况,它提供了两种展示模式,一种用书状结构展示资源的调用链路;另外一种则不区分调用链路展示资源的运行情况。通过链路监控,可以查看到每个资源的流控和降级的历史状态。
聚合监控 - 同一个服务下的所有机器的簇点信息会被汇总,实现实时监控,精确度达秒级。
三、Sentinel 基于 Dubbo的最佳实践
Dubbo 接入 Sentinel后,可通过对Dubbo核心模块中的服务提供方和服务消费方的限流来进一步提升服务的可用性。
1、对服务提供方的限流
对服务提供方的限流可分为服务提供方的自我保护能力和服务提供方对服务消费方的请求分配能力这两个维度。
服务提供方用于向外界提供服务,处理各个消费方的调用请求。为了保护提供方不被激增的流量拖垮影响稳定性,可以给提供方配置QPS模式的限流,这样当每秒的请求量超过设定的阈值时会自动拒绝阈值外的请求。若希望整个服务接口的QPS不超过一定数值,则可以为对应服务接口资源(resourceName为接口全限定名)配置QPS阈值;若希望对某个服务函数的QPS不超过一定数值,则可以为对这个服务函数资源(resourceName为接口全限定名:方法签名)配置QPS阈值。
根据调用方的需求来分配服务提供方的处理能力也是常见的限流方式。比如有两个服务 A 和 B 都向 Service Provider 发起调用请求,我们希望只对来自服务 B 的请求进行限流,则可以设置限流规则的 limitApp 为服务 B 的名称。Sentinel Dubbo Adapter 会自动解析 Dubbo 消费方(调用方)的 application name 作为调用方名称(origin),在进行资源保护的时候都会带上调用方名称。若限流规则未配置调用方(default),则该限流规则对所有调用方生效。若限流规则配置了调用方则限流规则将仅对指定调用方生效。
2、对服务消费方的限流
对服务提供方的限流可分为对控制并发线程数,和服务降级两个维度。
服务消费方作为客户端去调用远程服务。每一个服务都可能会依赖几个下游服务,若某个服务A依赖的下游服务B出现了不稳定的情况,服务A请求服务B的响应时间变长,从而服务A调服务B的线程就会产生堆积,最终可能耗尽服务A的线程数。我们通过用并发线程数来控制对下游服务B的访问,来保证下游服务不可靠的时候,不会拖垮服务自身。采用基于线程数的限制模式后,我们不需要再去对线程池进行隔离,Sentinel 会控制资源的线程数,超出的请求直接拒绝,直到堆积的线程处理完成。限流粒度同样可以是服务接口和服务方法两种粒度。
我们看一下这种模式的效果。假设当前服务A依赖两个远程服务方法 sayHello(java.lang.String) 和 doAnother()。前者远程调用的响应时间为1s-1.5s之间,后者RT非常小(30 ms左右)。服务A端设两个远程方法线程数为5,然后每隔50 ms左右向线程池投入两个任务,作为调用者分别远程调用对应方法,持续10次。可以看到 sayHello 方法被限流5次,因为后面调用的时候前面的远程调用还未返回(RT高);而 doAnother() 调用则不受影响。线程数目超出时快速失败能够有效地防止自己被调用所影响。
此外,当调用链路中某个资源出现不稳定的情况,如平均 RT 增高、异常比例升高的时候,Sentinel 会使对此调用资源进行降级操作。
动态规则数据源
Sentinel 的动态规则数据源用于从中读取及写入规则。从 0.2.0 版本开始,Sentinel 将动态规则数据源分为两种类型:读数据源(ReadableDataSource
)和写数据源(WritableDataSource
):
其中读数据源常见的实现方式有:
在实际的场景中,不同的存储类型对应的数据源类型也不同。对于 push 模式的数据源,一般不支持写入;而 pull 模式的数据源则是可写的。
下面我们分别来分析一下它们结合 Sentinel 控制台的使用场景,以及相应的需要改造的点。
| 原始情况
若应用未注册任何数据源,直接从 Sentinel 控制台推送规则的过程非常简单:
Sentinel 控制台通过 API 将规则推送至客户端并直接更新到内存中。这种情况下应用重启规则就会消失,仅用于简单测试,不能用于生产环境。一般在生产环境中,我们需要在应用端配置规则数据源。
| Pull模式的数据源
Pull 模式的数据源(如本地文件、RDBMS 等)一般是可写入的。使用时需要在客户端注册数据源:将对应的读数据源注册至对应的 RuleManager,将写数据源注册至 transport 的 WritableDataSourceRegistry
中。
本地文件数据源会定时轮询文件的变更,读取规则。这样我们既可以在应用本地直接修改文件来更新规则,也可以通过 Sentinel 控制台推送规则。以本地文件数据源为例,推送过程如下图所示:
首先 Sentinel 控制台通过 API 将规则推送至客户端并更新到内存中,接着注册的写数据源会将新的规则保存到本地的文件中。使用 pull 模式的数据源时一般不需要对 Sentinel 控制台进行改造。
| Push模式的数据源
对于 push 模式的数据源(如远程配置中心),推送的操作不应由 Sentinel 数据源进行,而应该经控制台进行推送,数据源仅负责获取配置中心推送的配置并更新到本地。
假设写入的操作也由数据源进行,那么 Sentinel 客户端收到控制台推送的规则后,将新的规则更新到内存中,同时将规则推送至远程的配置中心。此时,数据源监听到配置中心推送过来的新规则,又一次更新到内存中。也就是说应用在本地更新完规则并推送到远程后,又要接收变更并更新一次,这样显然是不合理的。因此推送规则正确做法应该是 配置中心控制台/Sentinel 控制台 → 配置中心 → Sentinel 数据源 → Sentinel,而不是经 Sentinel 数据源推送至配置中心。这样的流程就非常清晰了:
注意由于不同的生产环境可能使用不同的数据源,从 Sentinel 控制台推送至配置中心的实现需要用户自行改造。以 ZooKeeper 为例,我们可以按照如下步骤进行改造(假设推送维度为应用维度):
1. 实现一个公共的 ZooKeeper 客户端用于推送规则,在 Sentinel 控制台配置项中需要指定 ZooKeeper 的地址,启动时即创建 ZooKeeper Client。
2. 我们需要针对每个应用(appName),每种规则设置不同的 path(可随时修改);或者约定大于配置(如 path 的模式统一为 /sentinel_rules/{appName}/{ruleType},e.g. sentinel_rules/appA/flowRule)。
3. 规则配置页需要进行相应的改造,直接针对应用维度进行规则配置;修改同个应用多个资源的规则时可以批量进行推送,也可以分别推送。Sentinel 控制台将规则缓存在内存中(如 InMemFlowRuleStore),可以对其进行改造使其支持应用维度的规则缓存(key 为 appName),每次添加/修改/删除规则都先更新内存中的规则缓存,然后需要推送的时候从规则缓存中获取全量规则,然后通过上面实现的 Client 将规则推送到 ZooKeeper 即可。
4. 应用客户端需要注册对应的读数据源以监听变更,可以参考相关文档。
监控数据持久化
Sentinel 会记录资源访问的秒级数据(若没有访问则不进行记录)并保存在本地日志中,具体格式请见 秒级监控日志文档。Sentinel 控制台通过 Sentinel 客户端预留的 API 从秒级监控日志中拉取监控数据,并进行聚合。目前 Sentinel 控制台中监控数据聚合后直接存在内存中,未进行持久化,且仅保留最近 5 分钟的监控数据。若需要监控数据持久化的功能,可以自行扩展实现 MetricsRepository
接口(0.2.0 版本),然后注册成 Spring Bean 并在相应位置通过 @Qualifier
注解指定对应的 bean name 即可。MetricsRepository 接口定义了以下功能:
save
与 saveAll
:存储对应的监控数据queryByAppAndResourceBetween
:查询某段时间内的某个应用的某个资源的监控数据listResourcesOfApp
:查询某个应用下的所有资源其中默认的监控数据类型为 MetricEntity
,包含应用名称、时间戳、资源名称、异常数、请求通过数、请求 block 数、平均响应时间等信息。
同时用户可以自行进行扩展,适配 Grafana 等可视化平台,以便将监控数据更好地进行可视化。
Sentinel与Hystrix对比
参考链接: