如何提高微服务架构的可用性

本文涉及的产品
注册配置 MSE Nacos/ZooKeeper,118元/月
云原生网关 MSE Higress,422元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 本文来自中生代技术群的分享。本文作者陈爱珍具有多年企业级系统的应用运维及分布式系统实战经验。现专注于容器、微服务及devops落地的研究与实践。本文将为大家分享如何提高微服务架构的可用性,精彩不容错过。

业界通常用多少个9来衡量系统的可用性,如99.99%表示一年中有1小时左右的不可用时间。任何一个服务的可用性都不会是100%,意味着在服务运行时间里还是有可能发生故障。当把功能集中且运行在同一个应用中的单体架构拆分成多个相互独立的微服务架构后,虽然可以降低一损俱损的全局性故障风险,但由于微服务之间存在大量的依赖关系, 随着微服务个数的增多,依赖关系也将会变得越来越复杂,而且每个微服务都有可能发生故障,如果不能做好相互依赖的隔离,避免故障的连锁反应,结果可能比单体更糟糕。假设有100个微服务,并且每个微服务只会发生1种故障,那么总共会有2100种不同的故障场景,而每个微服务自身可能不止1种故障。当某个微服务发生故障时,如何确保不会导致其他依赖的微服务不可用, 如何确保系统自动降级把发生故障的微服务排除出去,如何确保故障不会扩展到整个系统? 那么如何有效确保微服务架构的可用性将会成为挑战。


下图是一个简化的用户请求示意图,设定一个用户请求依赖5个微服务的协作完成(pod为K8S容器框架中的定义,为一组相同功能的容器)。
  75ac4a93a47a5ba0f4fea001278fbe487306f348
在一开始每个依赖的Service都是正常的,现假设有一个Service异常了,这时可能会有三种情况:
1. 这个请求成功,假设因网络异常或宕机导致Service C某个节点不可用,但有高可用节点取代了这个失败节点,这时Service C不受影响,依然可用,如下图所示:
  4d87f2b4a4778f0136a098833399c5fbfeaf5bd5
2. 这个请求是成功的,假设是Service D故障,而这个Service不是关键性的,运行失败也可以继续进行,比如注册用户需要调用一个服务发送注册成功的邮件给用户,如果发邮件的这个Service不可用,但不会影响用户的注册,所以用户注册还是会成功,邮件可以等服务恢复后再发送,只有时间上的延迟。这时Service A不受影响,依然可用,如下图所示:
  fb7bfc44a31afee2a08a0baba79b3a828338cb40
3. 这个请求失败,比如异常的节点是Service E,而Service E是代码级逻辑异常,所有高可用节点都不可用,这时需要将Service E进行依赖隔离,否则ServiceA可能会受到ServiceE的影响而不可用。需要做一些措施确保Service A不会受影响,依然可用,如下图所示:
9f092a88ab2ab3438dcf39fc8c4def923d5fe27d
可以从以下几个策略可以提高微服务架构的可用性:
1) 失效转移
提高服务的高可用性,最基本的原则就是消除单点,通过负载均衡技术构建集群,所有的集群节点都是无状态且完全对等的。如上面讲的第1种情况。当一个节点异常时,负载均衡服务器会把用户发送的访问请求发送到可用的节点上。对用户来说,某个节点异常是无感的,用户请求会透明的转移到了可用的节点上执行。

2) 异步调用
避免一个服务失败导致整个应用请求失败很重要的是使用异步调用。如上面讲的第2种情况。如果采用的是同步调用,当邮件服务异常时,会导致其他两个服务也无法执行,最终导致用户注册失败。如果采用异步调用,Service A把用户注册信息发送给消息队列后立即返回用户注册成功的响应,虽然邮件服务不能用,但是写数据库的服务,权限开通等服务都能正常执行。所以即使邮件不能发送成功,也不会影响其他服务的执行,用户注册可顺利完成。

3) 依赖隔离
用户请求发送给Service A,Service A分配线程资源通过网络远程调用其他的Service,假设调用Service E发生异常时,Service A中对Service E调用的线程就可能会响应慢或僵死,而线程是系统的资源,如果短时间内得不到释放,在高并发的情况下资源就会被耗尽,结果会导致Service A也不可用,虽然其他的服务依然可用。
3a36239456eafb0218e603ab1d722d086a0e7ec8
Service A的资源是有限的,比如Service A启动时分配了400个线程,当400个线程都因调用Service E时异常不能及时正常的释放,如线程死锁,响应时间慢,导致 400个线程全部僵死在调用Service E上,这里Service A就没有空闲的线程来接收新的用户请求,这时就会导致Service A挂起或僵死。所以避免Service A被依赖的服务拖垮就是要确保Service A的线程资源不会被调用的依赖服务耗尽,在 《Release It!》一书中总结了非常重要的两条方法: 设置超时和使用断路器。

设置超时
在应用中设置服务调用的超时时间后,一旦线程的执行时间超过了所设置的时间,就抛出异常信息,自动断开连接,这样服务的线程就不会都长时间僵死在调用异常的服务上,导致没有空闲线程接收新的用户请求,可以避免Service A因为调用Server E 异常而被拖垮,自身不可用。所以通过网络调用外部依赖服务时,都必须设置超时。

使用断路器
断路器大家都不陌生,家里电表在电流过载或者短路时就会跳闸,如果不跳闸,电路就不断开,电线就会升温,造成火灾。有了断路器之后,电流过载时就会自动跳闸断开电路,避免引起更大的灾难。在程序中也是如此,当知道服务调用某个依赖服务有大量超时的时候,再让新的请求去访问也只会超时,并不能得到想到的结果,还会消耗现有资源,增加负载,导致服务不可用。这个时候使用断路器就能避免这种资源浪费,在自身服务和依赖服务之间放一个断路器,通过断路器的监控实时统计访问的状态,当访问超时或者失败达到某个阈值的时候(如50%请求超时,或者连续20次请失败),就打开断路器,那么后续的请求就直接返回失败,而不是一个长时间的等待,再根据一个时间间隔(如30秒)或请求超时的情况(如0%的超时)尝试关闭断路器(或者更换保险丝),看依赖是否恢复服务了。

一个服务依赖多个服务时,如果其中一个非核心的依赖不可用,通过设置超时和使用断路器,可以确保Service A在调用异常的Service E并不会导致自身的异常,在大部分情况下服务还能健康运转,可以很好的做到依赖隔离。如下图所示:
a475b6d1e95cacdc23071ea6ae38b7d5c297b93e
4)设置限流
在服务访问的高峰期可能因为大量的并发导致性能下降,严重时将会有大量的请求排队,可能会导致服务宕机。为了保证应用的可用性,可拒绝低优先级的调用,让高优先级的请求成功,避免所有调用都失败的情况,并且为每个依赖服务提供一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队,可以加速失败判定时间。这样的结果是有些用户可以访问,而有些用户失败,但失败的用户重新访问又可以是正常的访问。这样能确保服务的可用性,而不是完全不可用。

虽然有了上面的一些可提高系统可用性的措施,但系统是复杂的,一个简单的修复都有可能造成不可想像的后果,且系统又是动态的,有些系统可能一天都发布几次,几十次。在这种情况下 故障依然是不可避免的。比起半夜深睡或正在享受节假日的美好时光时系统故障来当救火队员,会做更多的措施来提高系统的可用性。比如在某些企业里会定期举行生产环境的故障应急演练。过去都是在业务低峰时进行人为故障测试高可用方案是否生效,包括主机,网络,应用,存储等每一层架构都进行演练,而现在也逐渐的在正常的生产时间进行故障应急演练,检查系统的高可用性。问题在于可能在演练时能够立即恢复,但真实故障发生时还是会出现长时间故障得不到恢复的情况。一个是演练是按照已知的场景制定的方案实施,二是演练的范围基本是高可用节点的切换或灾备系统的切换,第三个问题是这个演练是人为操作,需要全员的参与,并不会频繁的举行。但系统是动态的,这次是高可用的,不代表下周,或下个月还是高可用的。


当单体架构变成微服务架构后,应用层的演练就会变得复杂,就像前面提到的,如果每个服务只有一个故障可能都会有2100种不同。因此需要有一种自动的故障测试方式来回避微服务化后演练实施的可操作性。Netflix公司提出了一种自动故障测试的方案来提高微服务架构的可用性。这个测试方案也是在生产环境中进行,而故障测试的最终目的,是为了当真的有故障发生时,生产环境不会停止服务,并且整套系统可以在没有人为干预的情况下,非常优雅地通过降级将发生故障的部分组件排除出去。他们认为如果只在测试环境中测试,而真实生产环境的业务压力,业务场景,环境配置、网络性能和硬件性能都没有测试过,当故障在生产环境中真实发生时发现缓解问题的方案可能会失效。而且这个测试只在工作时间运行,这样工程师可以得到告警并及时响应。


Netflix通过Peter Alvaro在论文 《路径驱动的故障注入(Lineage-driven fault injection)》中提到了一套名为“Molly”的算法和自身的故障注入测试FIT(failure injection testing)实现了这套安全地自动化故障注入测试。Molly是从一套系统的无故障状态出发,然后试图去回答说“系统是如何达到目前这种无故障的状态的?”简单举例介绍一下这个算法的原理。先利用自身的追踪系统绘制一个树来表示每个请求经过的所有的微服务,假设如下图所示:
  e9d8babee193fd1861274b31f351a96784e91c9c
                                                                        (A or R or P or B)

在最开始,上图中的四个节点都是必须的,且正常的。然后从这个正确输出反推,随机选择一个节点注入故障,找到并构建支持其正确性的逻辑链条图 。当节点注入故障后,这时可能有三种情况:


1.这个请求失败,我们已经找到一个节点会故障,从而我们可以删除未来的实验中包含这个故障。
2.这个请示是成功的-但这个失败的节点不是关键性的
3.这个请求成功,有高可用节点取代了这个失败
在这个例子中,首先在Ratings中注入失败,但请求是成功的。说明Rating失败并不会影响服务的使用,那就先把这个节点排除,重新绘制请求树:
e594b9a7f6982cd2f3a9b9dea1074e200a30a8f7
                                                             (A or P or B) and (A or P or B or R)

这时可以看到,请求可以通过(A or P or B)的方式实现,也可以通过  (A or P or B or R)的方式实现。接下来再在Playlist中注入故障,这时请求还是成功的,因为请求转发到备用节点上执行,这里将会有一个新的节点可以访问。
b2c83811692c1a25e2c3fd8dd6e08420ec528016
                                                    (A or PF or B) and (A or P or B) and (A or P or B or R)

这时可以更新公式,说明可以通过(A or PF or B) and (A or P or B) and (A or P or B or R)三种方式请求服务。然后通过这样不停的测试直到遍历完所有正确输出,没有失败的节点可以找到。


Molly没有规定怎么搜索空间,所以实现时会估算所有的方案,然后随机选择最小的方案的集合。比如,最后的方案可能是[{A}, {PF}, {B}, {P,PF}, {R,A}, {R,B} …]。先选择所有的单节点注入失败,再选择所的有双节点的组合注入失败,依此类推。


这个测试的目的是在影响大量成员前找到和修复故障,在生产环境上进行故障测试时,不能接受引起大量的问题节点。为了避免这个风险,只能在指定的范围构建测试,指定的范围包含两个关键的概念:故障范围(failure scope)和注入点(injection points)。故障范围指的是,把一次故障测试可能产生的影响,限制在一个可控的范围内,这个范围可以小到某个特定的用户或者设备,也可以大到所有用户的1%。而注入点指的是系统内计划会发生故障的组件,比如RPC层,缓存层,或者持久层。下图是这个测试的流程示意图:
             8e7567a195bd116d08bf79877f77df27dcbc7741
故障模拟测试从FIT服务把故障模拟元数据注入到Zuul(缘边网关服务)开始,如果请求符合故障范围(failure scope)则注入失败。这个故障可能是延迟服务调用,或达到持久层失败。每个被接触到的注入点(injection points)检查这个请求的上下文是否为指定要被注入故障的组件,如果是,在这个注入点模拟故障。

参考:
http://techblog.netflix.com/2016/01/automated-failure-testing.html
http://techblog.netflix.com/2014/10/fit-failure-injection-testing.html

作者简介:

陈爱珍,七牛云布道师。多年企业级系统的应用运维及分布式系统实战经验。现专注于容器、微服务及devops落地的研究与实践。

本文来自中生代技术交流群

微信公众号: freshmanTechnology


目录
相关文章
|
19天前
|
运维 Kubernetes Cloud Native
云原生技术浪潮下的微服务架构演进
在数字化转型的风潮中,云原生技术以其灵活性、可扩展性和弹性成为企业IT战略的核心。本文深入探讨了微服务架构如何借助云原生环境进行优化,并分析了容器化、服务网格等技术如何助力微服务更好地适应云原生生态。通过案例分析,我们揭示了微服务在现代云平台上的实践挑战与解决策略,同时对未来的技术趋势进行了预测。
36 0
|
2天前
|
监控 负载均衡 API
从单体到微服务:架构转型之道
【8月更文挑战第17天】从单体架构到微服务架构的转型是一项复杂而系统的工程,需要综合考虑技术、团队、文化等多个方面的因素。通过合理的规划和实施策略,可以克服转型过程中的挑战,实现系统架构的升级和优化。微服务架构以其高度的模块化、可扩展性和灵活性,为业务的持续发展和创新提供了坚实的技术保障。
|
11天前
|
Cloud Native 云计算 微服务
云原生时代:企业分布式应用架构的惊人蜕变,从SOA到微服务的大逃亡!
【8月更文挑战第8天】在云计算与容器技术推动下,企业分布式应用架构正经历从SOA到微服务再到云原生的深刻变革。SOA强调服务重用与组合,通过标准化接口实现服务解耦;微服务以细粒度划分服务,增强系统灵活性;云原生架构借助容器化与自动化技术简化部署与管理。每一步演进都为企业带来新的技术挑战与机遇。
38 6
|
9天前
|
设计模式 监控 API
探索微服务架构中的API网关模式
在微服务的宇宙里,API网关是连接星辰的桥梁。它不仅管理着服务间的通信流量,还肩负着保护、增强和监控微服务集群的重任。本文将带你走进API网关的世界,了解其如何成为微服务架构中不可或缺的一环,以及它在实际应用中扮演的角色和面临的挑战。
|
12天前
|
缓存 监控 API
【微服务战场上的神秘守门人】:揭秘API网关的超能力 —— 探索微服务架构中的终极守护者与它的神奇魔法!
【8月更文挑战第7天】随着微服务架构的流行,企业应用被拆分为围绕特定业务功能构建的小型服务。API网关作为微服务间的通信管理核心,对请求进行路由、认证、限流等处理,简化客户端集成并提升用户体验。以电商应用为例,通过Kong部署API网关,配置产品目录等服务的API及JWT认证插件,确保安全高效的数据交互。这种方式不仅增强了系统的可维护性和扩展性,还提供了额外的安全保障。
29 2
|
17天前
|
运维 监控 负载均衡
探索微服务架构中的API网关
在现代软件开发中,微服务架构已成为设计灵活、可扩展系统的首选方法。本文将深入探讨API网关的核心作用,包括它如何简化客户端与微服务之间的交互,提供请求路由、负载均衡、认证、限流及监控等关键功能。我们将通过实际案例分析,揭示API网关在提升系统性能、增强安全性和提高开发效率方面的重要性。
|
15天前
|
负载均衡 监控 API
探索微服务架构中的API网关模式
在微服务架构的海洋中,API网关扮演着枢纽的角色。它不仅是客户端请求的接收者,也是各个微服务间通信的协调者。本文将深入探讨API网关的设计原则、实现策略以及它在微服务生态中的重要性。我们将通过实际案例分析,了解API网关如何优化系统性能、提高安全性和简化客户端与服务的交互。
31 4
|
15天前
|
运维 监控 负载均衡
探索微服务架构中的API网关设计
在微服务架构的复杂性中,API网关作为客户端和后端服务间的桥梁,扮演着至关重要的角色。本文将深入探讨如何设计一个高效、可扩展且安全的API网关,包括处理请求转发、负载均衡、身份验证、监控与日志记录等核心功能,并讨论如何在保障性能的同时确保系统的高可用性和安全性。通过具体案例,我们将了解API网关在实际生产环境中的实现方式及其对整个微服务生态系统的影响。
37 3
|
15天前
|
Kubernetes 监控 开发者
探索后端开发的新境界:微服务架构与容器化技术
在数字化时代的浪潮中,后端开发不断演进,涌现出创新的架构与技术。本文深入探讨了微服务架构和容器化技术如何重塑后端开发,提升系统的可维护性、可扩展性和部署效率。通过实际案例分析,我们揭示了这些技术背后的原理,并提供了实施的最佳实践和面临的挑战,为后端开发者提供一条清晰的技术升级路径。
39 3
|
20天前
|
负载均衡 监控 API
探索微服务架构中的API网关模式
【7月更文挑战第30天】在微服务架构的复杂网络中,API网关扮演着交通枢纽的角色,不仅简化了客户端与各微服务的交互,还提升了系统的安全性和可维护性。本文将深入探讨API网关的设计原则、核心功能以及在实际应用中的部署策略,旨在为后端开发者提供一套完整的API网关解决方案。