极客时间已完结课程限时免费阅读

04 | 系统设计目标(二):系统怎样做到高可用?

04 | 系统设计目标(二):系统怎样做到高可用?-极客时间

04 | 系统设计目标(二):系统怎样做到高可用?

讲述:唐扬

时长13:35大小12.45M

你好,我是唐扬。
开课之后,有同学反馈说课程中偏理论知识的讲解比较多,希望看到实例。我一直关注这些声音,也感谢你提出的建议,在 04 讲的开篇,我想对此作出一些回应。
在课程设计时,我主要想用基础篇中的前五讲内容带你了解一些关于高并发系统设计的基本概念,期望能帮你建立一个整体的框架,这样方便在后面的演进篇和实战篇中对涉及的知识点做逐一的展开和延伸。比方说,本节课提到了降级,那我会在运维篇中以案例的方式详细介绍降级方案的种类以及适用的场景,之所以这么设计是期望通过前面少量的篇幅把课程先串起来,以点带面,逐步展开。
当然,不同的声音是我后续不断优化课程内容的动力,我会认真对待每一条建议,不断优化课程,与你一起努力、进步。
接下来,让我们正式进入课程。
本节课,我会继续带你了解高并发系统设计的第二个目标——高可用性。你需要在本节课对提升系统可用性的思路和方法有一个直观的了解,这样,当后续对点讲解这些内容时,你能马上反应过来,你的系统在遇到可用性的问题时,也能参考这些方法进行优化。
高可用性(High Availability,HA)是你在系统设计时经常会听到的一个名词,它指的是系统具备较高的无故障运行的能力。
我们在很多开源组件的文档中看到的 HA 方案就是提升组件可用性,让系统免于宕机无法服务的方案。比如,你知道 Hadoop 1.0 中的 NameNode 是单点的,一旦发生故障则整个集群就会不可用;而在 Hadoop2 中提出的 NameNode HA 方案就是同时启动两个 NameNode,一个处于 Active 状态,另一个处于 Standby 状态,两者共享存储,一旦 Active NameNode 发生故障,则可以将 Standby NameNode 切换成 Active 状态继续提供服务,这样就增强了 Hadoop 的持续无故障运行的能力,也就是提升了它的可用性。
通常来讲,一个高并发大流量的系统,系统出现故障比系统性能低更损伤用户的使用体验。想象一下,一个日活用户过百万的系统,一分钟的故障可能会影响到上千的用户。而且随着系统日活的增加,一分钟的故障时间影响到的用户数也随之增加,系统对于可用性的要求也会更高。所以今天,我就带你了解一下在高并发下,我们如何来保证系统的高可用性,以便给你的系统设计提供一些思路。

可用性的度量

可用性是一个抽象的概念,你需要知道要如何来度量它,与之相关的概念是:MTBF 和 MTTR。
MTBF(Mean Time Between Failure)是平均故障间隔的意思,代表两次故障的间隔时间,也就是系统正常运转的平均时间。这个时间越长,系统稳定性越高。
MTTR(Mean Time To Repair)表示故障的平均恢复时间,也可以理解为平均故障时间。这个值越小,故障对于用户的影响越小。
可用性与 MTBF 和 MTTR 的值息息相关,我们可以用下面的公式表示它们之间的关系:
Availability = MTBF / (MTBF + MTTR)
这个公式计算出的结果是一个比例,而这个比例代表着系统的可用性。一般来说,我们会使用几个九来描述系统的可用性。
其实通过这张图你可以发现,一个九和两个九的可用性是很容易达到的,只要没有蓝翔技校的铲车搞破坏,基本上可以通过人肉运维的方式实现。
三个九之后,系统的年故障时间从 3 天锐减到 8 小时。到了四个九之后,年故障时间缩减到 1 小时之内。在这个级别的可用性下,你可能需要建立完善的运维值班体系、故障处理流程和业务变更流程。你可能还需要在系统设计上有更多的考虑。比如,在开发中你要考虑,如果发生故障,是否不用人工介入就能自动恢复。当然了,在工具建设方面,你也需要多加完善,以便快速排查故障原因,让系统快速恢复。
到达五个九之后,故障就不能靠人力恢复了。想象一下,从故障发生到你接收报警,再到你打开电脑登录服务器处理问题,时间可能早就过了十分钟了。所以这个级别的可用性考察的是系统的容灾和自动恢复的能力,让机器来处理故障,才会让可用性指标提升一个档次。
一般来说,我们的核心业务系统的可用性,需要达到四个九,非核心系统的可用性最多容忍到三个九。在实际工作中,你可能听到过类似的说法,只是不同级别,不同业务场景的系统对于可用性要求是不一样的。
目前,你已经对可用性的评估指标有了一定程度的了解了,接下来,我们来看一看高可用的系统设计需要考虑哪些因素。

高可用系统设计的思路

一个成熟系统的可用性需要从系统设计和系统运维两方面来做保障,两者共同作用,缺一不可。那么如何从这两方面入手,解决系统高可用的问题呢?

1. 系统设计

“Design for failure”是我们做高可用系统设计时秉持的第一原则。在承担百万 QPS 的高并发系统中,集群中机器的数量成百上千台,单机的故障是常态,几乎每一天都有发生故障的可能。
未雨绸缪才能决胜千里。我们在做系统设计的时候,要把发生故障作为一个重要的考虑点,预先考虑如何自动化地发现故障,发生故障之后要如何解决。当然了,除了要有未雨绸缪的思维之外,我们还需要掌握一些具体的优化方法,比如 failover(故障转移)、超时控制以及降级和限流。
一般来说,发生 failover 的节点可能有两种情况:
1. 是在完全对等的节点之间做 failover。
2. 是在不对等的节点之间,即系统中存在主节点也存在备节点。
在对等节点之间做 failover 相对来说简单些。在这类系统中所有节点都承担读写流量,并且节点中不保存状态,每个节点都可以作为另一个节点的镜像。在这种情况下,如果访问某一个节点失败,那么简单地随机访问另一个节点就好了。
举个例子,Nginx 可以配置当某一个 Tomcat 出现大于 500 的请求的时候,重试请求另一个 Tomcat 节点,就像下面这样:
针对不对等节点的 failover 机制会复杂很多。比方说我们有一个主节点,有多台备用节点,这些备用节点可以是热备(同样在线提供服务的备用节点),也可以是冷备(只作为备份使用),那么我们就需要在代码中控制如何检测主备机器是否故障,以及如何做主备切换。
使用最广泛的故障检测机制是“心跳”。你可以在客户端上定期地向主节点发送心跳包,也可以从备份节点上定期发送心跳包。当一段时间内未收到心跳包,就可以认为主节点已经发生故障,可以触发选主的操作。
选主的结果需要在多个备份节点上达成一致,所以会使用某一种分布式一致性算法,比方说 Paxos,Raft。
除了故障转移以外,对于系统间调用超时的控制也是高可用系统设计的一个重要考虑方面。
复杂的高并发系统通常会有很多的系统模块组成,同时也会依赖很多的组件和服务,比如说缓存组件,队列服务等等。它们之间的调用最怕的就是延迟而非失败,因为失败通常是瞬时的,可以通过重试的方式解决。而一旦调用某一个模块或者服务发生比较大的延迟,调用方就会阻塞在这次调用上,它已经占用的资源得不到释放。当存在大量这种阻塞请求时,调用方就会因为用尽资源而挂掉。
在系统开发的初期,超时控制通常不被重视,或者是没有方式来确定正确的超时时间。
我之前经历过一个项目,模块之间通过 RPC 框架来调用,超时时间是默认的 30 秒。平时系统运行得非常稳定,可是一旦遇到比较大的流量,RPC 服务端出现一定数量慢请求的时候,RPC 客户端线程就会大量阻塞在这些慢请求上长达 30 秒,造成 RPC 客户端用尽调用线程而挂掉。后面我们在故障复盘的时候发现这个问题后,调整了 RPC,数据库,缓存以及调用第三方服务的超时时间,这样在出现慢请求的时候可以触发超时,就不会造成整体系统雪崩。
既然要做超时控制,那么我们怎么来确定超时时间呢?这是一个比较困难的问题。
超时时间短了,会造成大量的超时错误,对用户体验产生影响;超时时间长了,又起不到作用。我建议你通过收集系统之间的调用日志,统计比如说 99% 的响应时间是怎样的,然后依据这个时间来指定超时时间。如果没有调用的日志,那么你只能按照经验值来指定超时时间。不过,无论你使用哪种方式,超时时间都不是一成不变的,需要在后面的系统维护过程中不断地修改。
超时控制实际上就是不让请求一直保持,而是在经过一定时间之后让请求失败,释放资源给接下来的请求使用。这对于用户来说是有损的,但是却是必要的,因为它牺牲了少量的请求却保证了整体系统的可用性。而我们还有另外两种有损的方案能保证系统的高可用,它们就是降级和限流。
降级是为了保证核心服务的稳定而牺牲非核心服务的做法。比方说我们发一条微博会先经过反垃圾服务检测,检测内容是否是广告,通过后才会完成诸如写数据库等逻辑。
反垃圾的检测是一个相对比较重的操作,因为涉及到非常多的策略匹配,在日常流量下虽然会比较耗时却还能正常响应。但是当并发较高的情况下,它就有可能成为瓶颈,而且它也不是发布微博的主体流程,所以我们可以暂时关闭反垃圾服务检测,这样就可以保证主体的流程更加稳定。
限流完全是另外一种思路,它通过对并发的请求进行限速来保护系统。
比如对于 Web 应用,我限制单机只能处理每秒 1000 次的请求,超过的部分直接返回错误给客户端。虽然这种做法损害了用户的使用体验,但是它是在极端并发下的无奈之举,是短暂的行为,因此是可以接受的。
实际上,无论是降级还是限流,在细节上还有很多可供探讨的地方,我会在后面的课程中,随着系统的不断演进深入地剖析,在基础篇里就不多说了。

2. 系统运维

在系统设计阶段为了保证系统的可用性可以采取上面的几种方法,那在系统运维的层面又能做哪些事情呢?其实,我们可以从灰度发布、故障演练两个方面来考虑如何提升系统的可用性。
你应该知道,在业务平稳运行过程中,系统是很少发生故障的,90% 的故障是发生在上线变更阶段的。比方说,你上了一个新的功能,由于设计方案的问题,数据库的慢请求数翻了一倍,导致系统请求被拖慢而产生故障。
如果没有变更,数据库怎么会无缘无故地产生那么多的慢请求呢?因此,为了提升系统的可用性,重视变更管理尤为重要。而除了提供必要回滚方案,以便在出现问题时快速回滚恢复之外,另一个主要的手段就是灰度发布。
灰度发布指的是系统的变更不是一次性地推到线上的,而是按照一定比例逐步推进的。一般情况下,灰度发布是以机器维度进行的。比方说,我们先在 10% 的机器上进行变更,同时观察 Dashboard 上的系统性能指标以及错误日志。如果运行了一段时间之后系统指标比较平稳并且没有出现大量的错误日志,那么再推动全量变更。
灰度发布给了开发和运维同学绝佳的机会,让他们能在线上流量上观察变更带来的影响,是保证系统高可用的重要关卡。
灰度发布是在系统正常运行条件下,保证系统高可用的运维手段,那么我们如何知道发生故障时系统的表现呢?这里就要依靠另外一个手段:故障演练。
故障演练指的是对系统进行一些破坏性的手段,观察在出现局部故障时,整体的系统表现是怎样的,从而发现系统中存在的,潜在的可用性问题。
一个复杂的高并发系统依赖了太多的组件,比方说磁盘,数据库,网卡等,这些组件随时随地都可能会发生故障,而一旦它们发生故障,会不会如蝴蝶效应一般造成整体服务不可用呢?我们并不知道,因此,故障演练尤为重要。
在我来看,故障演练和时下比较流行的“混沌工程”的思路如出一辙,作为混沌工程的鼻祖,Netfix 在 2010 年推出的“Chaos Monkey”工具就是故障演练绝佳的工具。它通过在线上系统上随机地关闭线上节点来模拟故障,让工程师可以了解,在出现此类故障时会有什么样的影响。
当然,这一切是以你的系统可以抵御一些异常情况为前提的。如果你的系统还没有做到这一点,那么我建议你另外搭建一套和线上部署结构一模一样的线下系统,然后在这套系统上做故障演练,从而避免对生产系统造成影响。

课程小结

本节课我带你了解了如何度量系统的可用性,以及在做高并发系统设计时如何来保证高可用。
说了这么多,你可以看到从开发和运维角度上来看,提升可用性的方法是不同的:
开发注重的是如何处理故障,关键词是冗余和取舍。冗余指的是有备用节点,集群来顶替出故障的服务,比如文中提到的故障转移,还有多活架构等等;取舍指的是丢卒保车,保障主体服务的安全。
运维角度来看则更偏保守,注重的是如何避免故障的发生,比如更关注变更管理以及如何做故障的演练。
两者结合起来才能组成一套完善的高可用体系。
你还需要注意的是,提高系统的可用性有时候是以牺牲用户体验或者是牺牲系统性能为前提的,也需要大量人力来建设相应的系统,完善机制。所以我们要把握一个度,不该做过度的优化。就像我在文中提到的,核心系统四个九的可用性已经可以满足需求,就没有必要一味地追求五个九甚至六个九的可用性。
另外,一般的系统或者组件都是追求极致的性能的,那么有没有不追求性能,只追求极致的可用性的呢?答案是有的。比如配置下发的系统,它只需要在其它系统启动时提供一份配置即可,所以秒级返回也可,十秒钟也 OK,无非就是增加了其它系统的启动时间而已。但是,它对可用性的要求是极高的,甚至会到六个九,原因是配置可以获取的慢,但是不能获取不到。我给你举这个例子是想让你了解,可用性和性能有时候是需要做取舍的,但如何取舍就要视不同的系统而定,不能一概而论了。

一课一思

在今天的课程中,我提到了很多保证高可用的手段,那么你在工作中会有哪些保证系统高可用的设计技巧呢?欢迎在留言区写下你的思考,我会跟你一起讨论这些问题。
另外,期待你在评论区留下更多的声音,你的建议,我尤为珍重,我会和你一起,努力将课程做好。
最后,感谢你的阅读,如果这篇文章让你有所收获,也欢迎你将它分享给更多的朋友。
分享给需要的人,Ta购买本课程,你将得18
生成海报并分享

赞 34

提建议

上一篇
03 | 系统设计目标(一):如何提升系统性能?
下一篇
05 | 系统设计目标(三):如何让系统易于扩展?
 写留言

精选留言(59)

  • jc9090kkk
    2019-09-25
    感谢老师的分享,对于文章中的心跳检测我个人认为其实是需要做区分的,心跳检测是区分内部检测和外部检测,外部检测伴随着随机性,有时候可能系统的响应时间或者IO已经出现拐点了,但是心跳机制还是有可能会收到响应包,从而被错误的当做系统是“正常”的,所以很多高可用的系统基本都是采用内部心跳机制,比如kafka中的通过zookeeper的watch机制来做检测来做broker中controller的failover,mysql里面是通过内部的performance_schema库中的file_summary_by_event_name表来做监控,都是为了避免外部监测的随机性所带来的影响,不过对于老师提出的可用性和高性能当中出取舍无比赞同,不同的解决方案总是伴随着优势和劣势,无非是优势所带来的效果比劣势对业务更有帮助而已,比如之前在工作中碰到过mysql主从同步有延迟的情况,mysql的高可能性其实就是靠主备同步的数据一致性来保证的,经过排查发现,业务代码中存在有操作大事务的SQL语句,这个导致从库拿到binlog去做重放的时候,时间周期拖长了,从而影响了业务,另外一个情况就是,在做秒杀的时候,除了采用redis外,还会对用户划分成两部分,一部分用户是来了以后直接报秒杀已结束,另外一部分用户才会进入秒杀的逻辑中,心里觉得还是挺对不住那部分抢不到秒杀的用户的,所以,有时候我们需要在可用性和可靠性上需要做一个取舍,没有十全十美的技术解决方案。除此之外,其实大部分的时候采用的策略是需要对业务尽量是无损的,不然运营和产品会请你喝茶的,最后想跟老师提个建议,就是希望后面的一些篇章,能否尽量将之前提到过的知识点实际运用起来或者说串联起来,并且提供一些实际场景的解决方案细节,我举个例子,比如电商秒杀,秒杀如果只采用mysql去做库存判断,会出现什么的问题,为了避免这样的问题出现,该采用什么系统,假如如果采用redis,为什么?redis的一些操作是原子操作,原子操作是什么?如何保证?类似这样的方案的优缺点和延伸,这样的话既能有效的总结之前学习到的知识点,能够将整个知识网络做一个规整,还能了解到每个技术方案的不足加深理解,从而明白什么样的场景说不能采用当前的技术方案的,还有就是在采用技术方案的同时,说明下采用方案的一些基础的理论,知识,比如cas,或者分布式系统中的cap理论,这样对于基础知识欠缺的人,在学习的时候也可以去补一些平时没有刻意学习的知识,上面是我举的例子,再次谢谢老师的分享,期待后面老师的干货文章。
    展开

    作者回复: 好嘞,接受你的建议,后面会给出多一些的案例来帮助理解~

    共 15 条评论
    137
  • Fourty Seven
    2019-10-10
    高可用思路, 1.开发设计层面 冗余---主备,负载均衡,failover 取舍----降级,限流,熔断,超时控制 2.运维层面 灰度发布,故障演练,监控报警
    展开

    作者回复: 👍

    共 2 条评论
    29
  • Jxin
    2019-09-25
    1.本篇讲得主要是系统设计层面的服务高可用。降级,限流,超时熔断,好像主要大方向就这些了。(走mq销峰这种,我认为也是一种降级)。 2.从运维来看,上k8s后,编排控制下,自动维护运行的服务实例节点数量算(包括无状态和有状态服务),这算是一个高可用方面的方案。 3.从部署架构来看,单机房的集群,主备,redis es的多副本等等也算。扯远点还有同城异地,异城异地以及搭建灾备中心等高成本操作。(主要看系统不可用带来的损失有多大,进而做决定)。 总之工作中,感觉追求系统高可用比追求高性能难太多。
    展开

    作者回复: 其实都挺难的:)

    共 4 条评论
    16
  • sun
    2019-09-26
    我觉得思想最重要,光教你们具体的人家用过的实际案例,那不过是画饼充饥,每个公司的业务都完全不同,侧重点也不同,掌握了思想,明白了了设计方案和要注意的点,接下来只不过是考验个人思维严谨程度和代码水平,思想大于奇淫技巧

    作者回复: 👍👍👍

    共 5 条评论
    15
  • mickey
    2019-09-25
    希望老师能带来大家一步步设计出一个系统,从简单到复杂,演化出一个高并发、高可用、高性能、可扩展的系统。可以模拟遇到具体的问题,使用什么方法去解决。

    作者回复: 好滴~

    10
  • Julien
    2019-09-26
    老师您好,我们产品中用到了MongoDB自带的集群来达到高可用性,但发现当某个节点出现故障时,MongoDB大多数时候并不能自我恢复,从而极大影响了用户体验。对于这种案例,老师有什么好的建议?

    作者回复: 可以配置replica set,这样主节点故障,付节点可以提升为主节点

    7
  • mickey
    2019-09-25
    希望老师能带来大家一步步设计出一个系统,从简单到复杂,演化出一个高并发、高可用、高性能、可扩展的系统。可以模拟遇到具体的问题,具体使用什么方法、使用哪些工具去解决。

    作者回复: 好滴~

    5
  • 任鹏斌
    2019-09-25
    讲的思路很清楚,虽然以前也知道高性能高可用,但没看过这么全面的总结。

    作者回复: 谢谢~

    4
  • 长期规划
    2019-12-15
    网络跟交通真是很像 限流:对应交通中的限号; 降级:比如火车的站票,人太多,坐座没有了,改为站票,但票价没变,这就是有损服务啊 故障转移:轿车上一般都准备一个备胎。有些人找对象时,也会准备一个备胎,以防主胎出问题了,还可以更换。
    展开

    作者回复: 赞比喻

    4
  • fomy
    2019-11-19
    我的项目中是使用Dubbo框架自带的负载均衡策略,以及Dubbo自带的失败重试功能,还有Dubbo的超时时间设置。还有一次秒杀中使用Hystrix来限流。
    3
  • 小喵喵
    2019-09-28
    RPC是什么?是一个调用远程接口的工具,还是一个什么框架?

    作者回复: Remote Procedure Call,是跨进程调用的方式

    共 3 条评论
    2
  • 罗招材
    2019-09-26
    高可用,感觉本质上是冗余,所有关键路径避免单点。 核心要解决的问题是,故障发现 和 故障转移。 故障发现包括,超过处理流量做限流、熔断等,同时某个环节的某个节点故障时,系统能自动转移到冗余备份上,复杂点在于如何保证转移的可靠性。

    作者回复: 可以这么说吧,不过限流熔断感觉还不是冗余,是一种取舍

    共 2 条评论
    2
  • 饭团
    2019-09-25
    看来基本就是,避免单点!做好熔断,限流措施!理念是这样。静待老师讲解具体手段,和分享老师的经验!

    作者回复: 好嘞~

    2
  • 天草二十六
    2020-05-13
    搭建一套和线上部署结构一模一样的线下系统,成本很高哦,估计很多没这个投入

    作者回复: 可以只部署核心系统

    1
  • kamida
    2020-04-27
    老师 请问一般load balancer和web server分别能处理多大的qps 分别设多少的rate limit比较好

    作者回复: nginx大概几万,web server(tomcat)千级别,具体看业务复杂度

    1
  • 2020-04-06
    小结一下: 1:系统怎么做到高可用?高可用强调系统在出现问题的时候还可以继续使用,那怎么能做到呢?首先,就是冗余,服务冗余+数据冗余+设备冗余,通俗点就plan B,时刻准备。然后,就是故障隔离故障恢复,出问题不可怕,只要能自动或手动恢复就行。然后就是防止系统不可用,怎么防止限流熔断就是防止的手段。 2:不过这些说起来容易做起来并不简单,知道和做到之间是有鸿沟的。比如:现在有一个1亿条数据的文件,怎么高效的导入到数据库中,设计出一个完美的方案就不是那么容易。 3:高可用性比高性能实现起来难多了,难点我觉得在于各种出现各种异常情况该怎么处理,即使是小概率事件也需要思考对应的解决方案。比如:2中的例子,高效导入怎么实现?数据这么多库表怎么设计?如果导入的过程中出现异常怎么补偿?一亿条数据不多不少都保证放入库中怎么验证?
    展开

    作者回复: 总结不错~

    1
  • 陆江
    2020-03-25
    灰度发布的机制不是运维层的,基于业务的灰度发布机制需要写代码来实现。 业界有两种比较常用的方案: 第一种就是在NGINX层挂载lua脚本的方式,在lua脚本里面实现流量控制; 第二种就是在NGINX层后面再加一层api路由网关层,路由网关层的实现就比较灵活,如果是Python的,可以选用高并发异步web框架sanic来实现。 不知道老师说的灰度发布机制是什么样的方式,后面有没有细讲部分?
    展开

    作者回复: 基本上是动态的更改nginx upstream的方式

    1
  • 周曙光爱学习
    2020-03-08
    老师请问限流一般在哪一层做呢?是在流量的接入层比如slb,还是在框架层比如使用的一些http框架,还是在我们的应用代码里呢??

    作者回复: 一般在网关层,当然越上层越好,把无用请求挡出去

    1
  • 长期规划
    2019-12-24
    系统高可用是分级的,分级是考虑最坏情况下,系统不会崩溃。

    作者回复: 是的,要分核心服务和非核心服务

    1
  • 威特
    2019-12-22
    目前我们系统的主要手段就是降级,通过降级返回一些默认值或者其他约定好的值,以保证核心功能不收影响。

    作者回复: 要演练降级的预案

    1