分布式系统架构:高可用实现
可用性被定义为“系统或组件在需要使用时可操作和可访问的程度”[IEEE610],衡量可用性的指标是可用率,即
(总运行时长-业务中断时长)/总运行时长*100%下表总结了常用的可用率,高可用通常指3个9. 对于关键系统,如金融和电信,通常要求可用率为5个9。
| 可用率 | 简述 | 一年内停止服务时长(分钟) | 服务描述 |
|---|---|---|---|
| 90% | 1个9 | 52596 | 每年最多允许停止服务5周 |
| 99% | 2个9 | 5259.6 | 每年最多允许停止服务4天 |
| 99.9% | 3个9 | 525.96 | 每年最多允许停止服务9小时 |
| 99.99% | 4个9 | 52.60 | 每年最多允许停止服务1小时 |
| 99.999% | 5个9 | 5.26 | 每年最多允许停止服务5分钟 |
在架构中,与可用性类似的概念的还有可靠性,本书不做区分,这是因为这两个概念都有如上的衡量指标和实现高可用高可靠的对应的战术方法,区别是高可用更强调系统可用时长,而可靠性更强调故障率,如果一个系统频繁不可用但能快速恢复,它的可靠性很低但可用性则很高。
在互联网和物联网广泛使用之前,由于软件系统用户量少,访问系统少,系统的高可用的目标主要是针对硬件,比如服务器高可用,磁盘高可用,网络高可用。硬件高可用实现方式主要采用备份,这是因为硬件的故障率随着使用的时间增长而增加,硬件通过运行过程自动检测,故障隔离和自动替换成备份3个步骤实现高可用。
软件系统借鉴了自动检测和故障隔离,但因为软件则不会随着使用时长,使用频率增加导致可用性降低,导致软件无法达成高可用的原因较多,有系统外部的,比如流量变化,网络攻击;也有系统内部的原因,比如架构和设计不合理、软件迭代过程中引入BUG等。
实现软件的高可用要比硬件复杂的多,比如设计不当的软件,即使自动启用备份,或者采用负载均衡也可能还是依然会故障。下表总结了了分布式系高可用未能实现的主要原因,并在后面一个思维导图列出实现分布式系统高可用的战术
| 主要原因 | 描述 |
|---|---|
| 软件工程+高可用 | 软件工程缺少对高可用的关注,应该在业务架构时期就获得系统的高可用质量属性要求,并在随后技术架构和编码实现高可用。通过猴子捣乱验证高可用。通过可观测性来检测系统健康。软件工程中,除了需要完成的业务需求,系统的架构应该包含可高可用设计,编码也包含高可用的代码,不应该只完成功能性需求,而忽略了非功能性需求。 高可用成为业务自身的一部分必须完成,这同可观测性一样。参考 《1.8 分布式系统认知》中的Hello World |
| 流量变化 | 对于现代分布式软件系统,忽略流量变化而设计的系统,往往在运行过程中出现性能和高可用问题。常见流量变化包括特定时段,如电商的促销日,家电物联网早晚时段;以及系统重启时刻。以及三方系统内部调整导致流量变化。一个高可用架构需要管理流量。 |
| 外部干扰 | 系统缺少安全架构导致的请求被篡改、受到外部攻击,以及运营商的故障、灾都等可能导致系统不可用。需要通过备份等手段实现高可用避免外部干扰注意:安全架构不在本书的范围内。 |
| 技术团队过于乐观 | 架构师可以拿着技术架构图,如软件架构和物理结构,悲观的看架构图的每一个组件,思考此组件可能发生什么问题,如果出现故障,对其他组件有什么影响。常见的组件问题有“不响应”,“响应慢”,“响应错”,以及对外部服务有流量冲击等。架构师也需要如果此组件的外部环境发生变化,对系统可用性有什么影响,比如服务部署上Docker上,如果容器因故障发生漂移,对系统有什么影响。乐观的团队需要回到现实,用面向失败的态度设计系统,并采用防御性编码。 |
| 系统实现过于复杂 | 过于复杂的设计也会导致高可用实现难以达到,比如为了提高性能实现有状态服务,有状态服务通常难以达到高可用。过于复杂的设计和编码造成系统难以维护,导致系统腐烂,迭代中出现BUG的几率较高。 |
| 高可用实现方法 | 实现系统高可用是对架构师和程序员的挑战。本书会列举大量方法来实现。团队需要在软件工程实施过程中,检查这些方法是否需要应用到目标系统上。 |
| 可观测性 | 系统故障可能从一个小的缺陷开始,演变成错误,直到系统出现故障宕机,进而会广播影响其他系统。比如一个Map使用不当问题导致内存溢出,导致JVM频繁STW,使得系统逐渐变得访问超时,直到无在可用内存后系统完全不能使用,系统功能故障也可能影响上下游系统,如果上游系统不能对此有预案可观测既能预警系统早期故障,也能在系统出现故障时刻立刻通知运维和研发,并提供足够的信息供诊断 |
| 基础设施高可用 | 团队需要检查系统依赖的基础设施,或者第三方服务是否高可用。如系统高可用,但系统依赖的认证服务是单点而非集群。通常可以考虑云厂商提供的基础设施。如果自行搭建,需要考虑如何实现基础设施高可用。 |
下面思维导图从架构到实现,总结了一系列高可用战术方法。

作为分布式系统,通常分成无状态服务,有状态服务。
无状态服务: 服务端不保存客户端请求的数据
有状态服务: 即服务端需要记录客户请求数据,用户下次请求处理之前的数据,必须路由到此服务,查询之前的数据。典型的例子数据库记录数据,Redis保存数据到内存,Web应用在内存里保存用户的Session
由于无状态服务不会保持用户请求数据,因此无状态服务节点可以横向扩展,也可以停止以升级,因此无状态服务高可用战术更为简单。
架构具体可以参考本书第二篇,下表列出了高可用架构的三种架构风格
| 架构 | 高可用描述 |
|---|---|
| 代理架构 | 部署服务的多个实例,互为备份和分担流量。通过前置代理将请求路由到这些服务节点。代理包括网关,API网关,Nginx代理服务等 |
| 消息驱动 | 相比于直接RPC调用服务。把服务调用封装成消息发到中间件,服务的多个实例订阅消息平台,使得这些实例具备高可用。 |
| 微服务 | 微服务架构中包含了服务查找和发现(以及路由),通过此实现服务的高可用。 |
高可用设计通用原则
| 通用设计 | 高可用描述 |
|---|---|
| 负载均衡 | 在设计服务的时候,需要考虑到如何提实现服务负载均衡,比如如果服务以REST提供对外服务,则可以前置代理服务器。如果服务以MQTT提供对外协议,则需要考虑支持MQTT的代理。或者提供一个类似DNS的查找,使得设备能查找MQTT服务地址。我们也可以通过线程池,EventBus实现JVM内部任务(TASK)的负载均衡。 |
| 备份 | 软硬件最常用的高可用、高可靠方案是备份。包括热备,温备和冷备。以Redis的主从为例子,热备是从节点持续从主节点同步数据,并提供对外读服务;温备是从节点同步数据,但对外不提供服务。如果主节点维护则从节点切换为主节点。 冷备则是定期,如每天同步数据。通常用于数据库把数据定期保存到磁带上。 |
| 热部署 | 热部署是指不重启应用前提下,更新系统功能。关于热不是实现方案参考《3.5 可修改战术》 |
| 万一原则 | 万一原则是技术团队持有的一种悲观太多,去待系统内部和外部的态度。万一原则包含面向失败的设计和防御性编程 |
| KISS | KISS(Keep It Simple, Stupid)原则,越是简单设计和实现的系统,故障率越低,也越容易恢复。在后期持续迭代过程中也不容易出现错误。 |
| 可靠基础设施 | 毫无疑问,系统应该建构在可靠的基础设施上。检查你的技术架构,看看哪些基础设施不可靠,询问你的第三方,看看他们是如何提供高可用服务。 |
分布式系统里,节点服务包含了业务处理和数据俩部分, 如果数据是从持久化系统加载,则认为是此节点是无状态的。 如果数据常驻于节点内,业务逻辑是从外部加载进来的,则认为是有状态节点。无状态节点包括我们常见的大多数服务,如气象服务,搜索服务,订单服务等,有状态服务则有游戏房间,网络文档协作等,以及Redis,Zookeeper等基础设施。
有状态服务好处是状态维护在内存,提供了高性能服务,缺点是需要实现数据备份,分片等操作,因此有状态服务的高可用难度非常大。
关于有状态服务和无状态服务以及高可用,将在2.2节详细说明。
无状态服务高可用设计如下表所示(注意,有状态服务也需要用无状态服务的的高可用方案作为基础)
| 无状态高可用战术 | 高可用描述 |
|---|---|
| 端到端原则 | 端到端原则(End To End)最初来自计算机技术和通信系统的设计原则,在用来划分通信底层的功能的时候,强调端侧高可用如果依赖通信底层实现代价很大,且无法完全高可用,需要依赖端侧实现。这种原则后来也用于知道分布式系统和适微服务系统,当服务调用依赖一系列微服务系统或者中间件时候,端侧应用应该实现高可用而不依赖于底层或者中间件。 |
| 服务发现 | 当部署多个节点实现服务互为备份时候,调用者如何发现这些服务。像ZK,Nocos等提供了『服务与注册中心』,客户端查询注册中心获取可用服务,服务端则将服务注册到注册中心,并接收 当服务不可用,客户端可以注册中心获取一个可用的服务再次调用。通常微服务框架均实现了服务注册和查找。 |
| 故障检测 | 通过心跳或者Gossip检测服务集群节点的状态。 |
| 重试 | 客户端在访问服务失败的时候,可以重试,确保调用服务端成功,重试的前提是服务器实现幂等( |
| 幂等 | 幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同。比如,向服务提供者发送数据A自增的计算请求并保存到数据库,则可能因为多次请求造成A结果的变化。可以在此请求增加时间戳参数,以提醒服务方避免重复计算。或者更改逻辑直接让客户端计算A自增结果并保存到数据,这时候保存A实现幂操作等即可。 |
| 隔离 | 将同一个服务划分不同集群,如toB,toC,或者南北向,或者VIP集群(TODO 微博大V?),可以通过网关技术将用户路由到不同集群,或者用户端保存目标服务地址(TODO,隔离实现方式) |
| 快速失败 | 相比于重试,当业务调用失败后直接失败,尽管这看来不可取,但在微服务场景下,服务快速失败而不是重拾,避免了上游调用等待超时。微服务场景下,尽量设置微服务框架有较短的超时时间和禁止重试(其他节点)从而实现快速失败 |
| 优雅关闭 | 服务节点在重启前,通知其即将关闭以给服务一定时间清理资源。比如数据库连接池,文件句柄,停止消息订阅等。 |
| 预热 | 考虑到JVM需要预热,初期访问即会导致JIT优化,也会加载第三方资源。可以让系统启动后先预热完毕再对外提供服务。 |
| 唯一序列号 | 分布式服务请求,需要一个唯一序列号,用于记录和跟踪请求,以及实现幂等或者乐观锁等操作。唯一序列号可以通过UUID,雪花算法,或者数据库提供,或者把请求参数拼接字符串后通过HASH得出一个唯一序号 |
| 脚本&表达式引擎 | 脚本&表达式通常用于频繁修改的逻辑,而不需要重新启动系统。实现系统高可用和可修改性 |
| 读写分离 | 无论有状态服务还是无状态服务,均可以采用读写分离提高可用性,这是因为大部分系统都是读多写少的系统。以电商库存为例子,库存查询服务和库存预占服务就是读写分离的例子。有状态服务,比如ZK,Redis,数据库普遍具备读写分离能力 |
| 南北架构 | 与读写分离类似的还有南北架构,物联网南向系统是面向亿万级别在线的设备,北向系统是面向数十万在线用户。南向系统和北向系统的技术架构不一样,比如南向系统的物理架构,需要部署更多的硬件资源,分片更多的Redis集群。 |
有状态服务高可用难度比无状态服务高出1到2个数量级,除非你使用一些有状态服务框架,比如Akka,否则不建议自己实现有状态服务。下表仅仅列出高可用的战术和解决的问题,具体实现第5.3详细介绍。
| 有状态高可用战术 | 问题描述 |
|---|---|
| 分片 | 考虑到单机JVM保存的数据状态有限,需要将数据均匀分布在多个节点上提供对外服务。通常使用一致性HASH来对数据进行分片 |
| 粘性连接 | 当用户操作状态数据时候,多次请求如何能连接到拥有状态的那个分片。通常根据数据的一致性HASH结果定位到分片。也有通过专门的查询服务查找到分片节点。 |
| 主从 | 分片数据需要备份到另外节点也实现高可用,有状态服务通常实现了热备或者温备份 |
| 复制 | 实现主从备份的关键技术,把增量数据或者全量数据定期同步到多个从节点 |
| 日志和快照 | 实现主从备份的关键技术。通常通过同步操作日志既可以实现主从数据同步,快照则是某个时刻服务的整体状态。复制是基于日志+快照来实现的。比如节点故障后,可以通过快照以及气候的日志来实现数据恢复 |
| 时钟 | 有状态服务,事件或者请求的顺序特别重要,因此使用逻辑时钟来标识事件发生的事件,以Zookeeper为例子,时钟是一个64位Long类型,包含了高位32的 整型纪元+低32位的整型顺序号 |
| CAP | 有状态服务,当状态同步到其他从备份节点时候,需要一定的延时从节点才能跟主节点一致(或者极极端情况下需要很长时间)。CAP理论是指分区(P,比如主从节点)情况下,服务可以立即对外服务以实现高可用(A),或者等待主从节点同步完毕后再对外提供服务以实现一致性(C)。系统不可能同时实现A和C。 |
| 共识 | 共识是指当有有多个备份,且备份的状态不一致的时候,确定系统的最终状态。Zookeeper使用ZAB算法,Redis、ETCD采用Raft算法。 |
| 负载再均衡 | 通过一致性HASH,可以均衡的对数据进行分片。但随着节点的增减,某些分片的节点数据过多,某些节点数据特别少。 需要负载再均衡方方案重新分配数据。 |
| 转为无状态系统 | 通常业务系统不必因为性能去实现有状态服务,可以有多种方法将有状态服务转化成无状态服务,比如使用Redis存储状态,或者允许状态被携带到客户端以及回传到任意节点等 |
对于自动驾驶车,拥有摄像头和多个雷达,都能感知到外部环境。这些设备如何对外部环境达成一致。如果有人知道的,可以分享给我
在分布系统中,如何验证系统高可用,这需要我们能模拟一些极端情况,在这些极端情况下系统仍然可用,以及能正确响应。比如一段时间内,CPU慢导致服务可不用,或者是网络延迟增大导致调用时间长,或者网络故障导致调用超时等,或者一段时间网络流量激增后系统能恢复,我们称模拟这些极端情况验证系统高可用,成为捣乱。如下图所示,我们对下图物理架构各个部分进行捣乱

以模拟CPU使用率为例子,可以使用耗费CPU资源的计算类脚本,如下命令使用openssl命令,用来测试所有加密算法的速度
> openssl speed -multi 1这里的1是使用1个CPU进行计算,这个命令后果是强迫多核服务器中,CPU使用率提高,如果系统有8C,参数1替换成8,则主机的CPU处于100%忙碌。这里可以验证此主机上下游是否能正常处理(或者降级处理)
关于如何对系统进行捣乱,参考本书第三章。
注意:系统高可用实现是一个整体工程,不仅仅涉及到研发人员,还有架构,测试和运维,配套工具。本书仅从研发角度提供了高可用的实现战术和测试方法。可以参考如下书籍了解系统高可用工程
本节所有内容参考如下书以及 千问AI,豆包AI:
- 《Handbookof Reliability Engineering》
- 《Blueprints for High Availability 2》
- 《Design for Reliability》
- 《Google系统架构解密:构建安全可靠的系统》
- 《深入高可用系统原理与设计》
- 《软件可靠性和安全性设计(军队)标准》
- 《ELECTRONIC RELIABILITY DESIGN HANDBOOK》美国军队
