提高应用程序可用性的五个要点

管理员账号

2017-06-30

小编说:可用性问题通常会在你最想不到的地方出现,许多问题都是系统性的问题,而不仅仅是代码的问题。本文提出了五个要点能够帮助你的系统在规模增长的同时保证高可用性。相关图书推荐《可伸缩架构:面向增长应用的高可用》

构建一个高可用、可伸缩的应用程序不是一件容易的事,也不会是天上掉下来的馅饼。问题总会以你从未预期的方式出现,让你精心设计的功能对所有用户都停止工作。

这些可用性问题通常会在你最想不到的地方出现,甚至一些最严重的问题会来自于最不可能出现的地方。

一次简单的图标故障

这发生在我亲身经历的一个应用程序中,是一次因为忽视依赖故障的典型案例。该程序向用户提供了一个服务,为每个页面顶部提供一个自定义的图标,来表示当前登录的用户。这个图标由一个第三方系统负责生成。
有一天,这个生成图标的第三方系统发生了故障。我们的应用程序却假设该系统总是会正常运行,因此并不知道如何处理这种情况。结果是,我们的应用程序也跟着发生故障。我们整个系统仅仅是因为图标生成这样一个非常小的“功能”,导致无法提供任何服务。

如何才能避免这样的问题呢?如果我们能够预料到第三方系统可能发生故障,就可以在设计过程中考虑到这个故障发生的场景,从而发现我们的应用程序也会随之发生故障。这样,我们就能添加一些逻辑来检查第三方服务,在问题发生时删除图标,或者在问题发生时捕获错误,避免它传递下去并影响页面的其他部分。

一次小小的检查和一些错误恢复机制,就可以帮助应用程序保持正常运行。否则,我们的应用程序就会经历严重的服务中断。

所有这一切都是因为缺少了一个图标。

没人有能够预料到问题会在何处发生,也不可能依靠测试来发现所有这些问题。许多问题都是系统性的问题,而不仅仅是代码的问题。

为了发现这些可用性的问题,我们需要后退一步,系统地去了解应用程序的运行机制。以下是五个你可以关注、并且应当关注的要点,它们能够帮助你的系统在规模增长的同时保证高可用性:

  • 时刻考虑应对故障
  • 时刻考虑如何伸缩
  • 缓和风险
  • 监控可用性
  • 以可预期及明确的方式来处理可用性问题

让我们来详细讲解其中的每一个要点。

要点1 :时刻考虑应对故障

正如Amazon 的CTO Werner Vogels 所说,“所有事情每时每刻都会失败”。你应当提前为应用程序和服务发生故障而做出计划。问题迟早会产生。不过现在,我们要讲的是如何解决它。

假设你的应用程序发生了故障,那么它是如何发生的?当你构建系统的时候,应当在设计和实现的方方面面都考虑可用性。例如:

设计

你有考虑过任何设计模式吗?你有使用它们来帮助你提升软件的可用性吗?

通过使用一些设计模式,例如捕获底层异常、重试逻辑和断路器,可以帮助你捕获错误并尽可能避免影响其他功能。这样,你就能够限制问题的影响范围,即使应用程序的某些部分出现问题,依然能够提供其他一些有用的功能。

依赖

如果你依赖的组件出现了故障,你会怎样做?你如何进行重试?如果问题是一个无法恢复的(硬件)故障,你会怎样做?如果是一个可恢复的(软件)故障呢?

断路器模式在处理依赖故障时非常有用,因为它们可以降低依赖故障对你的系统的影响。

如果没有断路器,你可能会因为依赖故障而降低系统的性能(例如,需要一个很长的超时机制来检测故障)。而使用了断路器,你可以“放弃”并停止使用某个依赖,直到你确认它已经恢复了正常工作。

用户

如果出现问题的原因是系统的某个用户,你会怎样做?你能够处理海量的请求吗?你能够限制海量的流量吗?你能够处理传入的垃圾数据吗?如果数据量非常大,你会怎样做?

有些时候,拒绝式服务可能来自于“友方”。例如,用户可能会因为看到一个临时活动,而导致大量请求增加。或者,用户程序中的一个bug,可能导致他们向你的应用程序拼命地发送请求。如果这样的事情发生了,你会怎样做?流量突增会让你的应用程序宕机吗?或者你能否检测出这种问题,通过限制请求的速度来降低或者消除它们的影响?

要点2 :时刻考虑如何伸缩

你的系统现在正常运行,并不意味着它明天还能够继续正常运行。大多数Web 应用程序的流量都是在不断增加的。一个今天产生一定流量的网站,明天可能会产生远比你想象大得多的流量。当你构建系统时,不要只考虑当前的流量,要考虑未来的流量。

具体一点,这可能意味着:

设计出能够增加数据库数量和容量的架构。

考虑限制你的数据伸缩的原因。当数据库达到容量极限的时候会发生什么?你需要确认这些限制因素并在到达极限之前解决它们。

你应当能够很容易地添加额外的应用程序服务器。这通常需要仔细考虑在何处和如何来维护状态,以及流量是如何路由的。

将静态流量导向离线提供方。这样你的系统只需要处理必要的动态流量。使用外部的内容分发网络(CDN)不仅可以降低网络需要处理的流量,也能够利用CDN 的伸缩效率将静态内容更快地分发给用户。

考虑是否可以静态生成一些动态资源。通常来说,看上去动态显示的内容实际上大多数是静态的,并且生成静态内容可以让你的应用程序提高可伸缩性。这种“应该静态的动态资源”有些时候隐藏在你想象不到的地方,如下文中所述。

究竟内容应该是静态的还是动态的?

通常,看上去是动态的内容实际上大多数是静态的。设想一个网站上常见的顶部导航栏,绝大多数时候,其中的内容都是静态的,但是偶尔也会出现一些动态的内容。

例如,如果你没有登录,页面的顶部可能会显示“请登录”,如果你已经登录了则显示“你好,Lee”(当然前提是你的名称是Lee)。

这是否意味着整个页面都必须动态生成呢?显然不是。除了页面的登录/ 问候部分,其他部分都是静态的,通过CDN 可以轻松地进行分发并节省你的计算资源。

当导航栏中大多数内容都是静态内容时,你可以在用户的浏览器中动态地将变更内容添加到页面上(例如根据具体情况添加“请登录”或者“你好,Lee”的内容)。通过将这些动态数据进行分组,并与静态内容加以区分,可以提高Web 页面的性能,降低应用程序需要处理的动态数据量。这样可以提高可伸缩性,并最终提高可用性。

要点3 :缓和风险

保持系统高可用需要消除系统中的风险。当系统发生故障时,通常我们已经在这之前将故障原因确定为了风险。因此,确定风险是提高可用性的一个重要方法。所有的系统中都存在以下这些风险:

  • 存在系统崩溃的风险
  • 存在数据库崩溃的风险
  • 存在返回结果不正确的风险
  • 存在网络连接失败的风险
  • 存在新部署的软件功能出现故障的风险

保持系统高可用需要消除风险。但是当系统变得越来越复杂时,消除所有风险也变得越来越不可能实现。保持一个大型系统高可用,更多的是来管理系统的风险,知道这些风险是什么,哪些风险是可接受的,以及你能够做什么来缓和风险。

我们称之为风险管理,它是构建高可用系统的核心内容。

风险管理中的一个部分是风险缓和。风险缓和指的是当问题发生时,我们知道如何去尽可能降低问题所带来的影响。缓和意味着即使当服务和资源不可用时,依然尽可能确保你的系统以最好的、最完整的状态工作。风险缓和需要考虑哪些事情可能会出错,并且立即制订相应的计划,以便当问题发生时能够提供相应的解决方案。

示例:风险缓和——一个没有搜索功能的网上商店

假设有一个售卖T 恤的网上商店。它是一个很常见的在线商店,你可以在它的首页上浏览T 恤,跳转到其他页面查看不同的T 恤分类,并且可以搜索指定风格和类型的T 恤。

为了实现搜索的功能,通常这类网上商店需要调用一个独立的搜索引擎,这可能是一个单独的服务,或者甚至由第三方的搜索服务提供。

但是,因为搜索功能是一个独立的功能,你的系统就存在搜索服务不可用的风险。你的风险管理计划需要确定出该问题,并且将“搜索引擎失败”列为风险之一。

如果没有风险缓和计划,当搜索服务失败时,可能会产生一个错误页面,或者返回不正确或无效的结果——不管怎样,它都会带来很差的用户体验。

这个示例中的风险缓和计划可能是这样的:

我们知道最受欢迎的T 恤是红色条纹T 恤,60% 访问网站的用户最终都停留在(并很可能最后会购买)这个产品上。因此,如果搜索服务停止了,我们可以显示一个“很抱歉”的页面,下方是我们最受欢迎的T 恤列表,其中就包括红色条纹T 恤。

这会鼓励遇到这个错误页面的用户,继续浏览别人曾经感兴趣的T 恤。

此外,我们还可以显示一个“下一次购买享受10% 折扣”的优惠券,这样就可以鼓励之前没有进行购买的用户,在搜索服务恢复正常后,继续回到我们的网站上进行购买。

示例演示了什么是风险缓和,而确认风险、确定该如何处理风险,以及如何实现这些缓和措施的过程则被称为风险管理。

风险管理经常会暴露应用程序中未知的、需要立即修复的问题。它还可以用来处理已知的故障问题,减少故障恢复时间或者降低严重性。

可用性和风险管理息息相关。构建一个高可用的系统,主要就是要考虑如何管理风险。

要点4 :监控可用性

除非你看到问题发生,否则你不会知道应用程序中存在着问题。你应当确保对应用程序进行了适当的监控,以便可以从外部和内部两个视角来观察应用程序的运行状况。

监控的程度取决于应用程序的特点和要求,但是通常必须具备以下这些监控。

服务器监控

监控服务器的健康状况,并且确保它们始终在有效运行。

配置变化监控

监控系统配置的变化,以便确定它们对应用程序的影响。

应用程序性能监控

深入了解你的应用程序和服务,确保它们按照预期运行。

人为测试

从用户的角度来实时检测应用程序的运行情况,以便在用户真正发现问题之前发现它们。

报警

当问题发生时通知相关人员,以便使问题可以得到快速有效的解决,将对用户的影响降低到最小。

如今市面上有许多非常优秀的监控系统,包括免费和付费的服务。我个人推荐NewRelic,它提供了之前提到的所有监控和报警能力。作为一款软件即服务(SaaS)的软件,它能够支持任何规模的应用系统的监控需求。当你对应用程序和服务进行监控之后,请开始寻找它们的运行趋势。当你明确了一定的趋势之后,可以开始寻找一些异常值,将它们作为可能存在的可用性问题。你可以利用这些异常值,在系统发生故障之前通过监控工具来发送警报。除此之外,你还可以在系统增长过程中时刻进行跟踪,确保可伸缩性计划的实施。

你应当为服务间通信建立内部的、私有的运行目标,并持续对它们进行监控。通过这种方式,当出现任何与性能或者可用性相关的问题时,你都可以快速诊断出哪个服务或者系统出现了问题,并定位问题的原因。此外,你可以发现一些“热点”——即性能超出预期的地方,以及针对这些问题制订相应的开发计划。

要点5 :以预测和确定的方式来应对可用性问题

如果你对监控中所发生的问题置之不理,那么监控系统就毫无用处。这意味着当问题发生时必须要发出报警,这样你才能有所行动。除此之外,你应当建立整个团队都遵循的流程,帮助诊断问题,并轻松修复常见的故障问题。

例如,如果某个系统无法响应,你可能会有一系列措施来解决。这其中可能包括运行一个测试来诊断问题原因,重启一个已知会导致系统无法响应的守护进程,或者当其他手段都失败时重启整个服务器。为常见的故障问题提供标准化流程可以降低系统不可用的时间。此外,它们还可以提供更多有用的诊断信息,帮助工程师团队找到常见问题的根本原因。

当触发某个服务的报警时,该服务的负责人必须是第一个被通知到的。毕竟,他们负责修复自己服务中的所有问题。但是,其他与之紧密相关或依赖的团队也应当收到报警信息。例如,如果某个团队使用了一个特殊服务,他们希望知道该服务什么时候出现故障,从而在问题发生时能够更加主动地保证自己的系统不受影响。

这些标准的流程和办法应当被写进支持手册中,团队中每个负责人人手一份。这本支持手册还应该包含相关系统和服务的联系人列表,以及当无法用简单手段解决问题时,需要上报问题的联系人列表。

所有这些流程、办法以及支持手册都应该提前准备好,以便当服务出现问题时,值班人员能准确知道如何在不同的情况下进行操作,快速恢复服务。这些流程和办法之所以非常有效,是因为故障通常都发生在一些不太方便的时间点,例如午夜或者周末这些效率比较低下的时间。这些建议可以帮助你的团队更聪明、更安全地将系统恢复到可运行状态。

做好准备

没人能够预测到可用性问题在什么地方、什么时间发生。但是你可以假设它们会发生,尤其是当你的系统面临越来越多的用户需求,变得越来越复杂的时候。提前做好处理可用性问题的准备,是降低问题出现概率和严重性的最佳方法。

读者评论

相关专题

相关博文

  • spring-retry重试与熔断详解—《亿级流量》内容补充

    spring-retry重试与熔断详解—《亿级流量》内容补充

    张开涛 2017-05-02

    本文是《亿级流量》第6章 超时与重试机制补充内容。 spring-retry项目实现了重试和熔断功能,目前已用于SpringBatch、Spring Integration等项目。 RetryOperations定义了重试的API...

    张开涛 2017-05-02
    390 0 1 1
  • #小编推书#一个扣人心弦又趣味横生的侦探缉凶故事

    管理员账号 2017-02-27

    小编说 《算法神探:一部谷歌首席工程师写的CS小说》围绕程序设计典型算法,精心编织了一个扣人心弦又趣味横生的侦探缉凶故事。小说主人公运用高超的搜索技巧和精深的算法知识,最终识破阴谋、缉拿元凶。其间,用二分搜索搜查走私船、用搜索树跟踪...

    管理员账号 2017-02-27
    46 0 0 0
  • 组织架构适配下的敏捷开发

    管理员账号 2017-02-27

    小编说:本文将会讨论如何协调公司内各个工程师团队之间的合作,从而高效地保持系统的弹性和灵活性,以满足敏捷开发的需求。本文选自《Node.js微服务》。 如果一个公司采用微服务来构建软件系统,那么每个干系人都需要参与决策。 微服务...

    管理员账号 2017-02-27
    68 0 0 0