依赖管理
在大规模服务中,依赖管理这个话题通常得不到应有的关注。一般的准则是,对于小型组件和服务的依赖关系,对于判断管理它们的复杂性来说,并不足以节约成本。在以下情况中,依赖关系存在重要意义:
1. 被依赖的组件在大小和复杂度上有重要价值;
2. 被依赖的服务在作为单一的中央实例时存在价值。
第一类的例子有存储和一致性算法(consensus algorithm)的实现。第二类的例子包括身份和群组管理系统。这些系统的整体价值在于它们是一个单一且共享的实力,因此使用多实例来避免依赖关系就不是可选方案。
假定要根据上面的标准判断依赖关系,那么用来管理它们的最佳实践有:
◆为延迟做好准备。对外部组件的调用可能需要很长时间才能完成。不要让一个组件或者服务中的延迟在完全不相关的领域中引发延迟;确保所有的交互都存在长度合适的超时时长,避免资源阻塞更长的时间。运营等幂性允许请求在超时后重启,即便这些请求可能已经部分或完全完成。确保所有的重启操作都得到报告,并给重启操作设定界限,从而避免反复故障的请求消耗更多的系统资源。
◆隔离故障。网站的架构必须能防止层叠的故障,要总是“快速失败(fail fast)”。当依赖服务出现故障时,把这些服务标注为停机,并停止使用这些服务,以避免线程因等待故障组件而阻塞。
◆使用已经交付的历经考验的组件。历经考验的技术通常总是要比大胆前卫地走在潮流尖端运行要好很多。稳定的软件要优于新版本的早期,不管新特性如何有价值。这条原则也适用于硬件。批量生产的稳定硬件,往往要比从早期发布的硬件所获得的些许性能提升要有价值得多。
◆实现跨服务的监控和警报。如果服务中有一个附属服务过载,那么被依赖的服务就必须了解这个情况,并且如果服务无法自动备份,那么必须发送警报。如果运营部门无法快速地解决这个问题,那么服务就得设计得能容易迅速地联系到两个团队的工程师。所有相关的团队都应当在附属团队中安排工程联络人。
◆附属服务需要同一个设计点。附属的服务和附属组件的生产者至少必须遵循与所属服务相同的SLA(服务水平协议)。
◆对组件解耦。在所有可能的地方保证在其他组件故障时,组件可以继续运行,可能在一个降级的模式中。例如,比起在每一个连接上重新验证,维护一个Session键值,并且无论连接状况如何,每过N小时就刷新这个键值会更好些。在重新建立连接时,只使用现有的Session键值。这样的话在验证服务器上的负载就会更加一致,而且也不会在临时网络故障和相关事件之后的重新连接过程中出现登录高峰期的情况。
发布周期及测试
在生产环境中进行测试是一件很现实的事情,必须成为所有internet级服务所必须的质量保证方式之一。在尽可能(可以掏得起钱)的情况下,绝大多数服务都应当有至少一个与生产环境相似的测试实验室,并且所有优秀的工程师团队应当使用生产级的负载,以反映现实的方式测试服务器。不过,我们的经验是,这些测试实验
室即便模拟得再好,也绝不可能百分之百的逼真;它们至少总是会在一些细微的方式上和生产环境有所差别。由于这些实验室在真实性上接近生产系统,因此相应的费用也呈渐进趋势,快速逼近生产系统的开支。
与此不同,我们推荐让新的服务发布版本顺着标准单元测试、功能测试和生产测试实验室测试一路走下来,一直进入受限的生产环境作为最后的测试阶段。显然,我们不想让无法正常工作并给数据一致性带来风险的软件进入生产环境,因此这就不得不小心翼翼地实施。下面的原则一定要遵守:
1. 生产系统必须有足够的冗余,以保证在灾难性的新服务故障发生时,能够快速地恢复到原来的状态;
2. 必须让数据损坏或者状态相关的故障极难发生(一定要首先通过功能测试);
3. 故障一定要能检测得到,并且开发团队(而不是运营团队)必须监控受测代码的系统健康度;
4. 必须可以实现对所有变更的回滚操作,并且回滚必须在进入生产环境之前经过测试。
这听起来有点让人心惊胆战。不过我们发现,使用这个技术实际上能够在新服务发布时提升客户体验。与尽可能快地进行部署的做法不同,我们在一个数据中心中将一个系统放到生产环境数天。随后在每个数据中心内把新系统引入生产环境。接着,我们会将整个数据中心带入生产环境。最后,如果达到了质量和性能的目标,
我们就进行全局部署。这种方式可以在服务面临风险之前发现问题,事实上还可以通过版本过渡提供更优秀的客户体验。一锤定音的部署是非常危险的。
我们青睐的另一种可能违反直觉的方式是,在每天正午而不是半夜部署。在晚上部署,出现错误的风险更高,而且在半夜部署时如果有异常情况突然发生,那么能处理这些问题的工程师肯定会少些。这样做的目标是为了使开发和运营团队与整体系统的互动最小化,尤其在普通的工作日之外,使得费用得到削减的同时,质量
得到提高。
对于发布周期和测试的最佳实践包括:
◆经常性地交付。直觉上讲,人们会认为更频繁地交付难度要更大,而且会错误频出。然而我们发现,频繁的交付中突兀的变更数量很少,从而使得发布的质量变得更高,并且客户体验更棒。对一次良好的发布所进行的酸性测试,用户提供可能会有所变化,但是关于可用性和延迟的运营问题的数量应当在发布周期中不受改
变。我们会喜欢三个月一次的交付,但也可以有支持其他时长的论调。我们从心底认为,标准最终会比三个月更少,并且有许多服务已经是按周交付的了。比三个月更长的周期是很危险的。
◆使用生产数据来发现问题。在大规模系统中的质量保证,是个数据挖掘和可视化的问题,而不是一个测试问题。每个人都必须专注于从生产环境的海量数据中获得尽可能多的信息。这方面的策略有:
◇可度量的发布标准。定义出符合预期用户体验的具体标准,并且对其进行持续监控。如果可用性应当为99%,那么衡量可用性是否达到目标。如果没有达到,发出警报并且进行诊断。
◇实时对目标进行调优。不要停顿下来考虑到底目标应当是99%、99.9%还是任何其他目标,设定一个可以接受的目标,然后随着系统在生产环境中稳定性的建立,让目标渐进式地增长。
◇一直收集实际数据。收集实际的度量,而不是那些红红绿绿的或者其他的报表。总结报表和图像很有用,不过还是需要原始数据用来诊断。
◇最小化“假阳性(faIse positive)”现象。在数据不正确时,人们很快就不再关注它们。不要过度警报,真是很重要的,否则运营人员会慢慢习惯于忽略这些警报。这非常重要,以至于把真正的问题隐藏成间接损害常常是可以接受的。
◇分析趋势。这个可以用来预测问题。例如,当系统中数据移动的速度有异于往常的时候,常常能够预测出更大的问题。这时就要研究可用的数据。
◇使系统健康程度保持高度透明。要求整个组织必须有一个全局可用且实时显示的系统健康报告。在内部安置一个网站,让大家可以在任意时间查看并了解当前服务的状态。
◇持续监控。值得一提的是,人们必须每天查看所有数据。每个人都应当这么做,不过可以把这项工作明确给团队的一部分人专职去做。
◆在设计开发上加大投入。良好的设计开发可以使运营需求降到最小,还能在问题变成实际运营矛盾之前解决它们。非常常见的一个现象就是组织不断给运营
部门增加投入,处理伸缩问题,却从没花时间设计一套可伸缩的可靠架构。如果服务一开始没有进行过宏伟的构思,那么以后就得手忙脚乱地追赶了。
◆支持版本回滚。版本回滚是强制的,而且必须在发布之前进行测试和验证。如果没有回滚,那么任何形式的产品级测试都会存在非常高的风险。回复到先前的版本应该是一个可以在任意部署过程中随时打开的降落伞扣。
◆保持前后版本的兼容性。这一点也是至关紧要的,而且也前面一点关系也非常密切。在组件之间更改文件类型、接口、日志/调试、检测(instrumentation)、监控和联系点,都是潜在的风险来源。除非今后没有机会回滚到之前的老版本的可能,否则不要放弃对于老版本文件的支持。
◆单服务器部署。这既是测试的需求也是开发的需求,整个服务必须很容易被托管到单一的系统中。在对于某些组件单服务器无法实现的地方(比如说一个对于外部、非单箱的可部署服务),编写模拟器来使单服务器测试成为可能。没有这个的话,单元测试会的难度会很大,而且不会完全覆盖到实际条件。而且,如果运行完整
的系统很困难的话,开发人员会倾向于接受从组件的角度看问题,而不是从系统的角度。
◆针对负载进行压力测试。使用两倍(或者更多倍的)负载来运行生产系统的某些小部分,以确保系统在高于预期负载情况下的行为得到了解,同时也确保系统不会随着负载的增加而瓦解。
◆在新发布版本之前进行功能和性能测试。在服务的级别上这么做,并针对每个组件这么做,因为工作负载的特征会一直改变。系统内部的问题和降级现象必须在早期捕获。
◆表象性且迭代地进行构建和部署。在开发周期中早早地把完整服务的骨架先搭建起来。这个完整服务可能几乎做不了什么,也可能在某些地方出现偏差,但是它可以允许测试人员和开发人员更有效率,而且也能让整个团队在一开始就从用户的角度进行思考。在构建任何一个软件系统时,这都是一个好方法。不对,对于服务
来说这尤为重要。
◆使用真实数据测试。将用户请求和工作量从生产到测试环境分门别类。选择生产数据并把它放到测试环境中。产品形形色色的用户,在发现bug的时候总是显得创意无穷。显然,隐私承诺必须保持,使得这样的数据永远不会泄漏回到产品环境中,这是至关紧要的。
◆运行系统级的验收测试。在本地运行的测试提供可以加速迭代开发的健康测试。要避免大量维护费用,这些测试应当放在系统级别。
◆在完全环境中做测试和开发。把硬件放在一边,在专注的范围内测试。作重要的是,使用和在这些环境中的生产条件下同样的数据集合和挖掘技术,以保证投资的最大化。●