3.1 高可用的常用手段
高可用是一个比较复杂的命题。日常运维操作例如服务升级、硬件更新、数据迁移等都可能造成服务宕机。在功能上线周期越来越短、发布越来越频繁的迭代开发模式下,实现 “4 个9” 或 “5 个9” 的目标,其难度不言而喻。因此,在架构设计之初,就应充分考虑服务的高可用性,充分利用常规的高可用手段来保障后续迭代过程的平稳。简单地,在设计上需要避免单点故障(Single Point of Failure,即SPOF):路由、防火墙、负载均衡、反向代理及监控系统等在网络和应用层面上必须全部是冗余设计,以此来保证最佳的可用性。下面介绍一些提高系统可用性的常规方法。
1.服务冗余
每个服务运行多个实例,牺牲更多资源换取更高的可用性。除了需要考虑实例本身的冗余,还应考虑设备的隔离冗余,也就是说应考虑服务实例是否部署在不同的机架、不同的机房或者不同的数据中心中。具体采用跨机架部署、跨机房部署还是跨数据中心部署,则需要根据服务的业务和安全程度来决定。
主备模式是传统的服务冗余方法之一,根据策略又可分为N+1、N+2 等模式。N+1 的主备模式,即将两个设备绑成设备对儿。一个主设备提供服务,另一个从设备作为备份但不提供服务。在主设备出现故障时,只需通过切换主备设备进行故障转移,即可短时间恢复服务。
针对频繁变更的系统,单纯的主备模式不够用,由此建议至少部署N+2 个实例。在N+1 的主备模式下,系统在变更时至少会有一个实例不可用,如果此时另一个实例出现故障,那么整个服务将不可用。而N+2 的主备模式能够保证一个实例发生变更时,如果第二个实例发生故障,至少还有一个实例保证业务不中断。小到单个设备、单个服务,大到数据中心,都会有类似的冗余部署方案保证系统的整体高可用。
2.服务无状态化
所谓的无状态化是指每个服务实例的服务内容和数据都是一致的,每个服务实例皆提供服务。如果服务是无状态的,我们就能对服务随时进行扩缩容。这是目前微服务的主流趋势,这有利于服务在各个容器云平台上的部署。如果服务是有状态的,那么逻辑处理是依赖于数据的,应该将 “有状态” 的数据部分剥离出来,借助擅长数据同步的中间件使数据实现集中管理,保证数据的一致性,如图3-2 所示。其他提供逻辑处理的服务就是无状态的,当因流量爆发而进行扩容时无须考虑其内数据是否一致的问题。
3.服务拆分
如图3-3 所示,将一个大的系统拆分成多个独立的小模块,各个模块之间相互调用,是减少故障影响范围的主要手段。当一个模块出了问题时,只会影响系统的局部服务。模块之间的调用尽量异步化,调用的响应时间越长,存在的超时风险就越大;逻辑越复杂、执行的步骤越多,存在的失败风险就越大。可以在业务允许的情况下,将复杂的业务进行拆分以降低复杂度。读写分离是拆分的一种方式。写请求依赖主数据设备,读数据依赖备数据设备。当出现故障时,可以只开发读服务,写服务暂时关闭,从而减少了故障的影响面。但需要关注数据的一致性问题。
图3-2 服务无状态化
图3-3 服务拆分
4.数据存储高可用
不管业务如何拆分,有状态的数据存储服务依旧是限制整个服务高可用的瓶颈。存储高可用方案的本质是将数据复制到多个存储设备中,通过数据冗余的方式来实现高可用。但是,无论是正常情况下的传输延时,还是异常情况下的传输中断,都会导致系统的数据在某个时间点出现不一致。数据的不一致又会导致业务出现问题。分布式领域中有一个著名的CAP(Consistency、Availability、Partition Tolerance,一致性、可用性、分区容错性)定理,从理论上论证了存储高可用的复杂度,也就是说,存储高可用不可能同时满足 “一致性、可用性、分区容错性”。最多只能满足其中2 个,其中分区容错在分布式中是必须的,这意味着,我们在做架构设计时必须结合业务对一致性和可用性进行取舍。
5.服务降级
服务降级不是为了避免故障的发生,而是当故障发生时,怎么减少故障所造成的损失。这也就是我们常说的兜底预案。每种故障都应有对应的兜底方案。比如说,系统正常能提供的服务能力是100%,当故障发生后,我们能够通过有效的措施使得服务不是完全不可用,而是还能提供50%的服务能力。这类措施通常称为流量管理。常见的流量管理手段有限流和熔断。
限流是服务器端出于自我保护的目的,限定自己所接收的并发请求上限的手段。超出流量控制上限的请求将被直接拒绝或者随机选择拒绝,以防止服务过载。限流还可以结合业务进行自定义配置,优先保证核心服务的正常响应,非核心服务可直接关闭。
熔断是客户端在发出请求后,由于无法在固定期限收到预期目标,从而采取服务降级的手段。服务降级的常用手段包括返回错误提示、排队、关闭核心业务调用等,是客户端在上游服务出现故障时,避免将局部故障扩大到全局的自保手段。
6.负载均衡
负载均衡已经是高可用架构中必不可少的手段之一,可以通过按权重负载均衡、按地域就近访问等手段提升系统的整体销量,避免因为过载而导致整个系统全地域失效。
当负载均衡器检测到后端的实例连接出现错误时,会根据一定的机制将它从负载列表中清除,这样下一次请求就不会转发到有问题的后端实例上,这个过程就是故障转移。节点是否失效和恢复,皆能自动检测。判断代码可以根据业务的内容进行自定义,充分保证负载列表中的节点都是服务可用的。
7.变更流程管理
变更是影响可用性的最大因素,因此完备的流程管理(包括流程标准化和工具集的支持)直接影响变更的风险等级。对已发生的变更来说,为降低变更潜在的影响,灰度发布是对可用性的最后保障。应用的灰度部署保证变更只在小范围内发生,监控足够长时间,确保新版本没问题以后,再继续变更。与此同时,通过流量管理,还可以将新版本推送给固定特征的用户群体,以便降低对重要客户的负面影响。尽量选择在请求最低峰的时段升级,以减少影响的用户范围。尽量采用自动化发布,以减少人为发布的流程。持续集成和持续部署的自动化可以在很大程度上降低人为错误的概率。
灰度发布还应配合有效的回滚机制,这是让系统从变更引起的故障中恢复的最常规手段。对于涉及数据修改的灰度发布,发布后会引起脏数据的写入,需要可靠的回滚流程,以便保证脏数据也被清除。除了发布流程,还应在其他开发流程上做出相应的规范,比如代码质量控制、静态代码扫描、持续集成和自动化测试等。
8.服务监控
完善的监控系统对整个系统的可靠性和稳定性是非常重要的,可靠性和稳定性是高可用的一个前提。服务的监控更多是对风险的预判,在出现不可用之前就能发现问题,如果系统获取监控报警并能自我修复则可以将错误消灭于无形,如果系统发现报警无法自我修复则可以通知运维人员提早介入。
一套完善的服务监控体系需要涵盖服务运行的各个层次:基础设施监控(例如网络、交换机、路由器等底层设备的丢包错包情况)、系统层监控(例如物理机、虚拟机的CPU、内存利用情况)和应用层监控(例如请求数量和延时、服务性能和错误率等)等。越全面的监控指标越有助于运维人员判断出潜在风险。