我先做一下自我介绍,我是 07 年加入的 Google,在总部任 SRE,今年年初回到 Coding (http://coding.net) 任 CTO。SRE 的全称是 Site Reliability Engineering ,基本相当于国内的运维,但是更偏开发。
在 Google 我参与了两个比较大的 Project。
第一个是 YouTube,其中包括 Video transcoding,streaming 等。Google 的量很大,每个月会有 1PB 级别的存储量。存储、转码后,我们还做 Global CDN。到 2012 年的时候,峰值流量达到 10 TBps,全球 10 万个节点,几乎每台机器都是 16/24 核跑满, 10G uplink 也是跑满的状态。
然后我加入了 Google Cloud Platform Team, 也就是 Borg 团队。这个团队的主要工作是就是管理 Google 全球所有的服务器,全球大概有 100 万台左右。另外就是维护 Borg 系统,同时我也是 Omega 系统运维的主要负责人,很可惜这个项目最后由于各种各样的原因在内部取消了。
下面我想跟大家分享的是关于可用性、可靠性上面的一些理念和思考。
谈可用性不需要绕来绕去,大家只谈 SLA 即可。大家可以看下面这个图:
要谈可用性,首先必须承认所有东西都有不可用的时候,只是不可用程度而已。一般来说,我们的观念里一个服务至少要做到 99.9% 才称为基本上可用,是合格性产品。否则基本很难被别人使用。
从 3 个 9 迈向 4 个 9,从 8 小时一下缩短到 52.6 分钟的不可用时间,是一个很大的进步。Google 内部只有 4 个 9 以上的服务才会配备 SRE,SRE 是必须在接到报警 5 分钟之内上线处理问题的,否则报警系统自动升级到下一个 SRE。如果还没有,直接给老板发报警。
大家之前可能听说谷歌搜索服务可用度大概是全球 5 个 9,6 个 9 之间。其实这是一个多层,多级,全球化的概念,具体到每个节点其实没那么高。比如说当年北京王府井楼上的搜索集群节点就是按 3 个 9 设计的。
有关 SLA 还有一个秘密,就是一般大家都在谈年 SLA,但是年 SLA 除了客户赔款以外,一般没有任何实际工程指导意义。 Google 内部更看重的是季度 SLA,甚至月 SLA,甚至周 SLA。这所带来的挑战就更大了。
为什么说看这个图有用,因为 99%、99.9% 是基本可以靠运气搞定的哦。到 3 个 9 可以靠堆人,也就是 3 班倒之类的强制值班基本搞定。但是从 3 个 9 往上,就基本超出了人力的范畴,考验的是业务的自愈能力,架构的容灾、容错设计,灾备系统的完善等等。
说了这么多,作为一个架构者,我们如何来系统的分解“提升 SLA”这一个难题呢。
这里我引入两个工业级别的概念 MTBF 和 MTTR。
MTBF: Mean time between Failures。 用通俗的话讲,就是一个东西有多不可靠,多长时间坏一次。
MTTR: Mean time to recover。意思就是一旦坏了,恢复服务的时间需要多长。
有了这两个概念, 我们就可以提出:
一个服务的可用度,取决于 MTBF 和 MTTR 这两个因子。从这个公式出发,结合实际情况,就很好理清高可用架构的基本路数了。那就是: 要么提高 MTBF, 要么降低 MTTR。除此之外别无他法。
要注意的是,考虑 MTBF 和 MTTR 的时候都不能脱离现实。
理论上来说,作为一个正常人类,收到突发报警、能正确的分析出问题所在、找到正确的解决方案、并且 【正确实施】的时间极限大概是 【两分钟】。这个标准我个人觉得是高到天上去了。作为一个苦练多年的 Oncall 工程师,我 2 分钟能看清报警,上了 VPN,找到 dashboard,就不错了。就算是已知问题,有应对方案,能敲对命令,完全成功,也至少需要 15 – 20 分钟。所以如果按照这个标准的话,管理的服务如果想达到 4 个 9,那么一年只能坏 1 次,2 次就超标了。实现高可用基本靠运气~
回过来接着说说 MTBF 吧。请各位想一下,影响服务MTBF的三大因素!
发布
发布
还是发布!
这个术语上叫 Age Mortality Risk。
一般一个服务只要你不去碰他一年都不会坏一次。更新越频繁,坏的可能性就越大。凡是 Software 都有 BUG,修 BUG 的更新也会引入新的 BUG。发布新版本,新功能是 MTBF 最大的敌人。
说了 MTBF 和 MTTR 这两个定义,那么具体究竟应该如何落地实践来提高可用性呢?
首先说几个大家可能都听腻了的方案
虽然道理很简单,实现起来可不简单,有很多很多细节上的东西需要考虑。
N + 2 就是说平时如果一个服务需要 1 个实例正常提供服务,那么我们就在生产环境上应该部署 1 + 2 = 3 个节点。大家可能觉得 N + 1 很合理,也就是有个热备份系统,比较能够接受。但是你要想到:服务 N + 1 部署只能提供热备容灾,发布的时候就失去保护了。
因为刚才说了, 发布不成功的几率很高!
从另一个角度来讲,服务 N + 2 说的是在丢失两个最大的实例的同时,依然可以维持业务的正常运转。
这其实就是最近常说的 两地三中心 的概念有点像。
千万不要搞一大一小,或者相互依赖。否则你的 N + 2 就不是真的 N + 2。如果两地三中心的一个中心是需要 24 小时才能迁移过去的,那他就不是一个高可用性部署,还是叫异地灾备系统吧。
想做到高可用,必须拥有一套非常可靠的流量控制系统。这套系统按常见的维度,比如说源 IP,目标 IP 来调度是不够的,最好能按业务维度来调度流量。比如说按 API, 甚至按用户类型,用户来源等等来调度。
为什么?因为一个高可用系统必须要支持一下几种场景:
Isolation。A 用户发来的请求可能和 B 用户发来的请求同时处理的时候有冲突,需要隔离。
Quarantine。用户 A 发来的请求可能资源消耗超标,必须能将这类请求钉死在有限的几个节点上,从而顾全大局。
Query-of-death。大家都遇到过吧。上线之后一个用户发来个一个异常请求直接搞挂服务。连续多发几个,整个集群都挂没了,高可用还怎么做到?那么,对这种类型的防范就是要在死掉几台服务器之后可以自动屏蔽类似的请求。需要结合业务去具体分析。
但是想要达到高可用,这些都是必备的,也是一定会遇到的场景。还是那句话,靠人是没戏的。
还记得影响 MTBF 最大的因子吗?发布质量不提高,一切都是空谈。
线下测试永远比线上调试容易一百倍,也安全一百倍。
这个道理很简单,就看执行。如果各位的团队还没有完整的线下测试环境,那么我的意见是不要再接新业务了,花点时间先把这个搞定。这其中包括代码测试、数据兼容性测试、压力测试等等。
台上一分钟,台下十年功。
可用性的阶段性提高,不是靠运维团队,而是靠产品团队。能在线下完成的测试,绝不拍脑门到线上去实验。
这个道理说起来好像也很普通,但是具体实施起来是很有讲究的。
首先灰度发布是速度与安全性作为妥协。他是发布众多保险的最后一道,而不是唯一的一道。如果只是为了灰度而灰度,故意人为的拖慢进度,反而造成线上多版本长期间共存,有可能会引入新的问题。
做灰度发布,如果是匀速的,说明没有理解灰度发布的意义。一般来说阶段选择上从 1% -> 10% -> 100% 的指数型增长。这个阶段,是根据具体业务不同按维度去细分的。
这里面的重点在于1%并不全是随机选择的,而是根据业务特点、数据特点选择的一批有极强的代表性的实例,去做灰度发布的小白鼠。甚至于每次发布的 第一阶段用户(我们叫 Canary / 金丝雀) ,根据每次发布的特点不同,是人为挑选的。
如果要发布一个只给亚洲用户使用的功能,很明显用美国或欧洲的集群来做发布实验,是没有什么意义的。从这个角度来想,是不是灰度发布可做的事情很多很多?真的不只是按机器划分这么简单。
回到本质:灰度发布是上线的最后一道安全防护机制。即不能过慢,让产品团队过度依赖,也不能过于随机,失去了他的意义。
总之,灰度发布,全在细节里。
这点不允许商量!
这么重要的话题,我还要用一个感叹号来强调一下!
但是估计现实情况里,大家都听过各种各样的理由吧。我这里有三条买也买不到的秘籍,今天跟大家分享一下,保证药到病除。
理由1:我这个数据改动之后格式跟以前的不兼容了,回退也不能正常!
秘籍1:设计、开发时候就考虑好兼容性问题!!!比如说数据库改字段的事就不要做,改成另加一个字段就好。数据存储格式就最好采用 protobuf 这种支持数据版本、支持前后兼容性的方案。最差的情况,也要在变更实施『之前』,想清楚数据兼容性的问题。没有回滚脚本,不给更新,起码做到有备而战。
理由2:我这个变更删掉东西了!回退之后数据也没了!
秘籍2:你一定是在逗我。把这个变更打回去,分成两半。第一半禁止访问这个数据。等到发布之后真没问题了,再来发布第二半,第二半真正删掉数据。这样第一半实施之后需要回滚还可以再回去。
理由3:我这个变更发布了之后, 其他依赖这个系统的人都拿到了错误的数据,再回退也没用了,他们不会再接受老数据了!
秘籍3:这种比较常见出现在配置管理、缓存等系统中。对这类问题,最重要的就是, 应该开发一种跟版本无关的刷新机制。触发刷新的机制应该独立于发布过程。 要有一个强制刷新数据的手段。
以上三个秘籍覆盖了100%的回滚兼容性问题,如果有不存在的,请务必告诉我!
回滚兼容性问题,是一个整体难题。只有开发和运维都意识到这个问题的严重性,才能从整体上解决这个问题。而解决不了回滚难题,就不可能达到高可用。
说完了变更管理,给大家带来一个7级图表,可以看看自己的服务到底在哪个可用性的级别上。
当一个服务挂了的时候……
内存数据库就容易这样。出现个意外情况,所有数据全丢。写硬盘写到一半,挂了之后,不光进程内数据没了,老数据都丢光了。碰上这样的系统,我只能对你表示同情了。
一般来说 正常的服务都应该做到这一点…… 。挂了之后最多只丢个几秒之内的数据。
要达到这一级,需要付出一定程度的技术投入。起码搞清楚如何绕过 OS 各种 Cache,如何绕过硬件的各种坑。
做的好一点的系统,不要动不动就崩溃了…… 如果一个程序能够正常处理异常输入,异常数据等,那么就给刚才说的高级流控系统创造了条件。可以把其他的用户流量导入过来,把问题流量发到一边去,不会造成太大的容量损失。
这一级就还好了,如果多个业务跑在同一个实例上,那么起码不要全部坏掉。有部分服务,比完全没有服务要好
上升到这一级别,才摸到高可用的门,也就是有容灾措施。但是可能自动化程度不高,或者是一些关键性问题没有解决,所以业务能恢复,就是比较慢。
这边蝴蝶扇了一下翅膀,天空落了个打雷,打掉了一整个机房,结果业务完全没受影响。蓝翔技校一铲子下去,互联网都要抖一抖。嘿嘿, 高可用就是为了这种事情做准备。
评测系统的第一步是收集足够的信息。想知道自己的服务是不是高可用,必须得先监测啊!不光黑盒监测,还要有白盒监测。如果有一个自动化的 SLA 监控系统,能显示实时的 SLA 变化 ,会对系统的开发计划有很强烈的指导作用。
这个事情说起来简单,实际实现起来非常复杂。 因为很多东西深究起来都有无数的坑。 比如说:
OS 的 Cache 如何绕过。
系统硬件可能也有内置的 Cache,Firmware 也可能有 BUG 等等等等。还有一些集群系统为了所谓的性能实现的 fake sync。
这里是列不全的。我提出这个等级的意思,是想让大家有这个意识去系统化的应对这个问题。比如说关键数据是不是要多存几分,然后做一些 destruction 测试。比如多模拟断电等等的极端情况,这样才能有备无患。扫雷比触雷要容易多了。
首先高可用是按业务来的,不是所有业务都能做到高可用,也不是所有都需要做到高可用。我们下了很大精力在关键业务上,比如说 Git 系统的流控,数据安全等等,其他的就没办法啦。
首先就是要确定一个共同目标。高可用是有代价的,你的业务需要做到什么程度,一定是一个系统性的考虑。给大家举一个例子,Youtube 这么多视频, 但是每个视频的每种格式,只存了1份。所以可用性肯定受影响了。但是,数据量实在是太大了,然后各种小猫视频实在是不重要。相比之下,广告视频经常存8 份。所以!想要提高可用性,必须要和开发团队找到一个共同目标。这里再给大家一个秘籍,那就是 error budget。跟开发团队确定一个可用度,比如说 99% 。 如果业务团队搞出来的东西很烂,各种状况,最后达不到这个标准。那么对不起,暂时别发新功能,只修 BUG。
比较惭愧,我们没用什么开源工具,都是内部自己开发的。企业内部管理用了Puppet,但是生产系统上没有。
莫非现在不都用 haproxy / nginx 之类的7层代理?但是其实这个原理都差不多。只要达到你的目的,可以动态切换就好。
这句话说的是刚才提到的高可用必不可少的流控系统。任何一个系统都不是完美的,总会有各种各样的 hot spot,weak spot。问题流量的定义是跟业务紧密相关的。我再举一个例子:当年 Youtube 的 CDN 服务器有个问题,后端存储慢了之后,前端的请求会聚在一起,就像水管一样,于是内存就爆了。突然压力过高,肯定就挂了。如何处理这个问题? 最后流控系统升级,每个实例自己汇报自己的内存状况,超标之后流控系统自动绕过他。把这个问题变成了自动化处理的方案,问题面大大缩小了。再举一个例子,搜索系统是典型的热点密集型系统。有的冷僻词, 查一次要去各种读硬盘。而热词,消耗很小。所以流控系统做了个功能每个请求回复都带了 cost 值,流控系统自动均衡了整个集群。
这个是刚才说的那个秘籍第二条。其实秘籍第二条就是拆!没有什么发布是不能拆的。 拆到可以安全的往前滚再往后滚。
这个问题其实没那么复杂。就是在每个机器上运行一个agent,这个agent定期进行检查操作,有问题就通知管理系统自动下线。只要注意平时收集问题现象就行了。比如说线上突然出现了一个时间不同的问题,那么就把他加到这个agent里去,下次这个问题就是自动检测自动修复的了。
这个问题比较难,一般是要做一层智能一点的业务 proxy 。业务 proxy 检测到请求发到哪,哪个后端挂,就可以进行一些处理了。还有一个办法是在挂之前后端记log,处理之前记上。我要处理这个请求了,然后处理一半挂掉了。重启之后,检查 log 发现上次是处理这个请求挂了,那么就可以屏蔽掉这个请求。