现在而今眼目下,互联网越来越发达,大家不管是几乎每天24小时不离手的手机,还是工作时每天要打开的电脑,还是我们使用的各种社会工具和衣食住行的方方面面,各种软件应用已经成为我们生活中必不可少的一部分。软件的功能是否能满足需求,是我们关心的首要问题,软件是否稳定,也是一个重要的指标,也就是软件系统的可用性如何,在遇到一些问题时,系统的容错性如何,是否仍然能够对外提供服务,以及服务的效率如何,是迅速就能返回还是要等待很久。
回顾刚过去的2019年一年,全球大型互联网公司发生了数十起宕机事故,google、Microsoft、Amazon 、Facebook、阿里、腾讯等无一幸免,短则数分钟,长则数小时,有的是天灾,有的是人祸。对于用户上亿的商业应用来说,哪怕是一分钟的宕机,损失都非常巨大,一个中等严重的故障可能会让你损失一辆车,一个outage可能就会让你损失数座房子了。腾讯2018年宕机4小时,据估算损失了5000多万。
如何衡量软件的可用性呢?
其度量方式,是根据系统损害、无法使用的时间,以及由无法运作恢复到可运作状况的时间,与系统总运作时间的比较。计算公式为:
A=MTBF/(MTBF+MDT)
A(可用性),MTBF(平均故障间隔),MDT(平均修复时间)
可用性为99.999%的系统,一年故障时间为5分15秒;可用性为99.99%的系统,一年故障时间为5分34秒;可用性为99.9%的系统,一年故障时间为8小时46分。
在线系统和执行关键任务的系统通常要求其可用性要达到5个9标准(99.999%)。
当然,除了这个数据之外,我们也要看宕机发生在什么时候,才能综合评估事故影响的严重性,造成的损失到底有多大。当我们不得不主动的关闭或重启一些服务的时候,如何把影响减到最小。
软件应用一旦进入生产系统,接入了大量的用户,我们就得尽量保证它是24小时可用的,为用户提供不间断的服务。软件的高可用性(High availability -- HA),是一项非功能特性,在大型产品的架构设计阶段就需要专门纳入进来考虑。软件系统会划分为多个不同的模块,重要的组件或模块,都需要列出HA的Feature实施计划。
为了实现软件系统的高可用性,都有哪些关注点和技术手段呢?
1. 冗余设计和Fail over
冗余设计的核心目的是为了避免单点故障导致整个系统不可用,在不同的层级增加冗余,当单点故障发生时,可动态的把服务切换到正常的上面,从而能够继续为用户提供服务。冗余设计可以从几个方面进行考量:
1. 地理环境,如果你的所有服务器都在同一个地方,一场洪水或地震将让你的整个系统和数据化为乌有。大型系统通常都会把服务器或数据中心分布在不同的地域。这将有助于提高系统的可用性。
2.硬件,高可用的服务器必须对诸如停电,硬件损坏(如硬盘损坏,网卡损坏)等具有承受能力。
3. 软件,从操作系统到应用程序本身的整个软件栈,都需要设计来能应对非预期的失败而必须重启的情况。
4.数据,由于硬盘故障带来的数据丢失和数据不一致的情况,如何处理。高可用性系统必须要把故障情况下的数据安全考虑进来。
5.网络,非预期的网络中断是系统可能遇到的另一个问题。网络冗余策略也是非常有价值的。
先来看看软件设计层面应当如何考虑冗余,一个比较典型的B/S 架构的应用如下图所示。
我们在其每一层,都可以做冗余处理。
在网关层,我们可以配置多个网关,用keepalived管理起来,给它们分配一个浮动IP,当主网关出问题时,我们可以把浮动IP分配给备用网关,这样仍然可以继续对外提供服务。
在Web应用层,可以配置多个web后端,比如使用Nginx做网关层的反向代理时,它可以配置多个web后端,并且能够检测到多个后端的存活性,当一个web服务挂了时,nginx可以自动进行故障转移,将流量迁移到其他的web服务上。
业务服务层也可以实现冗余,在web应用层通过建立服务连接池来与下游的服务建立多个连接,每次请求可以随机的选择连接来访问下层的服务。当某一个service挂了的时候,连接池的管理器能够检测到,并自动进行故障转移。
大型系统的服务层下通常会有一个缓存层,数据缓存也可以实现冗余来达到高可用,
我们在每一个逻辑层,都可以增加冗余。如果其中一个服务挂了,可以通过自动的故障转移(fail over),让其他服务点继续提供服务。通过缓存数据的冗余实现的,常见实践是缓存客户端双读双写,或者利用缓存集群的主从数据同步与sentinel保活与自动故障转移;更多的业务场景,对缓存没有高可用要求,可以使用缓存服务化来对调用方屏蔽底层复杂性
数据库层的冗余可以采用”主从同步,读写分离”的架构,可以把它分为“读库高可用”和“写库高可用”两类。“读库高可用”的常见实践是通过db-connection-pool来保证自动故障转移,“写库高可用”则是通过写库的冗余实现的,常见实践是keepalived + virtual IP自动故障转移。
文件存储部分,对于上层文件系统而言,逻辑上是一个整体,我们可以使用分布式文件系统,利用分布式集群,提供对文件系统的支持,对外提供统一的命名空间,文件系统内部也实现了各种冗余和负载均衡操作,有比较好的容错性,提高了文件系统的可用性。
分布式文件系统广泛流行前,对存盘数据的高可靠性,可以引入RAID(独立冗余磁盘阵列)。RAID 技术将多个单独的物理硬盘以不同的方式组合成一个逻辑硬盘,同一份数据会以一定组合方式,写入多块磁盘。即使其中部分磁盘损坏,仍然可以保证数据的可用性。
2. 无状态设计
要想服务能够切换,做故障转移(fail over或fail back),很重要的一点就是不要保存业务的上下文信息,而仅根据每次请求提交的数据进行相应的逻辑处理,这样的话,多个服务实例之间就是完全对等的,请求提交到任意服务器,处理的结果是完全一样的。在这个情况下,我们就可以利用负载均衡进行无状态服务的失效转移。
但是我们总要管理session信息,这些信息可以下沉到缓存层和数据库层。这样服务层就可以根据需要做到动态的Scale-out/Scale-In和高可用了。
3. 负载均衡
上文谈到的几点,都可以看到负载均衡的影子,大型系统的网关层基本都会引入负载均衡,负载均衡可以提高系统并发性支持,反过来说减轻了单一服务的压力,提高了系统的可用性,通常会有两种形式:
· 硬件负载—F5 7层或4层网络代理
· 软件负载—Nginx, Haproxy等开源的负载均衡软件。
常用的算法通常有
1. 轮询法
2. 随机法
3. 源地址哈希法
4. 加权轮询法
5. 加权随机法
6. 最小连接数法
4.幂等设计
什么是幂等性呢,简而言之就是同样的请求,即使多次重复,也必须得到相同的结果。
幂等性在支付类场景尤为重要,比如重复的的订单号,同样的金额,不做幂等性设计,重复支付可能就会造成金额累加。
对于服务幂等性设计的要点就是一定要效验请求参数有效性,及已有数据的对比。如果同样的请求参数已经处理过就不要重复处理,直接返回,这就是幂等性核心点。
5. 超时机制
我们的服务,尤其是微服务化的场景下,通过REST API进行相互调用,为了保证系统的可用性,就需要对调用设置超时机制,一旦被调用服务超时还未返回,主调服务就应该进入超时处理流程。这样就不至于在某个服务不可用时,整个系统进入阻塞状态。
6.异步调用
采用异步调用方式调用被调用的服务,有助于将主调服务和被调服务解耦,减少等待时被阻塞的情况,并能提高系统的并发性能。对于不需要关心直接返回结果的请求类型以及当请求流程不是系统的关键路径时,采用异步化是非常有价值的。
7.服务分级与降级
在一个大系统中,一般会有核心服务和次要服务之分,那么对于不同的服务可以采用不同的处理方案,出现故障时应该优先保证核心服务的运行,对于次要的服务,可以延迟服务或在粒度范围内关闭服务。服务降级一般是关注业务,对业务进行考虑,抓住业务的层级,从而决定在哪一层上进行处理:比如在IO层,业务逻辑层,还是在外围进行处理。
8.服务熔断
服务熔断也叫服务的过载保护,服务熔断对服务提供了proxy,防止服务不可能时,出现串联故障(cascading failure),导致雪崩效应。服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;对于设计的任何一个系统,都需要进行容量的预估和最大容量设置,当外部请求量超过最大QPS容量时,应该启动防雪崩机制,以避免大量外部请求把服务压跨而不能对外提供服务。
9.系统监控
大型系统的服务模块众多,经常会因为各种原因出现进程挂掉,网络质量不好,机器宕机等现象,我们设计的系统应该具备监控上报和告警的能力,运维和开发能够通过监控报表实时的查看系统的运行状态。服务一旦出现问题能够及时发现,通过自动化处理,或者人工介入处理,从而达到缩短系统的不可用时间,提高可用性。
常见的监控指标有:CPU、带宽、内存使用率、网络连接状态,系统调用错误,成功率,PV,UV等。
谈了主要的应用治理,解析来要说说数据治理,它对于提高系统的响应效率也非常重要。
10.数据缓存
当读写的并发数越来越大时,数据库很容易成为系统的瓶颈,因此在大型系统中,缓存系统的设计就非常有必要了,设计良好的缓存系统,可以大大提高系统的可用性和并发效率。
11.数据库优化
我们需要的数据库不仅是要稳定,能提供服务,也要考虑其提供服务的效率,当数据库里面的数据有百万条以上时,查询效率就变得很低了。不仅需要考虑前面提及的读写分离,我们也需要根据业务特点,做出适当的分库、分表、分区策略,建立合适的索引。从而大大提高数据插入和查询效率。
前面讲了架构设计时应当考虑的高可用的技术手段,还有一点不可忽视的是我们的产品如何部署到生产系统中,尤其是在做产品升级的时候,升级是offline的还是online的?我们能保证不带来一点down time吗?如果系统部署到一半失败了,而线上数据还在源源不断的进来,怎么处理?如果部署失败了,系统能在不丢失新数据的情况下回滚吗?这些都可用性设计时需要考虑的问题,做不好就会带来糟糕的客户体验。