在复杂的生产环境下可能部署着成千上万的服务实例,当流量持续不断地涌入,服务之间相互调用频率陡增时,会产生系统负载过高、网络延迟等一系列问题,从而导致某些服务不可用。如果不进行相应的流量控制,可能会导致级联故障,并影响到服务的可用性,因此如何对高流量进行合理控制,成为保障服务稳定性的关键。
阿里巴巴中间件团队在上周的Aliware Open Source 深圳站的活动上,宣布对Sentinel开源,并在社区发布了首个开源版本v0.1.0。Sentinel是面向分布式服务架构的轻量级限流降级框架,以流量为切入点,从流量控制、熔断降级和系统负载保护等多个维度来帮助用户保障服务的稳定性。从本期开始,我们将围绕Sentinel的使用场景、技术对比和实现、开发者实践等维度推出系列文章。本文将基于Dubbo的服务框架,看看Sentinel 是如何对高流量进行限流的。
一、Dubbo 一键接入 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的一个最简单的场景 – 限流,Sentinel还可以处理更复杂的场景,例如 请求匀速、超时熔断、冷启动 等,下一期我们将分享如何借助Sentinel实现消息流转场景下的请求匀速功能。