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

本文涉及的产品
云原生网关 MSE Higress,422元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
注册配置 MSE Nacos/ZooKeeper,118元/月
简介: 本文来自中生代技术群的分享。本文作者陈爱珍具有多年企业级系统的应用运维及分布式系统实战经验。现专注于容器、微服务及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


目录
相关文章
|
16天前
|
弹性计算 API 持续交付
后端服务架构的微服务化转型
本文旨在探讨后端服务从单体架构向微服务架构转型的过程,分析微服务架构的优势和面临的挑战。文章首先介绍单体架构的局限性,然后详细阐述微服务架构的核心概念及其在现代软件开发中的应用。通过对比两种架构,指出微服务化转型的必要性和实施策略。最后,讨论了微服务架构实施过程中可能遇到的问题及解决方案。
|
26天前
|
Cloud Native Devops 云计算
云计算的未来:云原生架构与微服务的革命####
【10月更文挑战第21天】 随着企业数字化转型的加速,云原生技术正迅速成为IT行业的新宠。本文深入探讨了云原生架构的核心理念、关键技术如容器化和微服务的优势,以及如何通过这些技术实现高效、灵活且可扩展的现代应用开发。我们将揭示云原生如何重塑软件开发流程,提升业务敏捷性,并探索其对企业IT架构的深远影响。 ####
40 3
|
1月前
|
Cloud Native 安全 数据安全/隐私保护
云原生架构下的微服务治理与挑战####
随着云计算技术的飞速发展,云原生架构以其高效、灵活、可扩展的特性成为现代企业IT架构的首选。本文聚焦于云原生环境下的微服务治理问题,探讨其在促进业务敏捷性的同时所面临的挑战及应对策略。通过分析微服务拆分、服务间通信、故障隔离与恢复等关键环节,本文旨在为读者提供一个关于如何在云原生环境中有效实施微服务治理的全面视角,助力企业在数字化转型的道路上稳健前行。 ####
|
16天前
|
Java 开发者 微服务
从单体到微服务:如何借助 Spring Cloud 实现架构转型
**Spring Cloud** 是一套基于 Spring 框架的**微服务架构解决方案**,它提供了一系列的工具和组件,帮助开发者快速构建分布式系统,尤其是微服务架构。
129 68
从单体到微服务:如何借助 Spring Cloud 实现架构转型
|
15天前
|
运维 监控 持续交付
微服务架构解析:跨越传统架构的技术革命
微服务架构(Microservices Architecture)是一种软件架构风格,它将一个大型的单体应用拆分为多个小而独立的服务,每个服务都可以独立开发、部署和扩展。
139 36
微服务架构解析:跨越传统架构的技术革命
|
18天前
|
设计模式 负载均衡 监控
探索微服务架构下的API网关设计
在微服务的大潮中,API网关如同一座桥梁,连接着服务的提供者与消费者。本文将深入探讨API网关的核心功能、设计原则及实现策略,旨在为读者揭示如何构建一个高效、可靠的API网关。通过分析API网关在微服务架构中的作用和挑战,我们将了解到,一个优秀的API网关不仅要处理服务路由、负载均衡、认证授权等基础问题,还需考虑如何提升系统的可扩展性、安全性和可维护性。文章最后将提供实用的代码示例,帮助读者更好地理解和应用API网关的设计概念。
47 8
|
1月前
|
Dubbo Java 应用服务中间件
服务架构的演进:从单体到微服务的探索之旅
随着企业业务的不断拓展和复杂度的提升,对软件系统架构的要求也日益严苛。传统的架构模式在应对现代业务场景时逐渐暴露出诸多局限性,于是服务架构开启了持续演变之路。从单体架构的简易便捷,到分布式架构的模块化解耦,再到微服务架构的精细化管理,企业对技术的选择变得至关重要,尤其是 Spring Cloud 和 Dubbo 等微服务技术的对比和应用,直接影响着项目的成败。 本篇文章会从服务架构的演进开始分析,探索从单体项目到微服务项目的演变过程。然后也会对目前常见的微服务技术进行对比,找到目前市面上所常用的技术给大家进行讲解。
52 1
服务架构的演进:从单体到微服务的探索之旅
|
22天前
|
消息中间件 运维 Kubernetes
后端架构演进:从单体到微服务####
本文将探讨后端架构的演变过程,重点分析从传统的单体架构向现代微服务架构的转变。通过实际案例和理论解析,揭示这一转变背后的技术驱动力、挑战及最佳实践。文章还将讨论在采用微服务架构时需考虑的关键因素,包括服务划分、通信机制、数据管理以及部署策略,旨在为读者提供一个全面的架构转型视角。 ####
33 1
|
25天前
|
弹性计算 运维 开发者
后端架构优化:微服务与容器化的协同进化
在现代软件开发中,后端架构的优化是提高系统性能和可维护性的关键。本文探讨了微服务架构与容器化技术如何相辅相成,共同推动后端系统的高效运行。通过分析两者的优势和挑战,我们提出了一系列最佳实践策略,旨在帮助开发者构建更加灵活、可扩展的后端服务。
|
25天前
|
消息中间件 运维 Cloud Native
云原生架构下的微服务优化策略####
本文深入探讨了云原生环境下微服务架构的优化路径,针对服务拆分、通信效率、资源管理及自动化运维等核心环节提出了具体的优化策略。通过案例分析与最佳实践分享,旨在为开发者提供一套系统性的解决方案,以应对日益复杂的业务需求和快速变化的技术挑战,助力企业在云端实现更高效、更稳定的服务部署与运营。 ####
下一篇
DataWorks