大规模服务器设计与开发实践经验
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)