微服务架构如何避免大规模故障?

微服务架构通过一种良好的服务边界划分,能够有效地进行故障隔离。但就像其他分布式系统一样,在网络、硬件或者应用级别上容易出现问题的机率会更高。服务的依赖关系,导致在任何组件暂时不可用的情况下,就它们的消费者而言都是可以接受的。为了能够降低部分服务中断所带来的影响,我们需要构建一个容错服务,来优雅地应对特定类型的服务中断。

本文基于一些在RisingStack的顾问咨询与开发经验,介绍了如何运用一些最常用的技术和架构模型,去构建与维护一个高可用的微服务系统。

如果你不熟悉本文中的模式,并不意味着你做错了什么。毕竟构建一个高可用的系统需要很多额外的付出。

*微服务架构的风险 The Risk of the Microservices Architecture

微服务的架构将应用的逻辑移动到一个服务里面,服务之间通过网络层进行通信交互。通过网络通信交互的方式取代了内存的调用,同时需要多个物理和逻辑组件之间的相互协作,给系统带来了额外的延迟性与复杂性。分布式系统复杂性的增加,导致了特定网络故障的可能性变得更大。

微服务允许你实现优雅的服务降级,因为组件可以被单独的设置为失败。

团队可以独立地设计、开发与部署他们的服务,是微服务的最大优点之一。他们完全拥有整个服务的生命周期,这也意味着团队无法控制他们的服务依赖,因为这些服务更有可能是不同的团队在管理。我们需要记住,提供者的服务由于发布中断、配置等等其他的改变而暂时不可用,他们是由别人控制,并且组件之间独立活动。

*优雅的服务降级 Graceful Service Degradation

微服务最佳优势之一,当某个组件单独失败时,你可以实现优雅的服务降级,进行故障隔离。例如,一个照片共享的应用,由于中断,用户可能无法上传新的照片,但他们仍然可以浏览、编辑和分享他们现有的照片。

微服务的独立失败(理论上)

在大多数情况下,在一个分布式系统中,应用程序之间互相依赖,实现一种优雅的服务降级,这是很困难的,你需要采取多种故障切换逻辑(其中一些会在本文后面进行讨论),应对临时的故障与中断。

服务之间彼此依赖,在没有故障切换逻辑的情况下,一起失败。

*变更管理 Change management

谷歌网站的可靠性团队(SRE)发现,大约70%的中断是由一个实时系统的改变而引起。当你在服务中更改某些内容时——你部署了新版本的代码或更改了一些配置——总会导致更高的失败机率或者引入一个新的bug。

在微服务架构中,服务之间彼此依赖。这就是为什么你应该尽量减少失败,并限制它们的负面影响。如果要处理来自变更的问题,你可以使用变更管理策略和自动升级。

例如,当需要部署新代码或者更改某些配置时,你应该逐渐地将这些更改应用于实例的子集,监控它们,甚至当你看到关键指标有负面影响时,它们会自动回滚恢复。

变更管理 - 滚动发布

另一个解决方案,就是运行两个生产环境。只部署其中一个,并且在验证新版本的运行符合预期之后,才会将负载均衡指向新版本。这被称为蓝绿色部署,或红黑色部署。

恢复代码不是一件坏事情。你不应该把坏的代码留在生产中,然后再思考哪里出了问题。必要的时候,总是要恢复你的改变(回滚),越快越好。

*健康检查与负载均衡 Health-check and Load Balancing

实例会因为失败、部署或自动伸缩,而不断地启动、重新启动和停止。这会导致服务暂时或永久不可用。为了避免问题,你的负载均衡应该跳过不健康的实例,因为它们不能满足你的用户或子系统的需要。

应用实例健康可以通过外部观察来决策。你可以反复调用 GET /health 请求埋点或自身报告。现代服务发现解决方案,将不断从实例中收集健康信息,并配置负载均衡以保证健康的组件路由流量。

*自愈 Self-healing

自我修复可以帮助恢复应用程序。我们谈论的自愈,是指应用程序可以做一些必要的步骤来恢复崩溃状态。在大多数情况下,这样的操作是经由一个外部系统来实现的,它会监控实例的健康,并在它们较长时间处于错误状态的情况下,重新启动应用程序。自愈是非常有用的,但是在某些情况下,不断地重启应用程序会引起麻烦。由于负载过高或者数据库连接超时,你的应用程序不停的重启,会导致无法提供一个正确的健康状态。

实现一种为微妙的情况而准备的高级自我修复解决方案,可能会很棘手,比如数据库连接丢失。在这种情况下,你需要为应用程序添加额外的逻辑来处理一些极端情况,并让外部系统知道不需要立即重启实例。

*故障切换缓存 Failover Caching

服务通常会因为网络问题和系统的变更而失败。由于自愈和先进的负载均衡,大多数中断只是暂时的,然而我们还应该找到一个解决方案,让我们的服务在这些故障中能够正常工作。这就是故障切换缓存,它可以帮助应用程序提供一些必要的数据。

故障切换缓存一般使用两个不同的过期时间。设置一个较短的时间,显示在正常情况下可以使用多长时间的缓存;设置另一个较长的时间,显示在发生故障期间,可供使用缓存数据的时间会有多久。

故障切换缓存

很重要的一点是,只有当过时的数据比什么都不做要好的情况出现时,才可运行故障切换缓。

可以通过使用HTTP中的标准响应头(response header)来设置缓存和故障转移缓存。

例如,通过设定 header 参数 max-age 来指定一个资源被刷新时最大时间;也可以通过设定 header 参数 stale-if-error 来决定,在服务失败的情况下,需要多长时间从缓存获取数据。

现代的CDN和负载均衡器提供了各种缓存和故障切换的方式,你也可以为公司建立一个包含了统一的可靠性解决方案的共享标准库。

*重试机制 Retry Logic

在某些特定的场景下,我们可能无法缓存数据,或者我们想对其做出一些更改,但是我们的操作最终还是会失败。在这些情况下,我们可以重新尝试我们的操作,因为我们可以预计资源在一段时间后会恢复,或者我们的负载均衡将我们的请求转发到一个健康的实例。

在应用程序和客户端添加重试逻辑需保持谨慎,因为大量的重试会让事情变得更糟,甚至会阻止应用程序的恢复。

在分布式系统中,微服务系统重试会触发多个其他的请求或重试,引起一个级联效应。为了尽量减少重试带来的影响,你应该最大限度限制它们的发生次数,并使用指数补偿算法来持续增加重试之间的延迟。

重试由客户端(浏览器,其他微服务等)发起,客户端不知道这个操作是在处理请求之前失败还是之后失败的,你应该准备好应用程序来处理幂等性(idempotency)。例如,当操作重试购买时,不应该对用户进行重复扣费。对于每个事务,使用唯一的 幂等令牌(idempotency-key ),可以帮助处理重试。

*限流与降级 Rate Limiters and Load Shedders

限流是指在一个时间段内,特定的用户或应用程序可以接收或处理多少请求的技术。例如,有了限流,你就可以找出引起流量高峰的用户和微服务,或者可以确保应用不会在负载过高情况下,发生自动扩容都不能拯救。

你还可以限制业务优先级较低的流量,以便为核心业务提供足够的资源。

限速器可以抑制流量高峰

另外一种类型的限速器称为并发请求限制。当有一些昂贵的端点不应该超过指定的调用次数,但你仍然希望提供流量服务时,选择这样的操作是很有用的。

快速降级可以确保总是有足够的可用资源去服务关键的事务。它为高优先级请求保留一些资源,并且不允许低优先级事务使用所有的资源。降级与否是根据系统的整个状态进行判断的,而不是基于单个用户的请求桶大小。服务降级用于帮助恢复系统,当发生一些事故时,它们可以保证核心功能仍然继续工作。

如需获取更多有关限流与降级的信息,推荐前往https://stripe.com/blog/rate-limiters,阅读Stripe的文章。

*快速且独立地失败 Fail Fast and Independently

在微服务体系结构中,我们希望我们的服务能够快速、独立地失败。为了在服务级别上隔离问题,我们可以采用舱壁模式(bulkhead pattern)。你稍后可以在这篇文章中读到更多关于舱壁的信息。

我们还希望我们的组件快速失败,因为我们不想等待坏的实例超时。没有什么比一个挂着的请求和一个没有响应的UI更令人失望的了。这样不仅浪费资源,而且还会对用户体验造成影响。我们的服务是相互调用的,所以更应该额外注意,在这些延迟结束之前,阻止挂起操作。

第一个想到的想法是在每个服务调用上运用一个较好级别的超时时间。这种方法的问题在于,你不可能真正知道什么是一个好的超时时间值,因为在某些情况下,网络故障和其他问题只会影响到一两个操作。在这种情况下,如果只有少数几个请求超时,你可能不想拒绝这些请求。

我们可以说,在微服务中使用超时来实现快速失败的例子是一种反模式,你应该避免它。你可以依赖于操作成功/失败统计数据的断路器(circuit-breaker)模式,而不是超时。

*舱壁 Bulkheads

舱壁被用来将一艘船划分成多个部分,这样就可以在船体破裂的情况下对部分封闭。

隔离壁的概念可以应用于软件开发中,做到资源隔离。

通过采用舱壁模式,我们可以保护有限的资源不被耗尽。例如,如果我们有两种操作,它们与相同的数据库实例交互,我们的连接数量有限,那么我们可以使用两个连接池,而不是共享连接池。由于此客户端资源分离,当发生超时或者过度使用连接池的操作,不会导致所有其他操作的关闭。

泰坦尼克号沉没的主要原因之一,就是它的舱壁有一个设计上的失败,水可以通过舱壁顶部上的甲板注入,淹没整个船体。

泰坦尼克的舱壁(他们没有工作)

*断路器 Circuit Breakers

为了限制操作的持续时间,我们可以使用超时。超时可以防止挂起操作并保持系统响应。然而,在微服务通信中使用静态的、微调的超时是一种反模式,因为我们处在一个高度动态的环境中,几乎不可能发现正确的时间限制,以确保在每个场景下都能很好地工作。

我们可以使用熔断来处理错误,而不是使用小的特定事务的静态超时。断路器是以真实世界电子元件命名的,因为它们的行为是相同的(简单的说,这种模式主要是参考电路熔断,如果一条线路电压过高,保险丝会熔断,防止火灾)。你可以保护资源,帮助他们用断路器恢复。它们在分布式系统中非常有用,因为重复的失败会导致滚雪球效应(snowball effect),导致整个系统瘫痪。

当一个特定类型的错误在短时间内多次出现时,断路器就会打开。断路器的打开,阻止了进一步的资源请求——就像真的阻止了电流的流动。断路器通常在一定时间后关闭,为基础服务提供足够的空间来恢复。

请记住,并非所有的错误都应该触发断路器。例如,你可能希望跳过客户端问题,比如跳过 4xx 状态码响应的请求,但不包括 5xx 服务器端错误的请求。一些断路器也可以有半开状态,在此状态下,服务发送第一个请求检测系统的可用性,同时让其他请求失败。如果第一个请求成功,它将断路器恢复到一个关闭状态,并允许流量进入。否则,它就会打开。

*测试失败 Testing for Failures

你应该不断地测试你的系统以防止常见问题,以确保你的服务能够承受住各种失败。你应该频繁地测试失败,让你的团队为发生事故而做好准备。

对于测试,你可以使用一个外部服务来标识实例组,并随机终止该组中的一个实例。有了这个,你就可以为单个实例的失败做准备,你甚至可以关闭整个可用区来模拟云提供商的中断。

最流行的测试解决方案之一是由Netflix提供的ChaosMonkey弹性工具。

*结尾

实现和运行可靠的服务并不容易。这需要你付出很大的努力,也要花费你的公司很多钱。

可靠性有很多的层次和方面,所以为你的团队找到最好的解决方案是很重要的。你应该将可靠性作为业务决策过程中的一个因素,并为它分配足够的预算和时间。

*主要收获

动态环境和分布式系统——比如微服务——会导致更大的失败机率。 服务应该单独失败,实现优雅的降级,用以改善用户体验。 70%的中断是由变更引起的,恢复代码并不是件坏事。 快速和独立的失败。团队无法控制他们服务的依赖。 架构模式和技术,如缓存、舱壁、限流、熔断,有助于建立可靠的微服务。

原文:https://blog.risingstack.com/designing-microservices-architecture-for-failure/
译文:https://blog.csdn.net/xushuai110/article/details/77726447

近期热文推荐:

1.1,000+ 道 Java面试题及答案整理(2021最新版)

2.别在再满屏的 if/ else 了,试试策略模式,真香!!

3.卧槽!Java 中的 xx ≠ null 是什么新语法?

4.Spring Boot 2.5 重磅发布,黑暗模式太炸了!

5.《Java开发手册(嵩山版)》最新发布,速速下载!

觉得不错,别忘了随手点赞+转发哦!

热门文章

暂无图片
编程学习 ·

Java输出数组的内容

Java输出数组的内容_一万个小时-CSDN博客_java打印数组内容1. 输出内容最常见的方式// List<String>类型的列表List<String> list new ArrayList<String>();list.add("First");list.add("Second");list.add("Third");list.ad…
暂无图片
编程学习 ·

母螳螂的“魅惑之术”

在它们对大蝗虫发起进攻的时候&#xff0c;我认认真真地观察了一次&#xff0c;因为它们突然像触电一样浑身痉挛起来&#xff0c;警觉地面对限前这个大家伙&#xff0c;然后放下自己优雅的身段和祈祷的双手&#xff0c;摆出了一个可怕的姿势。我被眼前的一幕吓到了&#xff0c;…
暂无图片
编程学习 ·

疯狂填词 mad_libs 第9章9.9.2

#win7 python3.7.0 import os,reos.chdir(d:\documents\program_language) file1open(.\疯狂填词_d9z9d2_r.txt) file2open(.\疯狂填词_d9z9d2_w.txt,w) words[ADJECTIVE,NOUN,VERB,NOUN] str1file1.read()#方法1 for word in words :word_replaceinput(fEnter a {word} :)str1…
暂无图片
编程学习 ·

HBASE 高可用

为了保证HBASE是高可用的,所依赖的HDFS和zookeeper也要是高可用的. 通过参数hbase.rootdir指定了连接到Hadoop的地址,mycluster表示为Hadoop的集群. HBASE本身的高可用很简单,只要在一个健康的集群其他节点通过命令 hbase-daemon.sh start master启动一个Hmaster进程,这个Hmast…
暂无图片
编程学习 ·

js事件操作语法

一、事件的绑定语法 语法形式1 事件监听 标签对象.addEventListener(click,function(){}); 语法形式2 on语法绑定 标签对象.onclick function(){} on语法是通过 等于赋值绑定的事件处理函数 , 等于赋值本质上执行的是覆盖赋值,后赋值的数据会覆盖之前存储的数据,也就是on…
暂无图片
编程学习 ·

Photoshop插件--晕影动态--选区--脚本开发--PS插件

文章目录1.插件界面2.关键代码2.1 选区2.2 动态晕影3.作者寄语PS是一款栅格图像编辑软件&#xff0c;具有许多强大的功能&#xff0c;本文演示如何通过脚本实现晕影动态和选区相关功能&#xff0c;展示从互联网收集而来的一个小插件&#xff0c;供大家学习交流&#xff0c;请勿…
暂无图片
编程学习 ·

vs LNK1104 无法打开文件“xxx.obj”

写在前面&#xff1a; 向大家推荐两本新书&#xff0c;《深度学习计算机视觉实战》和《学习OpenCV4&#xff1a;基于Python的算法实战》。 《深度学习计算机视觉实战》讲了计算机视觉理论基础&#xff0c;讲了案例项目&#xff0c;讲了模型部署&#xff0c;这些项目学会之后可以…
暂无图片
编程学习 ·

工业元宇宙的定义与实施路线图

工业元宇宙的定义与实施路线图 李正海 1 工业元宇宙 给大家做一个关于工业元宇宙的定义。对于工业&#xff0c;从设计的角度来讲&#xff0c;现在的设计人员已经做到了普遍的三维设计&#xff0c;但是进入元宇宙时代&#xff0c;就不仅仅只是三维设计了&#xff0c;我们的目…
暂无图片
编程学习 ·

【leectode 2022.1.15】完成一半题目

有 N 位扣友参加了微软与力扣举办了「以扣会友」线下活动。主办方提供了 2*N 道题目&#xff0c;整型数组 questions 中每个数字对应了每道题目所涉及的知识点类型。 若每位扣友选择不同的一题&#xff0c;请返回被选的 N 道题目至少包含多少种知识点类型。 示例 1&#xff1a…
暂无图片
编程学习 ·

js 面试题总结

一、js原型与原型链 1. prototype 每个函数都有一个prototype属性&#xff0c;被称为显示原型 2._ _proto_ _ 每个实例对象都会有_ _proto_ _属性,其被称为隐式原型 每一个实例对象的隐式原型_ _proto_ _属性指向自身构造函数的显式原型prototype 3. constructor 每个prot…
暂无图片
编程学习 ·

java练习代码

打印自定义行数的空心菱形练习代码如下 import java.util.Scanner; public class daYinLengXing{public static void main(String[] args) {System.out.println("请输入行数");Scanner myScanner new Scanner(System.in);int g myScanner.nextInt();int num g%2;//…
暂无图片
编程学习 ·

RocketMQ-什么是死信队列?怎么解决

目录 什么是死信队列 死信队列的特征 死信消息的处理 什么是死信队列 当一条消息初次消费失败&#xff0c;消息队列会自动进行消费重试&#xff1b;达到最大重试次数后&#xff0c;若消费依然失败&#xff0c;则表明消费者在正常情况下无法正确地消费该消息&#xff0c;此时…
暂无图片
编程学习 ·

项目 cg day04

第4章 lua、Canal实现广告缓存 学习目标 Lua介绍 Lua语法 输出、变量定义、数据类型、流程控制(if..)、循环操作、函数、表(数组)、模块OpenResty介绍(理解配置) 封装了Nginx&#xff0c;并且提供了Lua扩展&#xff0c;大大提升了Nginx对并发处理的能&#xff0c;10K-1000K Lu…
暂无图片
编程学习 ·

输出三角形

#include <stdio.h> int main() { int i,j; for(i0;i<5;i) { for(j0;j<i;j) { printf("*"); } printf("\n"); } }
暂无图片
编程学习 ·

stm32的BOOTLOADER学习1

序言 最近计划学习stm32的BOOTLOADER学习,把学习过程记录下来 因为现在网上STM32C8T6还是比较贵的,根据我的需求flash空间小一些也可以,所以我决定使用stm32c6t6.这个芯片的空间是32kb的。 #熟悉芯片内部的空间地址 1、flash ROM&#xff1a; 大小32KB&#xff0c;范围&#xf…
暂无图片
编程学习 ·

通过awk和shell来限制IP多次访问之学不会你打死我

学不会你打死我 今天我们用shell脚本&#xff0c;awk工具来分析日志来判断是否存在扫描器来进行破解网站密码——限制访问次数过多的IP地址&#xff0c;通过Iptables来进行限制。代码在末尾 首先我们要先查看日志的格式&#xff0c;分析出我们需要筛选的内容&#xff0c;日志…
暂无图片
编程学习 ·

Python - 如何像程序员一样思考

在为计算机编写程序之前&#xff0c;您必须学会如何像程序员一样思考。学习像程序员一样思考对任何学生都很有价值。以下步骤可帮助任何人学习编码并了解计算机科学的价值——即使他们不打算成为计算机科学家。 顾名思义&#xff0c;Python经常被想要学习编程的人用作第一语言…
暂无图片
编程学习 ·

蓝桥杯python-数字三角形

问题描述 虽然我前后用了三种做法&#xff0c;但是我发现只有“优化思路_1”可以通过蓝桥杯官网中的测评&#xff0c;但是如果用c/c的话&#xff0c;每个都通得过&#xff0c;足以可见python的效率之低&#xff08;但耐不住人家好用啊&#xff08;哭笑&#xff09;&#xff09…