MRA,第 6 部分:使用 NGINX Plus 实现断路器模式

NGINX | 2016年11月10日

作者注:本文是系列博客文章的第六篇:

  1. NGINX 微服务参考架构简介
  2. MRA 第 2 部分:代理模型
  3. MRA,第 3 部分:路由器网状模型
  4. MRA,第 4 部分:Fabric 模型
  5. MRA,第 5 部分:针对微服务调整十二要素应用
  6. MRA,第 6 部分:使用 NGINX Plus 实现断路器模式(本文)

全部6篇博客文章以及一篇关于微服务应用Web前端<.htmla>的博客文章已汇编成一本免费电子书。

还可查看以下有关微服务的NGINX资源:

微服务应用设计彻底改变了应用的运行方式。在微服务架构中,“应用”如今是由一组相互依赖的服务构成,这些服务协同执行任务并提供功能。在复杂应用中,服务图可能层级很深,各项服务之间还存在多重相互依赖关系。

例如,用户服务可能是许多其他服务不可或缺的一部分,而这些服务依赖其提供的数据。在这种情况下,用户服务发生故障可能会导致整个应用出现级联故障。

作为一种应用设计模式,断路器模式(这一术语因Martin Fowler而广为人知)正日益受到微服务架构师的认可,用于避免服务级联故障。断路器模式的理念是监控应用服务及其相互之间的流量,以防止故障;当故障确实发生时,则尽可能降低故障对应用的影响。

对于微服务而言,断路器模式尤其有价值,可提供自下而上的韧性。如果实施得当,即使服务不可用,也能通过保持服务连续性来帮助避免级联故障。断路器模式已被Netflix广泛采用,并成为其应用设计理念中的关键组成部分。

不要回避故障,而要接纳故障

现代应用设计的一项关键原则是:故障必然会发生。现代应用依赖多层技术栈,从云托管虚拟机、容器和应用程序库,到动态网络,这意味着任何应用中都有大量动态组件。需要假设应用的一个或多个组件会在某个时刻以某种方式发生故障。预见故障并内置相应机制来减轻其影响,有助于显著提升应用的韧性。

断路器模式的关键目标之一,是尽可能从源头上防止故障发生。对于某些类型的错误情况,例如内存耗尽,可以识别出故障即将发生,并采取措施加以防范。通常,服务会发出自身运行状况不佳的信号,随后断路器通过限制请求数量或将请求全部重新路由,为服务提供恢复机会。服务恢复后,断路器还应逐步增加向该服务发送的请求,以免服务立即过载并再次陷入运行状况不佳的状态。

在NGINX微服务参考架构中,有一项名为resizer的服务。将大尺寸照片上传到系统后,resizer会对其进行解压缩、旋转校正和两次缩小处理,然后将校正后的原始图像和两张调整尺寸后的图像保存到对象存储中。由于这些处理的性质,resizer是应用中处理器和内存资源占用最高的部分。

同时调整大量图像的大小时,resizer可能会耗尽内存,并在某些情况下完全停止运行。为避免出现问题,我们在resizer服务实例与向其提供图像的uploader服务之间设置了断路器。uploader会定期查询resizer实例的运行状况。该查询会触发resizer执行多项运行状况检查,包括评估已用内存是否超过可用内存的80%,然后向uploader返回其运行状况。

如果某个图像大小调整器实例指示其运行状况不佳,上传器会将请求路由到其他实例(如图1所示),但仍会持续检查该实例是否已恢复。当该实例再次指示其运行状况良好时,它会被重新加入负载均衡池,上传器则逐步增加发送到该实例的流量,直至达到其满负载能力。此设计可防止图像大小调整器实例彻底失效,避免任务开始后无法完成,防止原本会因处理失败而受影响的用户长时间等待,并帮助系统更有效地处理收到的请求流。

断路器模式会切断流向不健康实例的流量。断路器与NGINX可以很好地协同工作。
图1. 主动健康检查可防止调用运行状况异常的微服务实例

断路器模式可提升一致性

在NGINX层实现断路器的优势之一,是可以创建一个通用、一致且高度灵活的层,用于管理整个微服务应用中的断路器。凭借这种通用性和一致性,无需再针对各编程语言的断路器库所存在的细微差异和不一致之处进行管理和适配。

将大部分断路器功能从各项服务的代码中移出,改为在NGINX Plus中实现,可带来诸多优势:

  • 例如,使用Java编写的服务与使用PHP编写的服务可以使用相同的断路器;断路器本身也可以根据需要使用其他语言编写
  • 无需针对各项服务所使用的不同语言和支持库,重复实现断路器功能
  • 无需包含断路器代码后,每项服务都得以简化,运行速度更快,也更易于编写、调试、运行和维护
  • 简化了每项服务的支持代码;所使用的库和系统组合只需体现服务的核心功能
  • 断路器代码得以简化;由于仅存在于一处,可以只保留核心部分,无需适配各处的本地上下文
  • 断路器代码可以利用NGINX Plus的缓存等功能,从而显著增强其能力
  • 您可以微调基于NGINX Plus的断路器代码,然后将其复用于其他应用和不同的部署平台,例如本地环境、不同的云平台以及混合环境。

但需要特别注意的是,仅靠NGINX Plus无法实现断路器。真正的断路器要求服务在指定的URI(通常为/health)上提供自检式主动健康检查。健康检查必须符合该特定服务的需求。

开发健康检查时,需要了解服务的故障特征以及可能导致故障的各种情况,例如数据库连接失败、内存不足、磁盘空间耗尽或CPU过载。健康检查过程会评估这些情况,并给出健康或不健康的二元状态。

断路器模式提供灵活性

如本文所述,在 NGINX 层实现断路器模式时,如果服务实例报告自身运行状况不佳,则由 NGINX Plus 处理这种情况。可采用多种处理方式。

第一种方案是将请求重定向到其他健康实例,同时继续查询不健康的实例,检查其是否恢复。第二种方案是向请求该服务的客户端提供缓存响应,即使服务不可用,也能保持稳定性。此方案非常适合内容服务等以读取为主的服务。

另一种方案是提供替代数据源。例如,我们的一位客户使用个性化广告服务器,根据用户画像数据向用户投放定向广告。如果个性化广告服务器发生故障,用户请求会被重定向到备用服务器,由该服务器提供一组适合所有用户的通用广告。这种替代数据源方案可以发挥很大作用。

最后,如果非常清楚某项服务的故障特征,可以通过为断路器添加速率限制来缓解故障。仅按该服务能够处理的速率放行请求。这样可在断路器中形成缓冲,以吸收流量峰值。

在类似路由器网格模型这样的集中式负载均衡场景中,速率限制尤其有效。在此类场景中,应用流量通过数量有限的负载均衡器进行路由,这些负载均衡器可以充分了解整个站点的总流量使用情况。

在NGINX Plus中实现断路器模式

如上所述,断路器模式可以通过减少流向异常服务的流量或将请求路由到其他服务,在故障发生前加以预防。为此,需要将主动健康检查与每项服务上的自检式健康监控器相连接。遗憾的是,被动健康检查无法实现这一点,因为它只能检查故障;等到检测出故障时,再采取预防措施已经太晚。正因如此,NGINX Open Source无法实现断路器模式,因为它仅支持被动健康检查。

不过,NGINX Plus拥有强大的主动运行状况检查系统,提供多种检查和响应运行状况问题的选项。通过了解微服务参考架构中部分服务类型的实现方式,可以更好地理解实现断路器模式时可采用的选项和适用场景。

先来看连接到图像大小调整服务的上传服务。上传服务将图像放入对象存储,然后指示图像大小调整服务打开、校正图像并调整其大小。这项操作会占用大量计算和内存资源。上传服务需要监控图像大小调整服务的运行状况,避免其过载,因为该服务确实可能导致其所在的主机崩溃。

首先,需要创建一个专用于检查图像大小调整服务运行状况的location块。此块是internal位置,这意味着无法通过服务器的标准URL(http://example.com/health-check-resizer)向其发送请求。此块仅用于存放运行状况检查信息。health_check指令每3秒向/health URI发送一次运行状况检查,并使用名为match的conditions块中定义的测试来检查服务实例的运行状况。如果服务实例未通过一次检查,就会被标记为运行状况不佳。proxy_*指令将运行状况检查发送到resizer上游组,通过HTTP 1.1使用TLS 1.2,并将指定的HTTP标头设置为null。

location /health-check-resizer {    internal;
    health_check uri=/health match=conditions fails=1 interval=3s;


    proxy_pass                        https://resizer;
    proxy_ssl_session_reuse           on;
    proxy_ssl_protocols               TLSv1.2;
    proxy_http_version                1.1;
    proxy_set_header Connection       "";
    proxy_set_header Accept-Encoding  "";
}

下一步是创建conditions match块,用于指定表示健康和不健康状态的响应。首先检查响应状态码:如果状态码在200至399范围内,则继续执行下一条评估语句。第二项检查是确认Content-Type为application/json。最后,第三项检查使用正则表达式匹配deadlocks、Disk和Memory指标的值。如果这些指标均处于健康状态,则判定该服务健康。

match conditions {    status 200-399;
    header Content-Type ~ "application/json";
    body ~ '{
        "deadlocks":{"healthy":true},
        "Disk":{"healthy":true},
        "Memory":{"healthy":true}
    }';
}

NGINX Plus断路器和健康检查系统还提供慢启动功能。当调整器实例刚从不健康状态恢复时,调整器服务的slow_start参数会通过server指令告知NGINX Plus调节流量。该指令位于upstream块中。NGINX Plus不会立即向该服务发送与健康服务相同数量的请求,而是根据slow_start参数指定的时长逐步将流向恢复中服务的流量提升至正常速率,本例中为30秒。慢启动有助于提高服务恢复全部处理能力的可能性,同时在服务未能完全恢复时减轻影响。

upstream resizer {    server        resizer slow_start=30s;
    zone backend  64k;
    least_time    last_byte;
    keepalive     300;
}

请求限制用于管理和调节流向服务的请求。如果充分了解应用的故障特征,能够确定应用在任意时刻可处理的请求数量,那么实施请求限制将对这一过程大有帮助。不过,只有当NGINX Plus能够全面掌握传入服务的连接总数时,此功能才有效。因此,最适合在与服务本身共同运行于容器中的NGINX Plus实例上实施请求限制断路器(如Fabric Model),或者在负责管理集群中所有流量的集中式负载均衡器上实施。

以下配置代码片段定义了对容器中resizer服务实例实施的请求速率限制。limit_req_zone指令将速率限制定义为每秒100个请求。使用$server_addr变量作为键,这意味着进入resizer容器的所有请求都会计入该限制。该区域名为moderateReqs,请求计数的保留时间为1分钟。limit_req指令使NGINX Plus能够缓冲最多150个突发请求。超过该数量时,客户端会收到503错误代码,该代码由limit_req_status指令指定,表示服务不可用。

http {    # Moderated delivery
    limit_req_zone $server_addr zone=moderateReqs:1m rate=100r/s;
    # ...
    server {
        # ...
        limit_req zone=moderateReqs burst=150;
        limit_req_status 503;
        # ...
    }
}

在NGINX Plus中运行断路器还有一项显著优势,即能够集成缓存,并集中维护缓存数据,供整个系统使用。这对于内容服务器等以读取为主的服务尤其有价值,因为从后端读取的数据不会频繁变化。

proxy_cache_path /app/cache levels=1:2 keys_zone=oauth_cache:10m max_size=10m inactive=15s use_temp_path=off;
upstream user-manager {
    server        user-manager;
    zone backend  64k;
    least_time    last_byte;
    keepalive     300;
}

server {
    listen 443 ssl;
    location /v1/users {
        proxy_pass http://user-manager;
        proxy_cache            oauth_cache;
        proxy_cache_valid      200  30s;
        proxy_cache_use_stale  error timeout invalid_header updating
                               http_500 http_502 http_503 http_504;
    }
}

如图2所示,通过缓存数据,许多客户数据请求无需到达微服务实例,从而释放容量,用于处理此前未收到过的请求。

NGINX Plus充当微服务断路器时也支持缓存。
图 2. 缓存通常用于避免调用微服务实例,从而提升性能;在服务完全故障时,缓存也有助于保持服务连续性

但是,对于数据可能发生变化的服务(例如用户管理服务),需要谨慎管理缓存。否则,用户更改个人资料后,可能会因数据已缓存而在某些情况下看到旧数据。设置合理的超时时间,并接受通过最终一致性实现高可用性的原则,可以解决这一难题。

NGINX缓存的一项实用功能是,即使服务完全不可用,它仍可继续提供缓存数据。在上面的代码片段中,这指的是服务返回四种最常见的500系列错误代码之一时。

即使服务器停机,缓存也不是响应客户端请求的唯一方式。正如我们在《断路器模式提供灵活性》中提到的,一位客户需要一种弹性解决方案,以应对个性化广告服务器停机的情况,而缓存响应并不适用。他们希望在个性化广告服务器恢复上线之前,由通用广告服务器提供通用广告。使用backup参数和server指令即可轻松实现这一点。以下代码片段指定,当为personal-ad-server域定义的所有服务器均不可用时,改用为generic-ad-server域定义的服务器。

upstream personal-ad-server {    server        personal-ad-server;
    server        generic-ad-server backup;
    zone backend  64k;
    least_time    last_byte;
    keepalive     300;
}

最后,还可以让NGINX评估响应代码,并根据服务返回的不同响应代码分别进行处理。在以下代码片段中,如果某项服务返回503错误,NGINX Plus会将请求转发到备用服务。例如,如果图像大小调整服务具备此功能,而本地实例过载或停止运行,请求就会被发送到该服务的另一个实例。

location / {    error_page 503 = @fallback;
}

location @fallback {
    proxy_pass http://alternative-backend;
}

结论

断路器模式是一种强大的工具,可为微服务应用提供韧性和控制能力。NGINX Plus提供多种功能和选项,帮助在您的环境中实施断路器。实施断路器模式的关键在于了解受保护服务的故障特征,然后选择适当的选项,尽可能防止故障,并在故障发生时尽量减轻其影响。

如需试用NGINX Plus,可立即开始30天免费试用,或联系我们,讨论您的用例。


分享

相关博客文章

NGINX Plus R26 正式发布
NGINX | 2022/02/15

NGINX Plus R26 正式发布

NGINX Plus R26通过缓存JSON Web密钥集加快JWT验证,并强化TLS握手,从而提升安全性。NGINX JavaScript的新功能包括增强对异步函数的支持,以及实现WebCrypto API。

使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击
NGINX | 2022/02/10

使用NGINX App Protect DoS保护gRPC应用,抵御严重的DoS攻击

gRPC具备开发人员设计和部署现代应用所需的速度与灵活性,但其开放性也使其极易受到第7层DoS攻击。NGINX App Protect DoS可保护gRPC应用免受此类破坏性攻击。

Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划
NGINX | 2021/09/13

Ingress Controller 选型指南(第 2 部分):风险与面向未来的规划

了解选择错误的Ingress控制器可能带来的风险,以及如何从关键方面着手,让您的选择更好地适应未来需求。

NGINX Plus R19 正式发布
NGINX | 2019/09/03

NGINX Plus R19 正式发布

NGINX Plus R19扩展了监控功能,不仅提供更多指标,还新增了指标分析方式;增加试运行模式,用于测试限速效果;增强键值存储,以支持IP地址范围;并使限速更加动态。

NGINX现已正式加入F5
NGINX | 2019/05/09

NGINX现已正式加入F5

NGINX现已正式成为F5的一部分。Gus Robertson现任F5 NGINX业务部门负责人。他阐述了两家公司在人才、文化和愿景方面融合的价值,以及双方将如何携手增强NGINX的开源产品和商业产品。

使用NGINX和NGINX Plus实现高性能缓存
NGINX | 2016/08/24

使用NGINX和NGINX Plus实现高性能缓存

我将介绍NGINX在内容缓存和性能方面的工作原理,并分享一些控制NGINX内容缓存方式的技巧。