高并发系统设计目标之高可用

高可用(High Availability,HA)

可用性指的是软件系统在投入使用时可正常提供服务的程度,或能实现其指定系统功能的概率,是衡量系统可以无故障的运行的能力。同样是一个每秒可以接受1万次请求的购物系统中,一个隔三岔五的就出现故障,而另外一个可以长时间工作,显然可以长时间工作的系统的体验更好。通常来讲对于一个高并发大流量的系统,系统的可用性通常比系统性能低下的影响更大,一个日活百万的系统,1分钟的故障影响的客户可能有成百上千,想想是多么的可拍。而随着系统的日活的增加,对于可用性的要求就越高。那么可用性都有什么衡量标准呢?

我们还经常听到一个词可靠性,可靠性是指系统可以无故障地持续运行,是一个持续的状态。与可用性相反,可靠性是根据时间段而不是任何时刻来进行定义的。如果系统在每小时崩溃1ms,那么它的可用性就超过99.9999%,但是它还是高度不可靠。与之类似,如果一个系统从来不崩溃,但是每年要停机两星期,那么它是高度可靠的,但是可用性只有96%。

可用性衡量标准

我们一般用几个9来衡量系统的可用性,下表列出了不同的可用性级别下的年故障时间与日故障时间。
在这里插入图片描述

从图中可以看出1个9与2个9下,年故障时间与日故障时间还是比较长的,如果没有太挫的程序员,基本靠人肉运维就可以实现了。但是当达到3个9与4个9的条件下,年故障时间分别降低到了8小时与52分钟,日故障时间降低到了1.44分与8.6秒。在如此低的故障时间要求下,想靠人肉运维来实现,显然是不可能的。想想看等你发现告警到打开电脑解决问题可能已经过了好几分钟了。为此当达到3个9及以上,我们就需要一整套机制来保障系统的可用性了,特别是4个9及以上我们就需要机器能够自动的处理故障,自动恢复系统的运行。
一般来说对于核心服务,我们至少要做到4个9,对于非核心服务至少要做到3个9。有些服务甚至要求做到6个9。

提升可用性手段

提升系统可用性的手段,一般需要从系统设计与运维的角度出发。下面我们分别从系统设计与运维的角度分别讨论。

系统设计

在一个成百上千的大规模机器中,发生故障是常态,为此我们进行系统设计的时候,就需要考虑如果发生了故障,系统如何恢复。我们要养成“design for failture”的思维。
系统设计我们一般从故障转移(failover)、超时控制、重试、降级、限流与熔断的维度出发。
1、故障转移(failover)
故障转移我们一般通过冗余节点来实现,当一个节点出现故障时,我们可以把流量从故障节点导流到其他节点。
对于对等服务,处理起来比较简单,由于每个节点都是无状态的,只需要简单把请求导流到其他的正常节点即可。
对于不对等服务,就比较麻烦,由于每个节点不是对等的,一般备份节点需要复制主节点的状态,不管是热备(备份节点同时也提供读服务)还是冷备(只是备份数据),当主节点故障了,failover机制需要进行主备切换。一般我们通过心跳机制来发现不可用节点。那么主备切换需要注意什么呢?主备切换一般需要考虑数据的完整性,选择数据最完整的备份节点,同时一般还需要保证只有一个主节点能够对外提供服务,为此需要使用raft,paxos等分布式一致性算法保证只有一个主节点。可以看出主备切换一般都比较复杂。

2、超时控制
对于调用方需要设置被调用服务的超时时间。想想如果没有超时时间,假如服务故障了,那么调用方的资源得不到释放并且会导致长时间得不到响应,而长时间无响应,可能还会导致整个调用练上的服务都堵塞在这个调用上,如果有大量的这种情况,那么可能导致整个系统雪崩。要是打开微博,由于故障导致长时间的停留在打开中的页面,对于用户体验的影响是多么的大。那超时时间设置的多长呢?对于一个后台服务超时时间一般不超过200ms,但是在性能篇中说过,整个系统的响应时间最好不超过200ms,如果系统的调用链比较长,200ms显然是太长了。我们在设置超时时间的时候,需要看被调用方的历史响应时间的,一般可以设置为99分位的响应时间。如果99分位响应时间较长,应该需要进行性能优化。

3、重试
对于调用方一般需要设置被调用方的重试次数,以提供更高的可用性,即使是4个9的高可用的系统,如果一个请求的调用链长度是8,在所有节点都不出故障的概率是99.92%,可用性直接少了一个9。为此我们一般需要设置重试次数,那么设置为多少次呢?对于一个4个9的系统来说,如果设置重试次数为3次,每次都出问题的概率实在太小了(概率为0.0001的4次方)。为此一般设置为3次即可。

4、降级
降级是为了保障核心服务的可用性,而舍弃非核心的应用,是一种有损的系统容错方式。
比如微博系统的反广告服务,相对于微博的发布功能,重要性相对差一些。那么再流量高峰时,反广告服务可能成为瓶颈,为了保障主流程的可用性,可以先临时关闭反广告服务。那么我们如何关闭呢?总不能通过发版实现吧。我们一般通过配置中心来实现,配置中心应该加一个开关,当打开时,调用方直接跳过或者被调用方返回某个固定值。添加开关的时候一定要通过测试并且定期的测试(可以在线上服务流量比较低的时候演练),确定添加的开关配置是有效的。
在比如,在读数据的场景中,如果降级开关打开,可以返回固定的数据。在轮询查询数据库的场景中,我们可以延长查询数据库的时间间隔,以缓解高峰流量时数据库的压力。

5、限流
限流则是限制服务的并发访问以保证系统。比如微博的注册服务,由于需要访问数据库,而数据库的TPS可能只有1000。那么我们可以限制注册服务每秒只能接受比1000小一些的请求如950。限流一定程度上也可以说是一种降级机制。

6、熔断
熔断这个引用于电路的保险丝机制,当电路的功率超过一定的阈值,保险丝就会熔断以保护电路。在服务治理中熔断指的是当服务的提供方返回连续几次错误或者超时达到一定的阈值就触发熔断,则后续的请求不在发给服务提供方而是直接返回错误。熔断是调用方的一种自我保护策略,而限流则是服务的提供方的一种自我保护策略。
服务的调用方需要维护熔断的三种状态:关闭、半关闭与打开。其状态的流转如下图
在这里插入图片描述

当服务调用失败或超时的次数达到一定的阈值,熔断进入打开的状态,后续的请求调用方直接返回错误,这里还有一个地方需要注意的,当服务返回成功了,应该重置失败次数;
半打开状态下,服务的调用方可以发送少量请求到服务提供方,如果调用超时或者失败,则重新回到打开状态;如果连续多次请求都成功,则回到关闭状态。需要注意的是,如果回到关闭状态时,如果全部流量都请求到被调用方,可能会把服务又打死,服务的提供方应该做好限流。

运维

1、灰度发布
在线上系统中出现的问题,大部分都是由于发布新版本导致的,你想要没有发布新版本,系统运行的好好的怎么会突然出现问题呢。为此我们发布新版本的时候需要采用灰度发布的策略。灰度发布是指系统的变更不是一次性的就全部发布到线上,而是一次发布部分节点只影响部分的请求如10%。恢复发布一般是以节点为单位的,比如一次发布10%的节点,然后慢慢的增加发布的节点数,当发布的过程中出现问题了,则立即回滚服务停止发布。由于我们引入了灰度发布,为此新的变更如果有问题,一般会在发布的过程中就暴露出来了,从而将影响限制在很小的范围。

2、故障演练 在一个大规模的系统中,集群由成百上千的机器构成,在如此大规模的集群中,某个节点发生故障是常态。如果没有一种机制定期测试如果某个节点发生故障,我们的集群是否能够正常提供服务,那么当真的发生了故障了,我们往往会不知所措。为此我们需要引入故障演练。故障演练指的是我们对系统做一些破坏(如随机地关闭线上节点),以观察系统的的表现,从而发现潜在的可用性问题。但是如果我们的线上系统还不能抵御这些异常情况,那么我们应该在测试环境搭建一套与线上一样的系统,以便测试。