大规模服务器设计与开发实践经验
1.简介
本文就设计与开发大规模服务器的话题进行总结,得出出一系列实践经验。 设计和开发大规模服务器是一个高速发展的领域,本文的目的是: (1)快速交付运维友好的服务; (2)避免凌晨收到报警短信的骚扰(深受其害啊);进入正题之前,提出三点原则,这三点贯穿后面讨论的主轴:
(1)故障时刻会发生; (2)KISS原则:时刻保持简单; (3)自动化;2.整体设计实践经验
问题出现的时候,人们往往自然倾向于首先审视运维工作,因为这是问题实际产生的地方; 不过,绝大多数运维问题都可以归因于设计和开发,这些问题比较适合在设计和开发阶段解决; 在服务领域,将开发、测试、运维严格分离不是最有效的方式,它们之间应该紧密相关。2.1为故障的发生而设计(Design for failure)
这应该是一条核心概念,大规模的服务集群,故障时时刻刻在发生, 如果一有故障就需要采取紧急措施来应对,那服务就无法以合理的成本来控制, 整个服务必须有承受故障,而不需要人工干预: (1)恢复故障步骤必须非常简单,并且经过反复测试; (2)不能使用优雅的停机方式测试故障恢复步骤,怎么粗暴怎么来; 如果没有粗暴的故障恢复测试,问题真正来临的时候,就可能溃不成军。2.2冗余与错误恢复(Redundancy and fault recovery)
服务器集群是脆弱的,设计必须保证,集群中的任何部件的随时崩溃,仍能提供正常服务(SLA,Service Level Agreement)。 设计中必须包含数据的冗余,服务的冗余与恢复机制。2.3廉价硬件(Commodity hardware slice)
所有服务器应该以廉价硬件为目标,因为: (1)大型服务器价格昂贵; (2)服务器性能增长速度比IO性能增长速度快很多,这样一来,给定容量的磁盘,小型服务器的CPU就能满足性能需求; (3)小型服务器集群故障时,可能只影响一部分服务,如果设计得当,可以不影响服务;2.4单版本软件(Single-version software)
某些服务比多数打包产品开发费用更低,且发展速度更快: (1)软件只需针对一次性的内部部署; (2)旧版本无需做到N年的支持,企业软件产品本是如此 经济型的服务不会把对客户运行的版本的控制权交给他吗,通常只应该提供一个版本,把我好单版本软件,需要注意: (1)发布不同版本的产品之间的用户体验变更不宜太大; (2)版本的控制应该在内部实施、2.5多重租赁(Multi-tenancy)
单一租赁将不同组别的用户分离在不同的集群中,而多重租赁相反,其成本更低。2.6其他实践经验
(1)快速服务健康性测试:新功能的开发,只有通过快速健康性测试,代码才能check in; (2)在完整环境中开发:单元测试往往需要其他组件的配合; (3)其他组件零信任:设想任何组件都可能出现故障,确保组件有能力恢复并继续提供服务,技巧包括:系统只读,依赖缓存数据运行;冗余恢复过程中,仍能向一部分用户提供服务; (4)一个功能只保留在一个组件中:不要多个组件干类似的事,这样做很可能引入冗余代码,导致代码膨胀和质量恶化,增加维护成本; (5)不同集群尽量不互相影响、互相依赖:集群独立,一个集群出故障时,可以迁移流量; (6)十万火急时人工干预:如果全自动化成本太高,或者紧急情况发生,请考虑人工干预的方式; (7)保持组件的简单与健壮性; (8)全面的准入控制; (9)服务分区:由于内存限制,或者数据规模限制,需要垂直、水平分割服务; (10)不承认网络层的透明性;2.7分析吞吐量和延时
必须全面了解和分析服务吞吐量与延时情况,了解它们的影; 到底哪个指标更加重要,每个服务都应该形成一个度量标准,用于性能规划。2.8运维工具是服务的一部分
谁说运维工具就不需要测试?2.9理解应用层访问模式
新特性的规划,必须考虑应用层访问模式; 服务模型与存储能完全抽象分开么?不要忘了,访问模式会对Cache和数据造成很大的冲击和影响。2.10所有工作版本化
目标是单版本软件,谁能保证呢,服务器所有组件应该保证N版本和N+1版本能够共存。 (1)保持最新版本的功能测试; (2)避免单点,尽量设计无状态的服务器;3.自动管理的实践
反思一下系统中是否存在以下问题: (1)是否为服务编写了大量出现故障时报警的代码,以便人工干预恢复服务,这种模式7*24小时运维成本很高; (2)运维工程师在出故障时,高压力情况下,艰难的决定,有很高的比例他们会犯错; 以下是一些自动管理实践经验: 3.1随时可以重启,并保持冗余 3.2支持地理分布 3.3自动预置与安装,保持环境一致 3.4配置是代码的一部分 3.5任何环境的变更必须有审计记录 3.6运维和管理服务器的角色,而不是服务器本身 3.7多组件故障是常见的,如机房故障 3.8重视服务级别的恢复 3.9不可恢复的信息,不能依赖于本地存储 3.10保持部署简单性 3.11定期停服务演习4.组件依赖管理实践经验
组件间多多少少存在一定依赖关系,其中的实践经验有: 4.1时刻为延时做准备:配合组件随时可能超时; 4.2快速故障隔离:fail-fast设计原则,冗余足够时,快速挂掉重启可能是更好的选择; 4.3使用经过线上考验的组件:新技术的实施需要预留提前量; 4.4跨服务的监控与报警:运维工程师和开发工程师都能收到报警么,分别布置的监控么,运维工程师收到报警,但解决不了问题时,能及时找到接口的开发工程师么; 4.5附属服务的设计也需要遵循SLA原则 4.6组件尽量解耦5.测试实践经验
如果条件允许,大部分服务都应该在一个与线上环境类似的环境下测试,并使用线上负载,以反映真实的状况。 以下原则必须遵守: (1)线上系统必须有足够冗余,在灾难性故障发生时,能够快速恢复; (2)数据不能丢失; (3)故障必须可检测,开发团队(非运维团队)必须监控系统健康度; (4)所有操作可回滚,所有回滚必须测试。 以下是一些实践经验: 5.1经常性交付:频繁的交付、测试,能够增强产品质量; 5.2使用线上数据来发现问题 (1)发布标准必须可度量,例如可用性必须达到99%,压力能承受100rps; (2)实时线上数据调优; (3)收集实际数据; (4)不能过度报警,过度报警会让运维人员麻木不仁; (5)趋势分析; (6)高透明度的系统健康性报告; (7)持续监控; 5.3在设计上加大投入:不是加大运维成本,而是花时间加强设计; 5.4支持版本回滚; 5.5保持版本兼容; 5.6降低环境搭建成本,支持快速单机部署(否则单元测试难度会很大); 5.7压力测试必须高于线上负载; 5.8功能测试、性能测试必不可少; 5.9迭代性构建与部署; 5.10使用真实数据测试; 5.11系统级测试必不可少; 5.12在完整环境中测试与开发。6.运维实践经验
我们的目标是:让一个8*5的运维团队高效维护整个高可靠的24*7系统。 一条普适规则是:如果没有经过频繁测试,任何操作与程序都无法正常工作,不要实现团队没有勇气使用的任何东西。 实践经验有这样一些: 6.1让开发团队承担责任:谁创建的就该谁管,如果不想运维团队半夜给你打电话,就做出一套自动化方案; 6.2只进行软删除,不要删除任何东西; 6.3跟踪资源的分配,了解系统开销; 6.4每次只变更一样东西; 6.5尽量让所有资源都可配置;7.审核、监控与报警
原则是:所有关键事件都得报警,无需采取措施时没有报警。 有两条度量标准: (1)报警与实际故障比; (2)没有报警与故障数量比; 一些实践经验是: 7.1所有资源都必须进行检测; 7.2数据是最有价值的资产; 7.3从用户的角度看服务; 7.4检测与监控是线上服务必不可少的; 7.5延时是棘手的问题; 7.6细粒度的监控机制必须在早期建立; 7.7可配置的日志功能; 7.8外部化健康信息; 7.9所有错误可应对,无法采取措施的错误报告毫无意义; 7.10快速诊断;8.优雅降级与访问控制
实践经验有: 8.1支持“大红开关(big red switch)”,能够在紧急状况时,卸载掉非关键负载; 8.2访问控制:系统能够方便的禁止某些负载么? 8.3对访问的统计;9.结束语
要降低大规模服务的运营成本并改善服务的可靠性,一切从编写服务时注重运营友好开始。注:原文见On Designing and Deploying Internet-Scale Services
作者是:James Hamilton(Windows Live Services Platform)