第11章 规模化微服务

 
11.1故障无处不在
 
我们知道事情可能会出错,硬盘可能会损坏,软件可能会崩溃。任何读过“分布式计算 的故障” ( https://en.wikipedia.org/wiki/Fallacies_of_Distributed_Computing)的人都会告诉 你,网络也是不可靠的。我们可以尽力尝试去限制引起故障的因素,但达到一定规模后, 故障难以避免。例如,现在的硬盘比以往任何时候都更可靠,但它们最终也会损坏。你的 硬盘越多,其中一个会发生故障的可能性就越大;从统计学来看,规模化后故障将成为必 然事件。
 
即使有些人不必考虑超大规模的情况,但是如果我们能够拥抱故障,那么就能够游刃有余 地管理系统。例如,如果我们可以很好地处理服务的故障,那么就可以对服务进行原地升 级,因为计划内的停机要比计划外的更容易处理。
 
我们也可以在试图阻止不可避免的故障上少花一点时间,而花更多时间去优雅地处理它。 我很惊讶地发现,许多组织使用流程和控制来试图阻止故障的发生,但实际上很少花费心 思想想如何更加容易地在第一时间从故障中恢复过来。
 
假设一切都会失败,会让你从不同的角度去思考如何解决问题。
 
11.2多少是太多
 
我们在第7章涉及过跨功能需求这一话题。跨功能需求就是,要考虑数据的持久性、服务 的可用性、吞吐量和服务可接受的延迟等这些方面。本章提到的许多技术,以及在其他地 方讨论过的方法,都能够帮助满足这些需求,但只有你自己知道需求本身到底是什么。
 
有一个自动扩容系统,能够应对负载增加或单节点的故障,这可能是很棒的,但对于一个 月只需运行一两次的报告系统就太夸张了,因为这个系统,即使宕机一两天也没什么大不 了的。同样,搞清楚如何做蓝/绿部署,使服务在升级时无需停机,对你的在线电子商务 系统来说可能会有意义,但对企业内网的知识库来说可能有点过头了。
 
了解你可以容忍多少故障,或者系统需要多快,取决于系统的用户。反过来,这会帮助你 了解哪些技术将对你有意义。也就是说,你的用户不是经常能阐明需求到底是什么,所以 你需要通过问问题来提取正确的信息,并帮助他们了解提供不同级别服务的相对成本。
 
如前所述,服务不同,这些跨功能需求也不一样,不过我建议你定义一些默认的跨功能需 求,然后在特定的用例中重载它们。当考虑是否以及如何扩展你的系统,以便更好地处理 负载或故障时,首先请尝试理解以下需求。
 
•响应时间/延迟
 
各种操作需要多长时间?我们可以使用不同数量的用户来测量它,以了解负载的增加会 对响应时间造成什么样的影响。鉴于网络的性质,你经常会遇到异常值,所以将监控的 响应目标设置成一个给定的百分比是很有用的。目标还应该包括你期望软件处理的并发 连接/用户数。所以,你可能会说:“我期望这个网站,当每秒处理200个并发连接时, 90%的响应时间在2秒以内。”
 
•可用性
 
你能接受服务出现故障吗?这是一个24/7服务吗?当测量可用性时,有些人喜欢查看 可接受的停机时间,但这个对调用服务的人又有什么用呢?对于你的服务,我只能选择 信赖或者不信赖。测量停机时间,只有从历史报告的角度才有用。
 
•数据持久性
 
多大比例的数据丢失是可以接受的?数据应该保存多久?很有可能每个案例都不同。例 如,你可能为了节省空间,选择将用户会话的日志只保存一年,但你的金融交易记录可 能需要保存很多年。
 
一旦有这些需求,你就会想要一种方式,系统性地持续测量。例如,可能你决定使用性 能测试,以确保系统性能满足可接受的目标,不过可能你也想要在生产环境上监控这些 数据。
 
11.3功能降级
 
构建一个弹性系统,尤其是当功能分散在多个不同的、有可能宕掉的微服务上时,重要的 是能够安全地降级功能。想象一下,我们电子商务网站上的一个标准的Web页面。要把网 站的各个功能组合在一起,我们需要几个微服务联合发挥作用。一个微服务可能显示出售 专辑的详细信息,另一个显示价格和库存数量。我们还需要展示购物车内容,这可能是另 一个微服务。现在,如果这些微服务中的任何一个宕掉,都会导致整个Web页面不可用, 那么我们可以说,该系统的弹性还不如只使用一个服务的系统。
 
我们需要做的是理解每个故障的影响,并弄清楚如何恰当地降级功能。如果购物车服务不 可用,我们可能会有很多麻烦,但仍然可以显示列表清单页面。也许可以仅仅隐藏掉购物 车,将其替换成一个新的图标“马上回来! ”。
 
对简单的单块应用程序来说,我们不需要做很多决定。系统不是好的,就是坏的。但对于 微服务架构,我们需要考虑更多微妙的情况。很多情况下,需要做的往往不是技术决策。 从技术方面我们可能知道,当购物车宕掉了有哪些处理方式,但除非理解业务的上下文, 否则我们不知道该采取什么行动。比如,也许关闭整个网站,也许仍然允许人们浏览物品 目录,也许把用户界面上的购物车控件变成一个可下订单的电话号码。对于每个使用多个 微服务的面向用户的界面,或每个依赖多个下游合作者的微服务来说,你都需要问自己: “如果这个微服务宕掉会发生什么? ”然后你就知道该做什么了。
 
通过思考每项跨功能需求的重要性,我们对自己能做什么有了更好的定位。现在,让我们 考虑从技术方面可以做的事情,以确保当故障发生时可以优雅地处理。
 
11.4架构性安全措施
 
有一些模式,组合起来被称为架构性安全措施,它们可以确保如果事情真的出错了,不会 引起严重的级联影响。这些都是你需要理解的非常关键的点,我强烈建议在你的系统中把 它们标准化,以确保不会因为一个服务的问题导致整个系统的崩塌。我们很快将看到应该 考虑的这些关键的安全措施,但在此之前,我想分享一个简短的故事,概述一下哪些事情 可能会出错。
 
我曾是一个项目的技术负责人,这个项目是构建一个在线的分类广告网站。网站本身需要 处理相当高的访问量,并获得了大量的业务收入。如图li-i所示,我们的核心应用程序是 处理一些分类广告本身的展示,同时代理调用其他服务以提供不同类型的产品。这其实是 一个绞杀者应用的例子,新系统拦截了对遗留应用程序的调用,并逐渐替代它们。作为这 个项目的一部分,也要逐步把这些遗留应用替换掉。我们刚刚迁移了访问量最多和收益最 大的产品,但剩余的大部分广告仍由许多旧的应用程序提供服务。无论是这些应用程序的 搜索数量,还是获得的收益,都非常大。
 
图11-1: 一个典型的新广告系统绞杀遗留系统的例子
我们的系统已经运行了一段时间,并且在一个不小的负载下表现良好。那时在高峰期,我 们每秒必须处理大约6000〜7000个请求,尽管大部分请求已经被应用程序服务器前的反向 代理缓存了,但产品搜索的(网站最重要的方面)绝大部分请求都没有被缓存,需要与服 务器有一个完整的往返通信。
 
一天,就在午餐高峰前,系统开始变慢,然后逐渐开始访问失败。我们在新的核心应用程 序上有某种程度的监控,它显示每个应用程序节点的CPU都达到100%的峰值,远高干平 常的、即使是高峰期的使用率。在很短的时间内,整个网站宕掉了。
 
我们找到了问题的原因,并恢复了网站。结果发现,下游的一个广告系统,也是最老的、 平常最不经常维护的系统之一,开始响应得非常缓慢。响应非常缓慢是最糟糕的故障模式 之一。如果一个系统宕掉了,你很快就会发现。但当它只是很慢的时候,你需要等待一段时间,然后再放弃。但无论故障原因是什么,我们创建了一个容易被故障级联影响的系 统。一个无法控制的下游服务,可以让整个系统宕掉。
 
当一个团队查看下游系统的问题时,其余的人开始查看我们的应用程序哪里出错了。我们 发现了几个问题。程序使用HTTP连接池来处理下游连接。连接池本身的线程,已经设置 了当用HTTP调用下游服务时会等待的时间。设置这样的超时本身很好,问题是因为缓慢 的下游系统,所有的worker都等了一段时间后再超时。当它们在等待时,更多的请求发送 到连接池要求worker线程。因为没有可用的worker,这些请求也被挂起。我们正在使用 的连接池,原来确实有一个worker等待的超时设置,不过默认是禁用的!这导致了一个超 长的阻塞线程队列。我们的应用程序任何时候通常只有40个并发连接。上述情况造成在 五分钟内连接数量达到大约800个,这最终导致系统宕掉。
 
更糟糕的是,我们调用这个出问题的下游服务向外提供的功能,只有低于5%的客户在使 用,并且获得的收入比这个比例还少。深入到细节中,我们发现,处理系统缓慢要比处理 系统快速失败困难得多。在分布式系统中,延迟是致命的。
 
即使我们连接池的超时设置是正确的,所有的出站请求还是共享一个HTTP连接池。这意 味着,即使其他的服务很健康,一个缓慢的服务就可能耗尽所有可用的worker。最后,很 明显下游服务是不健康的,但我们仍然一直发送通信。在这种情况下,这意味着,实际上 我们让情况变得更糟糕,下游服务都没有恢复的机会了。为了避免这种情况再次发生,我 们最终修复了以下三个问题:正确地设置超时,实现舱壁隔离不同的连接池,并实现一个 断路器,以便在第一时间避免给一个不健康的系统发送调用。
 
11.5反脆弱的组织
 
在《反脆弱》一书中,作者NassimTaleb认为事物实际上受益于失败和混乱。Ariel Tseitlin 用这个概念解释反脆弱的组织( http://queue.acm.org/detail.cfm?id=2499552) Netflix 是如何运作的。
 
像Netflix完全是基于AWS的基础设施一样,Netflix的经营规模也是众所周知的。这两个 因素意味着,它必须很好地应对故障。实际上Netflix通过引发故障来确保其系统的容错性。
 
一些公司喜欢组织游戏日,在那天系统会被关掉以模拟故障发生,然后不同团队演练如何 应对这种情况。我在谷歌工作期间,在各种不同的系统中都能遇到这种活动,并且我认 为经常组织这类演练对于很多公司来说都是有益的。谷歌比简单的模拟服务器故障更进 一步,作为年度 DiRT (Disaster Recovery Test,灾难恢复测试, http://queue.acm.org/detail. cfm?id=2371516)演习的一部分,它甚至模拟地震等大规模的自然灾害。Netflix也采用了 更积极的方式,每天都在生产环境中通过编写程序引发故障。
 
这些项目中最著名的是混乱猴子(Chaos Monkey),在一天的特定时段随机停掉服务器或机器。知道这可能会发生在生产环境,意味着开发人员构建系统时不得不为它做好准 备。混乱猴子只是Netflix的故障机器人猴子军队(Simian Army)的一部分。混乱大握 猩(Chaos Gorilla)用于随机关闭整个可用区(AWS中对数据中心的叫法),而延迟猴 子(Latency Monkey)在系统之间注人网络延迟。Netflix已使用开源代码许可证(https:// github.com/Netflix/SimianArmy)开源了这些工具。对许多人来说,你的系统是否真的健壮 的终极€证是,在你的生产环境上释放自己的猴子军队。
 
通过让软件拥抱和引发故障,并构建系统来应对,这只是Netflix做的一部分事情。它还知道 当失败发生后从失败中学习的重要性,并在错误真正发生时采用不指责文化。作为这种学习 和演化过程的一部分,开发人员被进一步授权,他们每个人都需要负责管理他的生产服务。
 
通过引发故障,并为其构建系统,Netflix已经确保它的系统能够更好地规模化以及支持其 客户的需求。
 
不是每个人都需要做到像谷歌或Netflix那样极致,但重要的是,理解分布式系统所需的思 维方式上的转变。事情将会失败。你的系统正分布在多台机器上(它们会发生故障),通 过网络(它也是不可靠的)通信,这些都会使你的系统更脆弱,而不是更健壮。所以,无 论你是否打算提供像谷歌或Netflix那样规模化的服务,在分布式架构下,准备好如何应对 各种故障的发生是非常重要的。那么我们需要做什么来应对系统故障呢?
 
11.5.1 超时
 
超时是很容易被忽视的事情,但在使用下游系统时,正确地处理它是很重要的。在考虑下 游系统确实已经宕掉之前,我需要等待多长时间?
 
如果等待太长时间来决定调用失败,整个系统会被拖慢。如果超时太短,你会将一个可能 还在正常工作的调用错认为是失败的。如果完全没有超,一个宕掉的下游系统可能会让整 个系统挂起。
 
给所有的跨进程调用设置超时,并选择一个默认的超时时间。当超时发生后,记录到日志 里看看发生了什么,并相应地调整它们。
 
11.5.2断路器
 
在自己家里,断路器会在电流尖峰时保护你的电子设备。如果出现尖峰,断路器会切断电 路,保护你昂贵的家用电器。你也可以手动使用断路器切断家里的部分电源,让电器安全 地工作。Michael Nygard在Release It!一书中,介绍了使用同样的想法作为软件的保护机 制会产生奇妙的效果。
 
想想我们之前分享的故事。下游的遗留广告应用程序在最终返回错误之前,响应非常慢。 即使我们正确地设置超时,也需要等待很长时间才能得到错误。接着我们等下次请求进来时将再次尝试,同样等待。下游服务发生故障已经够糟糕的了,它还让我们的系统变 得很慢。
 
使用断路器时,当对下游资源的请求发生一定数量的失败后,断路器会打开。接下来,所 有的请求在断路器打开的状态下,会快速地失败。一段时间后,客户端发送一些请求查看 下游服务是否已经恢复,如果它得到了正常的响应,将重置断路器。你可以在图11-2中看 到这个过程的概述。
 
图11-2:断路器的概述
 
如何实现断路器依赖于请求失败的定义,但当使用HTTP连接实现它们时,我会把超时或 5XX的HTTP返回码作为失败的请求。通过这种方式,当一个下游资源宕掉,或超时,或 返回错误码时,达到一定阈值后,我们会自动停止向它发送通信,并启动快速失败。当它 恢复健康后,我们会自动重新发送请求。
 
正确地设置断路器会有点棘手。你不想太轻易地启动断路器,也不想花太多时间来启动。 同样,你要确保在下游服务真正恢复健康后才发送通信。跟超时一样,我会选取一些合理 的默认值并在各处使用,然后在特定的情况下调整它们。
 
当断路器断幵后,你有一些选项。其中之一是堆积请求,然后稍后重试它们。对于一些场 景,这可能是合适的,特别是你所做的工作是异步作业的一部分时。然而,如果这个调用 作为同步调用链的一部分,快速失败可能更合适。这意味着,沿调用链向上传播错误,或 更微妙的降级功能。
 
如果我们有这种机制(如家里的断路器),就可以手动使用它们,以使所做的工作更加安 全。例如,如果作为日常维护的一部分,我们想要停用一个微服务,可以手动启动依赖它 的所有系统的断路器,使它们在这个微服务失效的情况下快速失败。一旦微服务恢复,我 们可以重置断路器,让一切都恢复正常。
 
11.5.3舱壁
 
Nygard在Release It中,介绍了另一种模式:舱壁(bulkhead),是把自己从故障中隔离 开的一种方式。在航运领域,舱壁是船的一部分,合上舱口后可以保护船的其他部分。所 以如果船板穿透之后,你可以关闭舱壁门。如果失去了船的一部分,但其余的部分仍完好 无损。
 
在软件架构术语中,有很多不同的舱壁可供我们考虑。结合我自己的经历,实际上我错过 了使用舱壁的机会。我们应该为每个下游服务的连接使用不同的连接池。这样的话,正如 我们在图11-3看到的,如果一个连接池被用尽,其余连接并不受影响。这可以确保,如果 下游服务将来运行缓慢,只有那一个连接池会受影响,其他调用仍可以正常进行。
 
关注点分离也是实现舱壁的一种方式。通过把功能分离成独立的微服务,减少了因为一个 功能的宕机而影响另一个的可能性。
 
看看你的系统所有可能出错的方面,无论是微服务内部还是微服务之间。你手头有舱壁可 以使用吗?我建议,至少为每个下游连接建立一个单独的连接池。不过,你可能想要更进 一步,也考虑使用断路器。
 
我们可以把断路器看作一种密封一个舱壁的自动机制,它不仅保护消费者免受下游服务问 题的影响,同时也使下游服务避免更多的调用,以防止可能产生的不利影响。鉴于级联故障的危险,我建议对所有同步的下游调用都使用断路器。当然,不需要重新创造你自己 的断路器。Netflix 的 Hystrix 库( https://github.com/Netflix/Hystrix)是一个基于 JVM 的断 路器,附带强大的监控。还有其他的基于不同技术栈的断路器实现,比如.NET的Polly ( https://github.com/App-vNext/Polly),或 Ruby 的 circuit_breaker mixin ( https://github.com/ wsargent/circuit—breaker) 。
图11-3:每个下游服务一个连接池,以提供舱壁
在很多方面,舱壁是三个模式里最重要的。超时和断路器能够帮助你在资源受限时释放它 们,但舱壁可以在第一时间确保它们不成为限制。例如,Hystrix允许你在一定条件下,实 现拒绝请求的舱壁,以避免资源达到饱和,这被称为减载(load shedding)。有时拒绝请求 是避免重要系统变得不堪重负或成为多个上游服务瓶颈的最佳方法。
 
11.5.4隔离
 
一个服务越依赖于另一个,另一个服务的健康将越能影响其正常工作的能力。如果我们 使用的集成技术允许下游服务器离线,上游服务便不太可能受到计划内或计划外宕机的 影响。
 
服务间加强隔离还有另一个好处。当服务间彼此隔离时,服务的拥有者之间需要更少的协 调。团队间的协调越少,这些团队就更自治,这样他们可以更自由地管理和演化服务。
 
11.6 幕等
 
对幂等操作来说,其多次执行所产生的影响,均与一次执行的影响相同。如果操作是幂等 的,我们可以对其重复多次调用,而不必担心会有不利影响。当我们不确定操作是否被执 行,想要重新处理消息,从而从错误中恢复时,幂等会非常有用。
 
让我们考虑一个简单调用的例子,当客户下一个订单后给他增加一些积分。我们以示例11-1所示的负载发起这个调用。
示例11-1:给一个账户增加积分
 
<credit>
 
<anount>100</anount>
 
<forAccount>1234</account>
 
</credit>
 
如果多次收到这个调用,我们会多次增加100点。因此,按照这种情况,这个调用不是幂 等的。然而,如示例11-2所示,当有更多的信息后,我们就可以让积分账户把这次调用变 成幂等操作。
 
示例11-2::添加更多的信息使这个调用变成幂等操作
<credit>
<amount>100</amount>
<forAccount>1234</account>
<reason>
<forpurchase>4567</forpurchase>
</reason>
</credit>
 
现在我们知道,这次信用与一个特定的订单4567相关。假如一个给定的汀单只能获得唯 一的积分,我们可以在不增加总积分的情况下,再次应用这个积分。
 
这种机制在基于事件的协作中也会工作得很好,尤其是当你有多个相同类型的服务实例都 订阅同一个事件时,会非常有用。即使我们存储了哪些事件被处理过,在某些形式的异步 消息传递中,可能还留有小窗口,两个worker会看到相同的信息。通过以幂等方式处理这 些事件,我们确保不会导致任何问题。
 
有些人太极端化这一概念,认为它意味着,后续的调用如果使用相同的参数,对系统不会 有任何的影响,这让我们处在一个有趣的位置。例如,我们仍然希望记录调用的发生及其 响应时间到日志中,以收集这个数据来做监控。这里的关键点是,我们认为那些业务操作 是幕等的,而不是整个系统状态的。(对系统状态而言,肯定不是幂等,因为多增加了日志等等。)
 
有些HTTP动词,例如GET和PUT,在HTTP规范里被定义成幂等的,但要让这成为事 实,依赖于你的服务在处理这些调用时是否使用了幂等方式。如果使用了这些动词,但操 作不是幂等的,然而调用者认为它们可以安全地重复执行,你可能会让自己陷入困境。记 住,仅仅因为你使用HTTP作为底层协议,并不意味着就可以免费得到它提供的一切好处。
 
11.7扩展
 
一般来说,我们扩展系统的原因有以下两个。首先,为了帮助处理失败;如果我们担心有 些东西会失败(比如主从备份),那么多一些这些东西会有帮助,对吗?其次,我们为性能扩展,无论是处理更多的负载、减少延迟或两者兼而有之。让我们看一些常见的通用扩展技术,并思考如 何将它们应用于微服务架构中。
 
11.7.1更强大的主机
 
一些操作可能受益于更强大的主机。一个有着更快的CPU和更好的I/O的机器,通常可以 改善延迟和吞吐量,允许你在更短的时间内处理更多的工作。这种形式的扩展通常被称为 垂直扩展,它是非常昂贵的,尤其是当你使用真正的大机器时。有时一个大服务器的成本 要比两个稍小服务器的成本高,虽然两者联合起来的总性能与大服务器相同。不过,有时 我们的软件本身,当有更多额外的可用硬件资源时并不能做得更好。大机器通常给我们更 多的CPU内核,但如果写的软件没有充分利用它们也是不够的。另一个问题是,这种形 式的扩展无法改善我们服务器的弹性!(随着业务的大小而动态改变成本,比如缩减机器、关机)尽管如此,这可能是一个可以快速见效的很好的方 式,特别是当你正在使用虚拟化供应商的服务,并且它允许你轻松地调整机器的大小时。
 
11.7.2拆分负载
 
正如在第6章中所述的,单服务单主机模型肯定要优干多服务单主机模型。然而在最初的 时候,很多人决定将多个微服务共存于一台主机,以降低成本或简化主机管理(尽管这个 原因有待商榷)。因为微服务是通过网络通信的独立进程,所以把它们切换到使用自己的 主机来提高吞吐量和伸缩性,应该是一件很容易的事。这还可以增加系统的弹性,因为单 台主机的宕机将影响较少数量的微服务。
 
当然,我们也可能因为要扩展需要把现有的微服务拆分成几个部分,以更好地处理负载。 举一个简单的例子,想象我们的账户服务提供创建和管理个人客户的财务账户的功能,同 时也暴露一个API用干运行查询来生成报表。这个查询功能会给系统带来一个严重的负 载。这个查询不是那么重要,因为白天需要保持订单流时并不需要它。然而,为客户管理 财务账单的能力是至关重要的,因此我们不能承担它宕机带来的后果。通过把这两个功能 拆分到单独的服务,减少了关键账户服务上的负载,并且引入一个用以查询的新的账户报 表服惹(也许使用我们在第4章中描述的一些技术,但作为一个非关键系统,并不需要像 核心账户服务那样以富有弹性的方式部署)。
 
11.7.3分散风险
 
弹性扩展的一种方式是,确保不要把所有鸡蛋放在一个篮子里。一个简单的例子是,确保 你不要把多个服务放到一台主机上,因为主机的宕机会影响多个服务。但让我们考虑一下 主机指的是什么。在大多数情况下,现在的主机实际上是一个虚拟的概念。如果所有的服 务都在不同的主机上,但这些主机实际上都是运行在一台物理机上的虚拟主机呢?如果物 理机宕掉,同样也会失去多个服务。一些虚拟化平台能够确保你的主机分布在多个不同的 物理机上,以减小发生上述情况的可能性。
 
对于内部的虚拟化平台,常见的做法是,虚拟机的根分区映射到单个SAN (Storage Area Network,存储区域网络)。如果SAN故障,会影响所有连接的虚拟机。SAN是大型的、 昂贵的,并且被设计成不会发生故障。不过,在过去的10年中,那些大型且昂贵的SAN 至少发生过两次故障,而且每次的后果都相当严重。
 
另一种常E的减少故障的方法是,确保不要让所有的服务都运行在同一个数据中心的同 一个机架上,而是分布在多个数据中心。如果你使用基础服务供应商,知道SLA (Service-Level Agreement,服务等级协议)是否提供和具备相应的计划是非常重要的。如果需要确 保你的服务在每季度不超过四小时的宕机时间,但是主机供应商只能保证每个季度不超过 八小时的宕机时间,你必须改变SAL或选取一个替代解决方案。
 
比如,AWS被拆分为多个地区,你可以把它们看作不同的云。每个地区依次被拆分成两 个或更多的AZs (Availability Zones,可用性区域)。AZs是AWS中数据中心的叫法。因 为AWS不提供单个节点甚至整个AZs可用性的担保,所以将服务分布在多个AZs是必 不可少的。对于其计算服务,把区域作为一个整体,AWS在每月给定的期间,仅提供 99.95%的正常运行时间保证,所以要将你的负载分布到单个地区的多个AZs中。对于一 些人来说,这依然是不够的,他们需要跨多个地区运行他们的服务。
 
当然,值得注意的是,供应商给你的SLA保证肯定会减轻他们的责任!如果供应商错失担 保目标,给他们的客户也就是你带来大量金钱上的损失,你会发现即使翻遍整个合同,也 很难找到可以从他们那里追回任何损失的条款。因此,我强烈建议你,了解供应商如果没 有履行义务的影响,并看看你是否需要准备一个B (或C)计划。例如,我的很多客户都 将一个灾难恢复托管平台放到一个不同的供应商那里,以确保他们不至于脆弱得因为一家 公司出错而受影响。
 
11.7.4负载均衡
 
当你想让服务具有弹性时,要避免单点故障。对干公开一个同步HTTP接口这样典型的微 服务来说,要达到这个目的最简单的方法是,如图11-4所示,在一个负载均衡器后,放置 多台主机运行你的微服务实例。对于微服务的消费者来说,你不知道你是在跟一个微服务 实例通信,还是一百个。
 
负载均衡器各种各样,从大型昂贵的硬件设备,到像mod_pmxy这样基于软件的负载均衡 器。它们都有一些共同的关键功能。它们都是基于一些算法,将调用分发到一个或多个实 例中,当实例不再健康时移除它们,并当它们恢复健康后再添加进来。
 
一些负载均衡器提供了其他有用的功能。常见的一个是SSL终止,通过HTTPS连接人站 负载均衡器后,当到实例本身时转换成HTTP连接。从经验上看,管理SSL的开销非常 大,拥有一个负载均衡器来处理这个过程是相当有用的。如今,这在很大程度上也简化了 单个主机运行实例的配置。(只对负载均衡器进行设置HTTPS,可以使得后面的所有服务实例都拥有同一个https证书)不过,使用HTTPS的原因,正如我们在第9章讨论的,是确保请求不容易受到中间人的攻击,所以如果使用SSL终止,在某种程度上可能会暴露我们 自己。缓解这个问题的一个方法,正如我们在图11-5中看到的,是把所有的微服务实例都 放在一个独立的VLAN里。VLAN是一个虚拟局域网,所有的外部请求只能通过一个路由 器访问内部。在这个例子中,这个路由器也就是SSL终端负载均衡器。VLAN外部跟微服 务通信的唯一方式是通过HTTPS,而内部的所有通信都是通过HTTP。(也就是外部跟负载均衡器之间是通过HTTPS,而VLAN内各个服务之间是通过http进行通信)
图11-4:使用负载均衡来扩展客户服务实例的一个例子
 
图11-5:使用更安全的VLAN,负载均衡提供的HTTPS终止
 
AWS以ELBs (Elastic Load Balancers,弹性负载均衡器)的形式,提供HTTPS终止的 负载均衡器,你可以使用其安全组或VPCs (Virtual Private Clouds,私有虚拟云)来实现 VLAN。另外,像mod_proxy这样的软件,可以发挥类似软件负载均衡器的作用。许多组 织使用硬件负载均衡器,不过它很难实现自动化。正因为如此,我自己倾向于在硬件负载 均衡器后使用软件负载均衡器,这样允许团队自由地按需重新配置它们。事实上' 硬件负 载均衡器本身往往也会成为单点故障!不过,无论采用哪种方式,当考虑负载均衡器的配 置时,要像对待服务的配置一样对待它:确保它存放在版本控制系统中,并且可以被自动 化地应用。
负载均衡器允许我们以对服务的所有消费者透明的方式,增加更多的微服务实例。这提高 了我们应对负载的能力,并减少了单个主机故障的影响。然而,很多(如果不是大多数的 话)微服务会有某种形式的持久化数据存储,很有可能是在另一台机器上的数据库。如果 多个微服务实例运行在多台机器上,但只有一台主机在运行数据库实例,那么数据库依然 是一个单点故障源。我们很快会讨论应对这个问题的模式。
 
11.7.5基于worker的系统
 
负载均衡不是服务的多个实例分担负载和降低脆弱性的唯一方式。根据操作性质的不同, 基于worker的系统可能和负载均衡一样有效。在这里,所有的实例工作在一些共享的待办 作业列表上。列表里可能是一些Hadoop的进程,或者是共享的作业队列上的一大批监听 器。这些类型的操作非常适合批量或异步作业。比如像图像缩略图处理、发送电子邮件或 生成报告这样的任务。
 
该模型同样适用干负载髙峰,你可以按需增加额外的实例来处理更多的负载。只要作业队 列本身具有弹性,该模型就可以用于改善作业的吞吐量,也可以改善其弹性,因为它很容 易应对worker故障(或worker不存在)带来的影响。作业有可能需要更长的时间,但不 会丢失。
 
我在一些组织中看到过这种方式且工作得很好,这些组织在一天的某些时候会有大量未使 用的计算能力。例如,在半夜你可能不需要很多机器运行电子商务系统,因此可以暂时使 用它们来运行生成报告任务的worker。
 
使用基于worker的系统时,虽然worker本身不需要很高的可靠性,但保存待办作业列表 的系统时需要。你可以使用一个持久化的消息代理来解决这个问题,或使用像Zookeeper 这样的系统。如果我们使用已有的软件来做这件事情,好处是可以享用很多前人所做的努 力。然而,我们仍然需要知道,如何配置和维护这些系统,使得它们具有弹性。
 
11.7.6重新设计
 
系统最初的架构,可能和能够应对很大负载容量的架构是不同的。正如Jeff Dean在他的 演讲 “Challenges in Building Large-Scale Information Retrieval Systems” (2009 年 WSDM 会议)中所说的,你的设计应该“考虑10倍容量的增长,但超过100倍容量时就要重写 了”。在某些时刻,你需要做一些相当激进的事情,以支持负载容量增加到下一个级别。
 
回忆我们在第6章讨论的Gilt的故事。一个简单的单块Rails应用程序可以很好地为Gilt 工作两年。其业务变得越来越成功,这意味着更多的客户和更多的负载。在某一个临界 点,该公司不得不重新设计应用程序,来处理之前就预见到的负载。
 
重新设计可能意味着拆分现有的单块系统,就像Gilt做的那样。或可能意味着挑选新的数据存储方式,以便更好地应对负载,我们很快会看到这个方案。它还可能意味着采用新的 技术,例如从同步请求/响应转换成基于事件的系统,采用新的部署平台,改变整个技术 栈,或所有介于这些之间的方案。
 
有人将到一定比例阈值时需要重新设计架构作为从一开始就构建大规模系统的理由,这是 很危险的,甚至可能是灾难性的。在开始一个新项目时,我们往往不知道真正想要构建的 是什么,也不知道它是否会成功。我们需要快速实验,并以此了解需要构建哪些功能。如 果在前期为准备大量的负载而构建系统,将在前期做大量的工作,以准备应对也许永远不 会到来的负载,同时耗费了本可以花在更重要的事情上的精力,例如,理解是否真有人会 使用我们的产品。EricRie,讲述了一个故事,他花了六个月的时间构建了一个产品,却压 根没有人下载。他反思说,他本可以在网页上放一个链接,当有人点击时返回404,以此 来检验是否真的有这样的需求。与此同时他可以在海滩上度过六个月,并且这种方式跟花 六个月构建产品学到的知识是一样多的!
 
需要更改我们的系统来应对规模化,这不是失败的标志,而是成功的标志。
 
 

猜你喜欢

转载自www.cnblogs.com/mongotea/p/11992122.html
今日推荐