This commit is contained in:
louzefeng
2024-07-09 18:38:56 +00:00
parent 8bafaef34d
commit bf99793fd0
6071 changed files with 1017944 additions and 0 deletions

View File

@@ -0,0 +1,109 @@
<audio id="audio" title="47 | 服务治理的宏观视角" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/4b/cf/4b9d12ccfa0a5d8613d82175d3a0e1cf.mp3"></audio>
你好,我是七牛云许式伟。
## 服务治理的目标
很多开发人员可能会习惯地认为,把软件开发出来交付给用户是其工作的结束。但实际上对于任何一个产品或者产品里面的某项功能来说,把东西开发出来只是个开始,实际上这个产品或功能在其被取代或去除之前,都会有很长一段时间的维护期。
<img src="https://static001.geekbang.org/resource/image/3a/d0/3aa1cdb5be02d4da504a3b1c91624ed0.png" alt="">
上图是很基础的产品或功能的生命周期示意图。它并不只是对软件适用,而是对所有的商品适用。我们后面在 “软件工程篇” 中还会进一步探讨它。
对于这个示意图,我们核心需要理解的是两点:
其一,虽然功能开发阶段的成本是非常显性的,但是功能维护期,包括了功能迭代和售后维保,它的隐性成本往往更高。
其二,产品的功能开发期虽然有可能很短,但是它是起点,是源头。它每一分每一秒时间是怎么花的,很大程度上决定了这个产品或功能的最终维护代价。
互联网的诞生,对今天我们的生活产生了翻天覆地的影响。虽然细究起来它进入民用市场还只有短短二十多年的历史,但它的发展速度只能以 “恐怖” 来形容。
以互联网为载体的软件,它不只是在功能上要满足用户需求,还要提供健康的 24 小时不间断的服务。功能开发与维护的边界变得模糊,一些公司甚至每天都在发布新的版本。
要做到 24 小时不间断服务,这并不是那么容易的一件事情。
我们知道,传统意义上的操作系统,实现的是软件治理,它们的关注点是如何让众多的软件一起融洽相处,感觉上好像自己在独享着物理的硬件资源。
而服务治理的核心目标,除了软件治理外,更重要的是考虑如何确保这些软件能够真正做到 24 小时不间断的服务。
而这,才是服务端操作系统的使命。
## 服务治理系统
在上一讲,我们已经介绍了部分提供 24 小时不间断的服务所带来的挑战。但我们上一讲的侧重点在业务架构,所以我们主要关注点放在了对业务架构产生重要影响的内容,比如负载均衡和存储中间件。
从服务治理角度来说,把软件做出来只是一个开始。接下来我们面对的第一件事情,是如何把它发布出去。这就需要涉及部署、升级和版本管理等相关的话题。
软件在线上成功跑了起来,为用户提供了服务,我们接着面临的挑战是怎么保证它不会挂掉。这涉及非常多层面的事情。
首先是怎么知道服务是不是挂了,这就涉及监控与报警。在发现服务挂掉后,需要考虑尽快把它重启起来,恢复到正常的状态。
微观上某个软件进程挂掉不能影响到正常的服务。所以我们需要考虑各类故障域,尽可能全面地把单点故障的风险消除掉。
单点故障消除,有可能会是个运维问题,但更多时候我们也得从软件的业务架构层面去解决它。
服务治理并没有那么简单纯粹。虽然在理想情况下我们应该尽可能自动化所有故障的恢复,但故障的可能性太多,很多时候是我们无法提前预知的,这意味着人工介入无可避免。
所以,互联网不只是产生了服务端开发这样的工种,同时也产生了运维,或者说业务 SRE 这样的工种。
SRE 全称是 Site Reliability Engineer (网站可靠性工程师),这是 Google 引入的一个职位后被各类公司所借鉴。区别于传统意义上的运维SRE 也是一个特殊的工程师群体,和服务端开发一样,他们肩负着自己独特的使命。
从服务端近年来的发展来看,产业进化的方向无不与服务治理相关:如何保证服务 24 小时不间断地运行。
故障基本上是难于避免的。可以导致故障的因素非常多。我们大体可以分为这么几个层面。
其一,软硬件升级与各类配置变更。变更是故障的第一大问题源头。保证系统不出问题的最简单的方法当然是不去升级。
但从用户的服务体验和竞争力的角度来说,升级又是必需的。所以这是一个服务端开发与 SRE 之间做平衡的问题。
其二软硬件环境的故障也可能引发我们的服务异常。软硬件环境的故障包括单机故障如硬盘坏、内存坏、网卡坏、系统死机失去响应或重启等。机房或机架故障如断网、断电等。区域性故障如运营商网络故障、DNS服务商故障、自然灾害比如地震等。
对于一个规模化的服务系统,从不间断服务的角度,低概率的软硬件环境故障就会变成必然事件。比如我们考虑,假设一块硬盘的寿命是三年,也就是说每 1000 天可能会发生一次故障,但如果我们的服务集群有 1000 块硬盘,这就意味着平均每天都会坏一块盘。
其三,终端用户的请求也可能引发故障。比较典型的场景是秒杀类,短时间内大量的用户涌入,导致系统的承载能力超过规划,产生服务的过载。当然还有一些场景比如有针对性的恶意攻击、特定类型的用户请求导致的服务端资源大量消耗等,都可能引发服务故障。
所以,一个合理的服务治理系统,不只是需要能够及时反应业务系统的健康状况。更重要的是,要在发生了故障的情况下,能够提供故障跟踪与排查的有效线索,方便业务 SRE 可以快速定位跟踪的根因Root Cause并进行及时的止损。
当然,大部分情况下服务是正常的。但这并不代表我们就不会遇到麻烦。从服务单例用户的角度来说,我们服务可能没有发生故障,但是我们的某个用户就是访问不了我们的服务,或者访问服务没有得到预期的结果。
从单例用户的支持角度,我们还需要考虑服务的可支持性。为什么我访问不了?为什么我点击某个按钮没有反应或者报错?如果我们不体系化去考虑这些问题,我们的售后支持将极其低效。
综上所述,一个服务治理系统看起来是这样的:
<img src="https://static001.geekbang.org/resource/image/a1/c0/a12eb8c0d40fc04f95c3da7d07746fc0.png" alt="">
这很不容易。
## 服务治理的发展历程
服务治理的发展进程涉及面非常之广。有自动化有业务架构改造还有人力SRE
最早,我们可能从最基本的脚本开始。我们可能 SSH 进入某一台机器,执行特定脚本。
最初的自动化努力给我们争取了足够的时间和必不可少的经验。
脚本的适用性如何?怎么才能让单个脚本不是 “任务” 的抽象,而是 “服务治理方法论” 的结果?
**我们的期望,是把服务治理建立成自治系统,而不是简单的自动化系统。**
基于这样的思考,人们逐渐建立了基于物理机器资源的服务治理体系。脚本成为了平台。而平台的形成,正是脚本的抽象化、产品化、普适化的结果。
把一个服务实例绑定在某一台物理的服务器,虽然让服务视图看起来很直观,但是这种绑定让我们应对物理资源故障变得被动,同时也不利于服务器资源的充分利用。
所以虚拟机和容器技术的诞生,促使人们开始探索物理资源和应用服务之间的解耦。而一旦我们完成了这一步,服务的逻辑视图就完全语义化了,它与物理资源就只是一个应用的过程。物理资源环境发生任何故障,都可以迅速在新的硬件设备上重新构建。
对 SRE 来说,机器的损坏和生命周期管理基本上已经不需要任何操作了。硬件已经被池化。成千上万的机器加入系统,或者出现问题,被修复,这一切都不需要 SRE 的任何操作。
这意味着,随着系统的层次结构不断上升,我们完成了从手动触发,到自动触发,到自主化。
这正是今天 DCOS数据中心操作系统走的路。
## 结语
今天我们对本章服务治理篇做了概要的介绍。服务治理不是纯理论,没有简洁的抽象问题模型,我们面对的是现实世界的复杂性。这些现实的复杂性,必然带来解决方案的复杂性。
直到今天为止,很多问题仍然没有被圆满解决。但是,它们的确已经在被解决的边缘。相关领域的探索与发展,日新月异。
如果你对今天的内容有什么思考与解读,欢迎给我留言,我们一起讨论。下一讲我们聊聊 “事务与工程:什么是工程师思维”。
如果你觉得有所收获,也欢迎把文章分享给你的朋友。感谢你的收听,我们下期再见。

View File

@@ -0,0 +1,77 @@
<audio id="audio" title="48 | 事务与工程:什么是工程师思维?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/33/05/336b5519da4669b58c3ac0b5cdc60d05.mp3"></audio>
你好,我是七牛云许式伟。
服务治理的目标,是保障软件提供 24 小时不间断服务。服务治理没有简洁的抽象问题模型,我们需要面对的是现实世界的复杂性。
保障服务的健康运行,必然有大量的事务性工作,运维或 SRE网站可靠性工程师这样的职业也由此诞生。
## 事务与工程
但是如果我们停留在事务中不能出来,那么随着我们所服务的用户数量增加,必然需要招聘大量的人员来应对繁重的事务工作。
事务性的工作不会总是让人不开心,特别是工作不太多的时候。已知的、重复性的工作有一种让人平静的功效。完成这些事可以带来一种满足感和快速胜利感。事务工作可能是低风险低压力的活动,有些员工甚至喜欢做这种类型的工作。
但是我们必须清楚,在 SRE 所扮演的角色中,一定数量的事务工作是不可避免的,这其实是任何工程类工作都具有的特点。少量的事务存在不是什么大问题。但是一旦事务数量变多,就会有害了。如果事务特别繁重,那就应该非常担忧了。
如果花在工程项目上的时间太少,你的职业发展会变慢,甚至停滞。我们可以鼓励那些做脏活累活的人,但仅仅限于在这些工作不可避免,并有巨大的正面影响的时候才会这样做。没有人可以通过不停地做脏活累活实现自己的职业发展。
## 把问题彻底解决
那么,什么是工程师思维?
在部分所谓的技术导向型公司,可能存在一些思维惯性,销售和产品经理会觉得自己没有话语权,开发工程师会觉得自己的地位高人一等。
对此我其实很反感。推崇技术当然不是个问题,但是所有的健康公司都必然是业务导向的公司,所有的技术人员如果希望有好的职业发展,也必然需要去理解业务。
七牛是推崇工程师文化的,但工程师文化显然并不是去尊崇工程师这样的职业。
什么才是真正的工程师文化?
从浅层的意义来说工程师就是要实现业务的自动化。DON'T REPEAT YOURSELF! 某件重复发生的事情只干一次就好,以后也不需要再重复做。
工程师的自动化思维,所体现的内在逻辑是如何把问题 Close如何把问题彻底解决掉而编码只是一种工具。
在我们日常生活中,很多问题不需要编码来解决,但是确实需要用 “彻底解决它” 的思维去完成。这种思维不仅限于工程师,同样适用于所有人。比如,我们开餐厅需要解决服务质量的问题,这一点可能海底捞就解决得很好,但是不一定是用编码的方式解决。同样地,假设我们办线下市场活动,要解决内容质量的问题。怎么彻底解决它,这是值得深度思考的问题。
很多人会习惯呆在自己的舒适区,习惯于做任务,每天重复相同的作业,这就不符合我们所说的 “工程师文化”。我们需要达到的状态是,今天干完一件事,明天开启新的事。
怎么判断自己在做新的事情?那就要看我们问题是否解决得够彻底。
比如我在做新媒体运营,每天写着不同的公众号文章,这是否代表我在做新的事情?答案显然是不一定。要回答这个问题,我们首先需要搞清楚的是,我每天发公众号文章,是在解决一个什么样的问题。如果我们没有想清楚这一点,那么我们就不是在 Close 问题,我们只是在做任务而已。
我们的目标显然不应该是每天发一篇文章。这是在定义一件事务,而不是定义一个目标。把问题定义清楚非常非常重要。清楚了问题,就是设定清楚了我们的目标。然后才能谈得上去彻底解决掉它。
从另一个维度看,工程师这种把问题 Close彻底解决掉的思维看重的是自己工作内容的长期价值。如果我们只是在做事务如果我们并没有在实质性解决一个问题那么这件事情的长期价值就是零。
**所以本质上,工程师文化也是产品文化,把问题以一种自动化的方式解决。**这才是我们真正应该尊崇的工程师文化。
一个公司各个岗位是彼此协作的团队,工程师并不是特殊群体。销售、技术支持、产品、开发工程师每一个角色都是平等的。每个人都应该秉承工程师精神,把一个个问题 Close让它不要再发生。不需要显得很忙忙不代表成就真正的工程师文化应该是推动整个团队往前走每个团队成员都在成长。
## 系统化思维与批判精神
从更深层次来说,工程师思维是一种系统化的思维。仅仅是编码和自动化是不够的,很可能你编码也只是在实现某种事务性工作,而不是用系统性或者说结构化的方案来解决问题。
真正的工程师会系统化地考虑方案的有效性。他们追求的是用最小化的编码工作来解决更大范围的问题。
**少就是指数级的多!**
现实中,一些工程师经常对于自己编写的代码形成一种情感依附,这是人之常情。一些人可能会在你删除多余代码时提出抗议:“如果我们以后需要这个代码怎么办?”“我们为什么只是把这些代码注释掉,这样稍后再使用它的时候会更容易吗?”“为什么不增加一个功能开关?”
这些都是糟糕的建议。源代码管理系统中的回滚其实很容易,但大量的注释代码则会造成干扰和混乱,尤其是我们还要继续演进时。那些由于功能开关没有启用而没有被执行的代码,更是像一个个定时炸弹一样等待爆炸。
极端地说,当你指望一个软件 24 小时不间断服务时,在某种程度上来说每一行代码都是负担。所以 SRE 需要推崇的实践是保证所有的代码行都有必须存在的目的。
另外,从软件工程角度来说,传统意义上的工程强调的是复制性,但软件的编码却是一项不确定性很强的创新性工作,我们总在不断迭代出新的技术。所以软件工程是颇为复杂的东西,它需要在不确定性和复制性这对儿矛盾中平衡。
所以优秀的工程师还需要有批判精神。经验当然是有价值的,但过于相信惯例就会抑制创新能力。寻求本源,不迷信惯例和权威。以数据为指导,从根源出发去系统性解决问题。
## 结语
今天看起来我们的话题有了一次比较大的跳跃,谈起了工程师思维和工程师文化。但服务治理不是纯理论,没有简洁的抽象问题模型。我们面对的是现实世界的复杂性。这些现实的复杂性,背后是大量的事务工作,尤其是我们对问题还不够了解的时候。
这个时候,工程师思维在背后起到了关键性的支撑。正是我们坚持了批判精神,坚持了以系统化的思维来把问题彻底解决,才有今天服务治理系统的日新月异的发展。
如果你对今天的内容有什么思考与解读,欢迎给我留言,我们一起讨论。下一讲我们聊聊 “发布、升级与版本管理”。
如果你觉得有所收获,也欢迎把文章分享给你的朋友。感谢你的收听,我们下期再见。

View File

@@ -0,0 +1,131 @@
<audio id="audio" title="49 | 发布、升级与版本管理" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a4/0e/a45197fa164e25be8102ee62181a400e.mp3"></audio>
你好,我是七牛云许式伟。
今天我们探讨服务治理的第一个环节:发布与升级。
在应用开发工程师完成一个版本的迭代后,他们交付的是软件新版本的源代码,这些代码存储在源代码仓库中。
一次正常的发布过程,大体分为这样几个典型的步骤:
- 构建:从源代码仓库检出源代码,编译出对应的目标文件,也就是我们新版本的软件。
- 测试:对新版本的软件进行测试,以确认软件的质量符合期望。
- 打包:将新版本的软件及其执行所需的相关文件,比如配置文件,一起打包并记录相应的版本号。
- 部署:将打包好的新版本更新到线上环境。为了保证线上环境的质量,更新过程往往需要灰度,而不是一步到位直接全面切换到新版本。
当然,并不是所有的升级都是发布新版本的软件。有时候我们仅仅只是进行配置变更,也就是修改线上的配置参数。配置参数可能存在于软件配套的配置文件中,也可能存在于线上的某个配置数据库。
整个发布与升级的过程,大体可以用下图来表示。
<img src="https://static001.geekbang.org/resource/image/05/2e/05763faab10855d55880de570475e52e.png" alt="">
从上面我们可以看出,发布是一个具备很强的事务特征的工作,过程很复杂。不仅如此,发布工作的心智负担也很大。所有 SRE 都应该牢牢记住以下这句七字箴言:
>
变更是故障之源。
我们应该怎么做,才能彻底解决发布与升级的问题?
让我们从 “工程师思维” 的角度,用系统化、产品化的思维来考虑这样一个复杂事务。
我们第一个要回答的问题是:我们的发布哲学是什么?
## 密闭性与可重复性
为保障服务可靠运行需要可靠的发布流程,我们首先要保证的是发布过程的密闭性与可重复性。
可重复性是我们的核心目标。相同的版本可以反复发布,不应该由此产生什么副作用。只有做到可重复,我们才可以安全地进行升级,或者在发现问题时安全地回滚。
要做到可重复性,就需要保证密闭性。
所谓密闭性Hermetic简单说就是环境的完整性。
比如,软件的源代码必须是密闭的,每次通过特定的版本号,检出内容必须是完整的,一致的且可重复的。编译的时候不需要再去任何第三方额外检出外部依赖的源代码。
再比如,从构建过程来说,同样必须确保一致性和可重复性。让两个工程师在两台不同的机器上基于同一个源代码版本构建同一个产品,构建结果应该是相同的。这意味着它不应该受构建机器上安装的第三方类库或者其他软件工具所影响。构建过程需要指定版本的构建工具,包括编译器,同时使用指定版本的依赖库(第三方类库)。编译过程是自包含的,不依赖于编译环境之外的任何其他服务。
## 从自动化到自服务
发布过程一方面是如此复杂,另一方面却又频繁地被执行。所以单单将发布事务做到单次发布的自动化是远远不够的。
为了应对大规模扩张,每个团队必须能够自给自足。故此,很多公司会成立工程效率团队。工程效率工程师将负责开发工程效率平台,包括发布相关的工具,制定发布的最佳实践。
这样,产品研发团队可以自己掌控和执行自己的发布流程。
每一个团队都可以决定多久或者什么时候来发布产品的新版本。发布过程可以自动化到“基本不需要工程效率工程师干预”的程度。很多项目都是利用自动构建工具和部署工具平台来自动构建、自动发布的。发布过程是真正自动化的,工程师仅仅在发生问题时才会进行干预。
这就是自服务的思想。
在这种配合模式下,团队之间配合有着清晰的边界。工程效率团队为发布平台的效率负责,产品研发团队为产品负责。用工程师土话来说,这叫 “吃自己的狗粮”。
## 追求速度
以什么样的频率来发布新版本比较好?
我们认为在质量保障,能力满足的前提下,越频繁越好。
可以从两个角度来看版本发布的频率。
其一是市场竞争。产品迭代速度可以看作市场竞争力的体现。尤其是面向用户的软件,发布频率往往需要非常频繁。甚至有的团队会采用一种 “测试通过即发布Push On Green” 的发布方式,也就是说,发布所有通过测试的版本。
其二是工程质量。我们认为,频繁的发布可以使得每个版本之间的变更减少。这种方式使得测试、出错的调试和定位工作变得更简单。
所以,无论是从市场竞争还是工程质量管理的角度,我们都鼓励这样的版本发布哲学:
>
少量发布、频繁发布。
从数据驱动的角度,我们需要监测各种数据,尤其是我们关注的核心指标。例如,我们需要监测发布速度,也就是从代码修改提交到部署,再到生产环境一共需要多长时间。
## 重视质量,尊重流程
在发布流程中,有很多需要进行质量保障的环节。包括:
- 代码评审Code Review批准源代码改动
- 批准创建新的发布版本,基于源代码仓库的某个版本,以及可能的少量 Bug 修改;
- 批准实际去部署某个发布版本;
- 批准配置修改。
要确保在发布过程中只有指定的人才能执行指定的操作而不能随随便便跳过必要的环节进行发布。另外SRE 需要非常了解某个新发布中包含的所有具体改动,以便在发布出现问题时可以更快地进行在线调试。
这意味着自动化发布系统需要能够整合并提供每个发布中包含的所有改动的报告包括但不限于源代码修改的记录、Bug Issue、配置修改等等。
## 配置管理
配置管理在发布过程中看起来很小,但是它其实是线上不稳定性的重要来源。
配置管理随时间在不停地发展。七牛云早期通过代码仓库来管理线上环节的所有配置。这有非常大的好处,所有的配置变更就如同源代码变更一样,可以被跟踪,也可以进行严格的代码评审。
但随着集群规模的增加,这种方式的弊端也越来越突出。
最大的问题是,配置变更并不完全来源于版本发布。线上故障也会引发配置变更,比如 A 机器由于某种原因要下线,可能需要把服务迁移到 B 机器,这也会引发配置变更。
随着机器数量的增加,线上配置变更就会变得相当频繁。
基于代码仓库做配置变更管理,在应对硬件故障时显得很拙劣。在理想情况下,硬件故障的响应应该是免操作的,不需要 SRE 进行任何操作。
有两个方式可以解决这个问题。
方式一是引入配置中心,把有些高频的配置变更支持做到应用逻辑中去。服务治理中有一个子课题叫 “服务发现”,就是基于这样的思想。
方式二是将配置管理与物理硬件环境彻底进行解耦这也是数据中心操作系统DCOS在做的事情。本质上你也可以把它理解成是将高频的配置变更支持做到应用逻辑中只不过这由一个基础平台来实现罢了。
## 结语
今天我们探讨服务治理的第一个环节:发布与升级。它包括了以下这些子过程:
- 构建;
- 测试;
- 打包;
- 部署;
- 配置变更。
我们并没有探讨具体的发布与升级系统怎么做,虽然业界针对发布的各个环节其实都有蛮多的实作案例。如果你正在评估应该采纳什么样的系统,可以结合我们今天探讨的发布哲学来进行评估。
发布系统非常复杂,有很大的事务工作量。要做到高效的发布能力,工程师思维是关键性的支撑,我们需要坚持以系统化的思维来彻底解决发布问题。
如果你对今天的内容有什么思考与解读,欢迎给我留言,我们一起讨论。下一讲我们聊聊 “日志、监控与报警”。
如果你觉得有所收获,也欢迎把文章分享给你的朋友。感谢你的收听,我们下期再见。

View File

@@ -0,0 +1,182 @@
<audio id="audio" title="50 | 日志、监控与报警" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/34/c0/344ca5f4a99949eab9dcf089cf69c6c0.mp3"></audio>
你好,我是七牛云许式伟。
上一讲我们介绍了发布与升级,这是一项复杂的事务,有非常长的业务流程,包括:构建、测试、打包、部署以及配置变更。但总体上来说,发布与升级在 SRE 的工作范畴中,还并不是最难工程化的事务工作。我们简单分析就可以明白:发布与升级总体上来说,只和集群中服务之间的调用关系有关,而与具体服务的业务特性没有太大的相关性。
真正难工程化的是监控与报警。
## 好的监控与报警系统是怎样的?
监控一个复杂的业务系统本身就是一项极其复杂的事务。即使在具有大量现成的基础设施的情况下设计监控项、收集数据指标、显示并提供报警支持这些工作通常需在10 人的 SRE 团队中,可能就需要有 12 人全职进行监控的构建和维护工作,这些工作都是非常定制化的,与具体业务密切相关。
如果我们把服务比作一个人的话,发布与升级更像是一个交通工具,尽管内部机制也许复杂,但是从功能上来说,它只是把我们载到某个目的地,人的个性在这里并不需要得到充分的重视。
但监控与报警不同,它更像是私人医生,需要因人而异,因地制宜,提供一套完整的健康保障方案。
监控与报警的目标是什么?
简单说,监控的核心目标就是要解决好两个问题:其一,什么东西出故障了。其二,为什么它出故障了,根因在哪里。
一个监控与报警的系统(简称监控系统),在于它可以让一个业务系统在发生故障时主动通知我们,或者告诉我们即将发生什么。当系统无法自动修复这个问题时,通过报警引导 SRE 人工来介入。SRE 首先判断目前是否真实存在某个故障,如果存在故障则采取手段来消除故障,解决故障后最终还要找出导致故障的根源问题,并推动各方去彻底解决掉它,避免未来出现类似的问题。
一个初级的监控系统是比较好做的。我们坚持不懈地往里面加各种监控项,系统看起来越来越完善。很多人会由此而感觉到了满足。
但是,一个有 “完善的监控项” 的监控系统,是不是就是一个好的监控系统了?
这当然不是。
要做好监控,我们一定要分清楚什么是现象,而什么是原因。
某个监控项不正常了“某个东西出故障了”这只是一种现象Symptom。但这个监控项 “为什么出问题” 了则是原因。我们找到的原因也可能只是中间原因而不是根因Root Cause
在一个复杂系统中,某一个服务的现象可能是另外一个服务的原因。例如,数据库性能问题。数据库读操作很缓慢是数据库 SRE 角度检测到的一个现象。然而,对前端业务的 SRE 来说,他们看到的是网站缓慢这个现象,而数据库读操作的缓慢则是原因。当然数据库慢只是一个中间原因,它还无法对应到一个明确的动作,但这对缩小问题定位范围已经产生了巨大作用。
一个完善的监控系统,并不是 “报警很多很完善” 的系统,而是 “信噪比高,有故障就报警,有报警就直指根因” 的监控系统。
“信噪比高”关注的是误报率问题。
我们不应该仅仅因为 “某东西看起来有点问题” 就发出报警。报警处理会占用员工的宝贵时间。如果该员工正在工作时间段,该报警的处理会打断他原本的工作。如果该员工正在家,该报警的处理则会影响他的个人生活,甚至是把他从睡眠中叫醒。
当报警出现得太频繁时,员工就会进入“狼来了” 效应,怀疑报警的有效性甚至忽略报警。有的时候在报警过多的时候,甚至会忽略掉真实发生的故障。由于无效信息太多,分析和修复可能会变慢,故障时间也会相应延长。高效的报警系统应该提供足够的信息,并且误报率非常低。
“有故障就报警” 关注的是报警的覆盖率。如果我们通过客户报障或其他手段发现故障,对于监控系统来说,就应该认为是一次监控事故。
“有报警就直指根因” 关注的是报警的有效性和排障的效率。一个初级的监控系统,往往很容易产生的报障现象是,线上发生一个故障会同时会产生大量的报警,这些报警杂乱无章,接警人看到一堆报警后,并没有有效的信息指引他如何快速消除故障,并找到故障的根本原因。
## 日志:监控与报警的基础
一个现代化的监控与报警系统,最底层往往基于一个日志系统。什么是日志?它不局限于业务服务输出的程序日志,实际上更多的数据来源是各种系统指标的采集。简单说,凡是时序相关的、持续产生的数据,都可以称之为日志。
原始的日志有可能是结构化的,也可能是非结构化的。如果是非结构化的数据,那这就需要先经过文本解析过程进行结构化。结构化后的日志存储系统,本质上就是一个时序数据库。
日志通过收集、结构化、清洗等步骤后,就可以对外提供日志分析和检索服务。分析和检索的结果可以直接提供数据结果,也可以用报表形式呈现,或者在满足特定条件下触发报警。
采用时序数据库来做监控系统的好处是,不依赖特定的脚本来判断系统是否正常工作,而是依赖一种标准数据分析模型进行报警。这就使得批量、大规模、低成本的数据收集变得可能。
收集回来的数据可以同时用来渲染图表和报警。报警规则往往用简单的数学表达式形式表达。这样,数据收集过程就不再是一个短暂的一次性过程,所有收集回来的历史数据都可以被用来作为报警规则的计算因素。
不同监控规则产生的报警alert可能有不同的优先级比如紧急状态和一般状态。紧急状态的报警通常意味着已经产生或即将产生用户可见的影响需要立刻介入处理。而一般状态则可以延迟到第二天再处理。报警的目标对象不一定是某个人而可能是某个系统比如工单。
当然,监控一个指标并不一定是出于报警的考虑。它还可以有各种原因,如下:
- 分析长期趋势。例如每日活跃用户的数量,以及数量增长的速度。
- 跨时间范围的比较,或者是观察 AB 测试组之间的区别。例如增加新节点后memcache 的缓存命中率是否增加?网站是否比上周速度要慢?使用 A 方案和 B 方案哪个更有助于用户的活跃?
- 临时性的回溯分析,即在线调试。例如,我们网站的请求延迟刚刚大幅增加了,有没有其他的现象同时发生?
## 添加监控项
搭建好了监控系统,收集上来了监控数据,我们第一件事情就是添加监控项。不得不承认,它是监控与报警系统中最难的一件事情。我们都需要注意些什么呢?
#### 4 个黄金指标
首先,让我们谈谈监控系统的 4 个黄金指标。它们分别是:延迟、流量、错误和饱和度。如果我们只允许监控某个系统的 4 个指标,那么就应该监控这 4 个指标。
延迟,也就是服务处理某个请求所需要的时间。延迟指标区分成功请求和失败请求很有必要。例如,某个由于数据库连接丢失或者其他后端问题造成的 HTTP 500 错误可能延迟很低。在计算总体延迟时,如果将 HTTP 500 回复的延迟也计算在内的话,可能会产生误导性的结果。但是,“慢” 错误要比 “快” 错误更糟!极少量的慢错误请求就可能导致系统吞吐能力的大幅降低。因此,监控错误回复的延迟是很重要的。
流量,是系统负载的度量方式。通常我们会使用某个高层次的指标来度量,比如 IOPS、每秒交易量等。不同的业务系统的流量指标有较大差别。例如对于普通 Web 服务器来说,该指标通常是每秒 HTTP 请求量IOPS同时可能按请求类型分类静态请求与动态请求。对于音频流媒体系统来说这个指标可能是网络 I/O 速率,或者并发会话数量。针对键值存储系统来说,指标可能是每秒交易数量,或每秒的读取操作数量。
错误,也就是请求失败的数量。请求失败的表现很多样。最简单的当然是显式失败,例如 HTTP 回复 500 状态码。还有的请求可能是隐式失败,例如 HTTP 回复虽然是 200但回复内容中提示出现了错误。还有一种是策略原因导致的失败。例如如果我们要求回复在 1s 内发出,任何超过 1s 的请求就都认为是失败请求。
饱和度Saturation它度量的是服务容量有多 “满”。通常是系统中目前最为受限的某种资源的某个具体指标的度量。比如,在内存受限的系统中,即为内存的使用率;在 I/O 受限的系统中,即为 I/O 的使用率。要注意,很多系统在达到 100% 利用率之前性能就会严重下降,增加一个利用率目标也是非常重要的。
饱和度是最需要进行预测的指标。比如,一个典型的预测是:“看起来数据库会在 5 个小时内填满硬盘”。
在复杂系统中,饱和度可以配合其他高层次的负载度量来使用。例如,该服务是否可以正常处理两倍的流量,是否可以应对 10% 的额外流量?这些是 SRE 面临的非常现实的容量规划上的问题。
为什么我们需要做负载测试,也是为了评判服务的饱和度,究竟受何种度量指标的影响。大部分服务都习惯使用某种间接指标,例如 CPU 利用率,或者网络带宽等来评判饱和度,因为这些指标通常有一个固定的已知的上限。
延迟增加是饱和度的前导现象。所以 99% 的请求延迟(在某一个小的时间范围内,例如五分钟)可以作为一个饱和度早期预警的指标。
如果我们度量所有这 4 个黄金指标,同时在某个指标出现故障时发出报警,或者对于饱和度来说,快要发生某类故障时进行预警。只要能做到这些,服务的监控就基本差不多了。
#### 关于长尾问题
构建监控系统时很多人往往会采用某种量化指标的平均值。比如延迟的平均值节点的平均CPU 使用率等。这些例子中,后者存在的问题是很明显的,因为 CPU 的利用率的波动可能很大。
但是其实同样的道理也适用于延迟。如果某个 Web 服务每秒处理 1000 个请求,平均请求延迟为 100ms。但是 1% 的请求可能会占用 5s 时间。如果用户依赖好几个这样的服务来渲染页面,那么某个后端请求的延迟的 99% 值很可能就会成为前端延迟的中位数。
区分平均值的 “慢” 和长尾值的 “慢” 的一个最简单办法是将请求按延迟分组计数。比如,延迟为 010ms 之间的请求数量有多少30100ms 之间100300ms 之间等。可以按分组制作成直方图。将直方图的边界定义为指数型增长这个例子中倍数约为3是直观展现请求分布的最好方式。
#### 采用合适的精度
应该仔细设计度量指标的精确度这涉及到监控的成本问题。例如每秒收集CPU负载信息可能会产生一些有意思的数据但是这种高频率收集、存储、分析可能成本很高。如果我们的监控目标需要高精度数据但是却不需要极低的延迟可以通过采样+汇总的方式降低成本。例如:
- 将当前 CPU 利用率按秒记录。
- 按 5%粒度分组,将对应的 CPU 利用率计数+1。
- 将这些值每分钟汇总一次。
这种方式使我们可以观测到短暂的 CPU 热点,但是又不需要为此付出高额成本进行收集和保留高精度数据。
#### 怎么添加监控项?
为什么我前面说,添加监控项是最难的事情?添加监控项看起来像是一个很繁琐的事务工作,但实际上它非常依赖你的架构能力。
一个很牛的监控 SRE他要干的绝对不是不停地添加新的监控项以 “完善某个业务的监控”。
**少就是指数级的多!**
就和软件开发工程师需要经常需要重构,去删减掉自己历史的无效代码一样,负责业务监控的 SRE 也需要经常重构自己的监控指标,以期用最少的监控项,来全面覆盖和掌控系统的健康状况。
我们需要遵循的监控与报警的设计哲学。记住这些哲学将有助于鼓励团队在解决问题时向正确的方向进行。
当我们打算为监控系统增加新规则时,在心中回答以下问题:
- 该规则是否能够检测到一个目前检测不到的、紧急的、有操作性的,即将发生或者已经发生的用户可见故障?
- 是否可以忽略这条报警?是否还有其他人也会收到这条报警?
- 这条报警是否确实显示了用户正在受到影响?是否存在用户没有受到影响也可以触发这条规则的情况?例如,系统维护状态下发出的报警是否应该被过滤掉。
- 收到报警后,是否要进行某个操作?是否需要立即执行该操作,还是可以等到第二天早上再进行?该操作是否可以被安全地自动化?
- 该操作的效果是长期的,还是短期的?
以上这些问题其实反映了在报警上的深层次的理念:
- 每当收到紧急状态的报警时,应该立即需要我进行某种操作。每天只能进入紧急状态几次,太多就会导致“狼来了”效应。
- 每个紧急状态的报警都应该是可以具体操作的。
- 每个紧急状态的报警的处理都应该需要某种智力分析过程。如果某个报警只是需要一个固定的机械化动作,那么它就应该被自动化。
- 每个紧急状态的报警都应该是关于某个新问题的,不应该彼此重叠。
## 接警:故障响应
接到报警我们应该怎么做?
接警后的第一哲学,是尽快消除故障。找根因不是第一位的。如果故障原因未知,我们可以尽量地保留现场,方便故障消除后进行事故的根因分析。
每一个监控项的报警应该尽可能代表一个清晰的故障场景。这会极大改善监控的有效性,直指根因,消除故障自然也就更快速。
虽然越少越好,但是不清楚故障原因的报警是难以避免的,否则我们的报警就难以完整覆盖所有的故障。比如,对于业务服务的入口级的故障,我们怎么也得报出来。每发生一次新的入口级的故障场景,我们就有必要把这个故障场景独立出来,这样下一次出现同类故障时我们就能够直接定位到根因了。
一般来说,有清晰的故障场景的监控报警,都应该有故障恢复的预案。而在那些故障原因不清晰的情况下,消除故障的最简方法是基于流量调度,它可以迅速把用户请求从故障域切走,同时保留了故障现场。
解决了线上的故障,我们就要开始做故障的根因分析,找到问题发生的源头。
这主要仰仗两种分析方法。
一种是看看同时间段下,除了我们的故障现象外,还有那些异常现象同时发生了。如果我们的监控数据足够全面,这种分析方法可以很快地定位到 “怀疑对象”。
另一种方式是分析故障请求的调用链。这方面的技术已经非常成熟。很多公司的业务实现都会把请求从前端入口到后端的整个调用过程通过一个 request id 串起来。
通过随机抽样一些故障请求的日志,然后在日志系统中搜索 request id 找到整个调用链,分析调用链找到问题的根源。
七牛云发布的日志系统 Pandora提供了完整的监控、报警和故障的根因分析模块。除了传统的 SRE 方法论外,我们也探索基于 AI 的智能化监控与根因分析能力。
## 结语
监控与报警是一项非常复杂的事务,这种难度不是因为业务流程复杂导致的,而是因为与业务的高耦合导致。
监控系统需要跟随不断演变的软件一起变化。软件的全局或某个局部发生重构,负载特性和性能目标就会变化。
某个不常见的、自动化比较困难的报警,很可能随着用户增长很快变成一个经常触发、需要一个临时的脚本来应对的问题。这时,就需要有人去寻找和消除背后的根源问题,而不是在持续被故障牵着鼻子走。
解决故障的方案有可能是需要进行业务的架构调整。这也是监控与报警的复杂性所在:你需要和业务开发工程师一起配合去完善系统。
添加监控看起来像是一个很繁琐的事务工作,但实际上它非常依赖你的架构能力。
一个很牛的监控 SRE他要干的绝对不是不停地添加新的监控项以 “完善某个业务的监控”。
少就是指数级的多!
就和软件开发工程师经常需要重构,去删减掉自己历史的无效代码一样,负责业务监控的 SRE 也需要经常重构自己的监控指标,以期用最少的监控项,来全面覆盖和掌控系统的健康状况。
如果你对今天的内容有什么思考与解读,欢迎给我留言,我们一起讨论。下一讲我们聊聊 “故障域与故障预案”。
如果你觉得有所收获,也欢迎把文章分享给你的朋友。感谢你的收听,我们下期再见。

View File

@@ -0,0 +1,160 @@
<audio id="audio" title="51 | 故障域与故障预案" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/ef/3b/ef780eff482d722cf84c9eeb72f9553b.mp3"></audio>
你好,我是七牛云许式伟。
我们前面介绍了服务的发布和监控,来保障线上业务的持续服务。但是,不管我们怎么小心谨慎,故障仍然会不可避免地发生。并不是所有的故障都会影响到用户,一些局部的故障如果处理得当,对用户并不产生可见的影响。
今天我们就聊聊故障产生的原因与对策。可以导致故障的因素非常多。在 “[47 | 服务治理的宏观视角](https://time.geekbang.org/column/article/144803)” 一讲中,我们大体将故障类型分为以下几种。
- 软硬件升级与各类配置变更,即发布。
- 软硬件环境的故障。
- 终端用户的请求。比较典型的场景是秒杀类,短时间内大量的用户涌入,导致系统的承载能力超过规划,产生服务的过载。当然还有一些场景,比如有针对性的恶意攻击、特定类型的用户请求导致的服务端资源大量消耗等,都可能引发服务故障。
我们先来看看 “软硬件升级与各类配置变更”,也就是发布。发布过程引发的故障实际上有别于另外两种故障类型,它源于我们主动对系统作出变更,属于过程型的故障。
变更是故障的第一大问题源头。所以我们在发布的过程中多谨慎都不为过。不过我们今天的主题并不是以此类故障为主,我们已经在 “[加餐 | 怎么保障发布的效率与质量?](https://time.geekbang.org/column/article/154246)” 一讲中专门探讨了发布的过程管理应该怎么做。
大部分情况下,变更导致的故障在短期内就会得以暴露,这也是我们采用灰度发布这样的手段能够达到规避故障风险的原因。但当我们讨论故障域的时候,我们还是应该意识到,灰度并不能发现所有变更风险。有时代码变更引发的故障需要达到特定的条件下才得以触发,例如数据库规模达到某个临界点可能导致数据库操作异常。
这类有很长潜伏期的故障风险,是非常令人头疼的。一方面它爆发时点与风险产生的时点间隔太远,不容易定位。另一方面,有可能对有状态的服务而言,它发作起来就已经是不可控制的灾难。怎么才能避免这类问题?严谨的白盒代码审查和全面的测试覆盖率提升,才有可能消除此类风险,至少理论上是这样。
## 软硬件环境的故障
今天我们重点讨论的是 “软硬件环境的故障” 引发的服务异常。我们追求的是 24 小时不间断服务,所以站在更长的时间维度,或者更大的集群规模维度看,可以预期软硬件环境的故障是一种必然。
仍然拿我之前举的例子来说,假设一块硬盘的平均寿命是 3 年,也就是说大约 1000 天会出现一次坏盘。但是如果我们集群有 1000 块硬盘,那么平均每天就会坏 1 块盘。故障从偶然就变成了必然。
怎么为 “软硬件环境的故障” 做好故障预案?常见做法无非两种:要么用 SRE 的手段,通过监控系统发现特定的软硬件故障并进行报警,收到报警后并不是通知到人,而是触发去自动执行故障恢复的脚本。另一种做法是干脆把故障恢复的逻辑实现在业务服务的代码逻辑中,避免因软硬件故障而出现单点问题。
这两种方法本质上是相同的,只不过是选择的技术手段有所差别。无论通过哪种方式,要想找到所有可能的故障点,我们就需要对服务所有请求的路径进行分析。要正确画出请求链路,需要了解以下两个方面。其一,对我们所依赖的基础架构要了解,否则我们不知道请求是如何到达我们的业务服务器的。其二,要对我们服务的业务架构了解,知道请求的逻辑是怎么样的。当然对业务架构不甚了解问题不大,代码就在那里,我们可以看着一行行代码,把请求的链路忠实画出来。
如何通过源代码来看请求链路?
首先IO 之外的普通业务代码是不太会出问题的。要出问题也是业务逻辑错误的问题,这类故障我们并不归类到 “软硬件环境的故障” ,而是 “软硬件升级与各类配置变更”。这个话题我们前面已经聊过,不再展开。
IO 代码包括哪些?常见的是磁盘 IO 和 网络 IO。当然完整来说所有外部设备的操作都算只不过我们极少会碰到。
考虑到代码的分支结构,我们的请求链路可以画出一棵树,树的每个节点是一次 IO 操作。一个示意性的请求链路图如下:
<img src="https://static001.geekbang.org/resource/image/54/20/54ab3467ca93e115963da6be2c717220.png" alt="图片: https://uploader.shimo.im/f/Teq8OPTMQy4MkGzi.png">
表面上看起来,这里每一个 IO 请求,我们都应该考虑万一失败了我应该怎么处理,以确保用户的 API 请求都被正确地处理。但实际写业务代码的时候,我们大部分情况下是比较心宽的,并不会处处小心翼翼去做异常的恢复。这里面的原因是因为前端有负载均衡。
下面这个图你很熟悉了,它是服务端程序的宏观体系架构图。
<img src="https://static001.geekbang.org/resource/image/66/7f/66811afd16269acf140363357cdfd47f.png" alt="">
有了负载均衡,实现业务架构的心智负担小了很多。遇到任何异常,我们返回 5XX 错误的请求结果,这时负载均衡发现请求出错了,就会把该请求发给其他的业务服务器来重试。这就解决了业务架构的单点问题。
不同服务的 API 请求链路会有所差异。但是大体来说,一个经典的 API 请求,如果我们把所有基础架构也考虑在内的话,它的故障点主要有:
- 网络链路,包括用户端网络和服务端网络;
- DNS
- 机房;
- 机架;
- 交换机;
- 负载均衡;
- 物理服务器;
- 业务服务本身;
- 缓存/数据库/存储。
我们一一进行分析。
先看网络链路,它包括用户端网络和服务端网络。
如果是用户个人的网络出问题,就没法正常为这个用户服务了,但是从服务提供方来说,只要这个用户不是超级 VIP 用户,他的损失并不大,一般不会为他做太多的事情。
但如果不是个例用户问题,而是某个地区的运营商网络出问题了,那么对业务可能就造成了较大程度的影响。只是对于大部分业务服务提供方来说,他自身并没有太大的实力去解决它。但我们如果开放性考虑这个问题的话,这个问题也并不完全是无解,今天暂且不提。
只要用户端网络没问题,它连接到服务端网络就非常可控了。整个网络链路非常复杂,它是一个有向图。为了确保网络请求的通畅,服务端网络链路会准备多个,这时我们通常用多个服务的域名,每个域名代表一个网络链路,由客户端进行链路的选择与重试。
当然如果我们的服务是单机房的,那么它和用户端网络一样,也会有网络链路的单点故障。如果投入产出比可以的话,我们往往会做多机房的容灾。这一点我们分析机房级别的故障再展开。
我们再看 DNS它负责的是将域名解析为 IP。DNS 服务分 DNS 权威服务器和 DNS 递归或转发服务器。DNS 权威服务器是域名解析服务的真正提供方,而 DNS 递归/转发服务器是域名解析的缓存。
DNS 权威服务怎么防止单点?提供多个就好。域名服务商通常允许我们为该域名配置多个 DNS 权威服务,比如 ns1.example.com 和 ns2.exmaple.com。这两个或多个 DNS 权威服务器最好不要在同一个机房,这样才可以达到容灾的目的。
DNS 递归/转发服务分用户侧与服务侧。用户侧来说,只要用户请求我们的服务,第一步就需要把我们服务的域名解析为 IP就需要请求 DNS 递归/转发服务。除了用系统默认的之外,也有一些公共的 DNS 递归服务,比如 Google 提供了 8.8.8.8 和 4.4.4.4。
一般操作系统都允许用户自行设置 DNS 服务器,它可以是多个 DNS 递归或转发服务器。比如在 MAC 下的配置界面如下图。
<img src="https://static001.geekbang.org/resource/image/d5/98/d55ba6e6a8aad0d82a8a1271676a1c98.png" alt="">
服务端侧也其实是一样的,只不过它对 DNS 服务器的需要来源于业务服务向外部发起请求的场景,比如我的业务服务是个爬虫,它需要去抓网页,或者其他原因需要请求某种公网的服务。
但服务端侧的解决方案和用户侧差别比较大,我们很少会配置 8.8.8.8 和 4.4.4.4 这类公共的 DNS 递归服务器,而是自己在机房中搭建一组高可用的 DNS 服务器。同样地,我们可以通过给操作系统配置多个 DNS 服务器来达到避免单点故障的目的。
聊完 DNS我们来看下机房。机房级别故障可以有多方面的原因比如机房断电、断网。为了防止机房故障造成服务中断我们就需要做多机房容灾。
如果我们的服务偏静态,也就是写很少,大部分情况下都是读操作,那么可以用 2AZ 架构(双机房容灾)。但是在大部分通用的业务场景下,我们建议 3AZ 架构(三个机房容灾)。
在整体业务的体量足够大的情况下3AZ 不一定会比 2AZ 成本高。因为 2AZ 我们每个 AZ可用区的容量规划必须得能够支撑全部的业务体量否则不足以支撑某个 AZ 挂掉。而 3AZ 我们可以让每个 AZ 的容量只是全部业务体量的 1/2。这样我们总成本只花了 1.5 倍,而不是 2 倍。
任何容灾其实最麻烦的都是数据库和存储也就是业务状态是如何容灾的。从数据库的主从的选举角度来说3AZ 也比 2AZ 好。因为 2AZ 的话,一个 AZ 下线就意味着数据库有一半节点下线这时候就没法选举出新的主Master节点。
机房故障会导致一批 IP 下线。这时我们就需要在 DNS 解析中把这批 IP 下线。但是 DNS 解析的调整生效周期比较长,具体时间与 DNS 条目设置的 TTL 相关,比如 1 小时。但有的 DNS 递归/转发服务器会忽略 TTL这就导致 DNS 解析生效时间变得更加不确定。
要解决这个问题,通常的做法是在客户端引入 HTTP DNS 支持。顾名思义HTTP DNS 基于 HTTP 协议来提供 DNS 解析服务,绕过了传统的 DNS。结合客户端自身适当做下的 DNS 缓存,就可以解决掉 DNS 解析生效不及时的问题。
接下来聊聊机架故障。它和机房故障类似,整排机架的机器都同时断电或断网。为了避免机架故障导致的负面影响,我们一般在服务编排时会考虑两点:
- 同类服务尽可能分散到不同的机架上,避免因为机架故障导致某个服务整体下线。
- 同一份数据的多个副本,或者同一 EC 条带的不同数据块,尽可能分散在不同的机架上,避免因为机架故障导致该数据不可访问。
聊完了机架,我们接着聊交换机。为了避免交换机故障导致大范围的机器下线,我们用两个交换机进行相互热备,这通过交换机之间的 “热备份路由协议”Hot Stand by Router Protocol简称 HSRP进行。
下一个故障点是负载均衡。负载均衡的任何一个实例发生故障,意味着我们业务的入口发生故障。比如,假设我们负载均衡集群有 N 个实例,其中某个实例发生了故障,就意味着有 1/N 比例的用户受到了影响。这通常是不可接受的。
你可能会想到说,我把故障的负载均衡实例的 IP 从 DNS 解析中去除,就可以消除掉这个故障。这理论上可行,但是我们实际很少会这么做,原因和前面说的 DNS 解析的生效时间过长有关。我们不能够接受长达 1 小时的入口级故障。
比较好的做法是,所有负载均衡实例的 IP 是 VIP即基于虚 IP 技术。关于 VIP 技术的介绍,你可以回顾一下 “[35 | 流量调度与负载均衡](https://time.geekbang.org/column/article/125952)”。一旦检测到 VIP 对应的主实例故障,我们就通过 VIP 技术把它的流量打到其他负载均衡实例上,这样就避免了因为负载均衡实例故障导致的损失。
下一个故障点是业务服务本身。这块相对容易。只要我们坚持业务服务器 “无状态”,那么任何一个业务服务器故障,都不会对用户产生实际的影响。这也是强大的基础架构带来的好处,它让我们做业务更轻松了。
这个架构课开课以来看到一些人反馈说不太理解为什么从计算机底层原理开始谈起好像和常规的架构课很不一样。事实上关于这一点我在开篇第0讲就解释过理由今天我在这里重申一下
- 首先,基础架构是业务架构的一部分。不了解基础架构,你就不知道自己写的软件背后都发生了什么,你就无法掌控全局,这对你思考架构演进会有很大的局限性,因为你是 “戴着脚镣跳舞”。
- 其次,基础架构是最宏大的架构实践案例,需要我们好好感悟。我们不只是要知道基础架构怎么用,还应该理解它为何演变成今天这样。对于优雅的基础架构设计,我们应该要有强烈的共鸣,惊喜赞叹。如果你没有感觉,说明你对架构思维也还没有感觉,也就更不可能构建出极致的架构。
- 最后,在后面总结架构思维的时候,我们会以大家耳熟能详的基础架构作为例子,这一定程度会更加深入人心。当然,具体业务实战方面的案例也必不可少,我们会结合两者一起谈。
最后我们聊聊缓存、数据库和存储。这些服务有一个共同特征,它们都是有状态的服务。
缓存集群通常是单副本的,通过特定的分片算法,比如一致性哈希,来定位具体的缓存实例。
部分缓存实例挂掉,一般来说带来的冲击并不大,基本也就是缓存命中率瞬间有个下降,然后逐步回升。但是如果缓存实例挂掉过多,甚至极端情况下全部挂掉的情况下,就会导致后端数据库的压力很大,出现延时变高,甚至出现雪崩现象。
对于数据库压力太大导致雪崩,数据库再起来就又立刻被打爆,怎么都起不来的情况,最好的做法是在数据库层面就做好过载保护。在数据库不支持自我保护的情况下,一个替代的做法是通过 SRE 的手段来实现:一旦监控系统发现数据库过载了,就选择由负载均衡来扔掉部分用户请求。
如果雪崩已经发生,常见的做法是让负载均衡先扔掉足够多的用户请求,让数据库能够正常服务用户。然后观察数据库的负载情况,逐步减少负载均衡扔掉的用户请求量,直至最后完全正常提供服务。
数据库和存储要保证高可靠高持久性和高可用必然是多实例的。无论是什么架构对于特定的数据这些实例有主Master有从Slave一旦主节点挂掉就会触发选举确定新的主。当然有一些老的数据库是基于一主一备备节点 Stand-by 直到主节点挂掉时接替它继续工作。但是这种模式已经太过时了,并不推荐使用。
## 故障恢复
清楚了所有的故障点,我们就可以针对性去做故障预案。对于大部分的故障来说,我们会优先倾向于通过切流量来消除故障。
流量切换,需要遵循最小切量原则。能够通过更细粒度的切量动作来消除故障,就应该用细粒度的。
通过以上分析,我们可以看出流量切换的控制点有这样几个:
- 负载均衡;
- 负载均衡实例的 VIP 入口;
- DNS 解析。
但是故障根因如果是有状态服务,比如数据库与存储,那么我们就很难通过切量来消除故障。这时我们应该用过载保护机制来对服务进行降级,也就是在特定环节把一定比例的用户请求扔掉。
扩容也是解决数据库与存储集群压力大的常规思路,这块我们后面再详细展开。
## 结语
今天我们就聊聊故障产生的原因与对策。可以导致故障的因素非常多,我们大体分为以下几种。
- 软硬件升级与各类配置变更,即发布。
- 软硬件环境的故障。
- 终端用户的请求。
今天我们重点讨论的是 “软硬件环境的故障” 引发的服务异常及其故障预案。我们追求的是 24 小时不间断服务,所以站在更长的时间维度,或者更大的集群规模维度看,可以预期软硬件环境的故障是一种必然。
如果你对今天的内容有什么思考与解读,欢迎给我留言,我们一起讨论。下一讲我们聊聊 “故障排查与根因分析”。
如果你觉得有所收获,也欢迎把文章分享给你的朋友。感谢你的收听,我们下期再见。

View File

@@ -0,0 +1,144 @@
<audio id="audio" title="52 | 故障排查与根因分析" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/51/54/5159a8c26485b8f651f297cf7756d454.mp3"></audio>
你好,我是七牛云许式伟。
## 写在故障排查之前
一旦 SRE 发现了我们线上业务出现故障,要如何解决呢?
正确解决问题的逻辑不是当场把问题修复好,而是用最快的方式让问题得以缓解或消失。考核 SRE 的核心指标中,通常会有季度故障率或年故障率指标。一次磨蹭的线上故障恢复,可能直接导致团队的年终奖取消。
及时故障恢复的手段应该怎么样,首先要看故障原因的初判。如果是在软硬件环境升级中发现的故障,通常的做法是版本回滚。如果是因为用户请求导致负载过高,则考虑扩容。如果扩容不能让问题消失,则考虑服务降级,关闭一些不重要的功能以降低负载,或者主动抛弃一定比例的请求。如果是软硬件环境本身故障导致的业务故障,则通常是进行流量切换,把用户流量导向其他没有问题的任务实例。
解决了故障,我们就要开始定位引发故障的根本原因。
需要理解的一点是,定位故障原因的细节必然是和具体业务服务息息相关。但是有效地应对紧急问题,及时恢复故障的方法论,对每个业务团队来说却往往都是完全通用的。
另外,并不一定要线上出现了故障才需要去排查原因。
我们做事不能这么 “故障驱动”,要居安思危,主动出击。一个 SRE 例行的工作应该包含服务入口的指标检查,一旦发现存在非预期的指标波动,虽然有可能还没有触发警告线水位,就应该去排查问题。这就如同医生需要对病人定期进行诊断一样,这样的工作不一定要病人真生病了才去进行。
例行的排查过程需要有结论,发现问题风险要有应对方案。要避免团队陷在例行指标检查中,把它看做一项纯事务的工作来做。
SRE 的确看起来会有很多事务性的工作。但其实对工程师来说,这是一份极好的工作。
为什么我这么说?
我想从我自己的工作经历谈起。我的第一份工作是在金山,做的是 WPS 办公软件。我还没有毕业就去实习了,第一项工作任务做的是存盘和读盘,我们简称 IO。
在大部分工程师看来IO 是一个非常事务性的编码工作否则也轮不到我一个新手实习生来做。IO 工作干的最多的事情就是把一个个变量写到磁盘中,再把它从磁盘读出来恢复变量内容。听起来很无聊,对吧?
但实际上它非常核心。
首先,用户文件对办公软件来说,是最重要的资产。它的位置就如同用户关系在微信和 QQ 中的地位类似。当时 WPS 存盘读盘用的是 MFC 序列化框架。用这套框架最大的问题是版本不兼容。新版本保存的文件,老版本读不出来。从需求分析角度来说,这是极不应该发生的,这根本就是在抛弃用户。
其次,数据是软件的灵魂。要了解一个软件的设计思想,最快的方法不是读代码,是研究它的数据结构。所以我当时虽然没有在做具体的某个功能,但是每每分析到微软的文件数据格式中出人意表的精巧设计,就会和团队中做对应功能的成员沟通,把微软设计思想告诉他们,而这往往能让他们受益匪浅,甚至改变自己的架构设计。
最后业务功能是点IO 是面。通过做存盘和读盘工作,我就成为了第一个对办公软件整体的架构设计思路有了全局理解的人。这很可能也是我仅仅毕业两年,就得以能够主导办公软件这样庞大的业务系统的整体架构的原因。
可能会有人觉得我太幸运了,第一个工作任务就是 IO 这样的核心工作。
但是我想说的是,遇到这种幸运的人,我显然不是第一个。对 WPS 团队来说,我只是第一个抓住这个幸运机会的人。
其实对于服务端工程师来说SRE 工作也是一样的幸运机会。
对于一个服务端软件,最大的挑战并不是把业务逻辑写出来,而是写出来后如何保障 7x24 小时持续不间断的运行。
如果你不碰线上环境你就也无法成为最理解系统的专家。SRE 工作是最有机会成为专家的,它和架构师一样,需要整体理解业务架构,并且通过线上环境的理解,去反向影响业务架构,以提高系统的稳定性。
从另一个角度来说SRE 有越多的事务工作,就意味着这里面有越多的价值洼地还没有被挖掘。这也是为什么近年来服务治理的迭代如此日新月异的原因。
今天的 SRE 是幸运的,我们正处在历史变革的浪潮之中。
## 故障排查的方法论
回到正题。
今天我们想聊一聊的,是我们如何建立一种系统性或者说结构化的方案,来进行故障排查,找到问题的根因。
故障排查是 SRE 的一项关键技能。这项技能有时候会被认为是与生俱来的,有些人有,有些人没有。造成这种误解的原因通常是:对一个经常需要进行排除故障的人来说,让他去解释 “如何” 去进行故障排查是一件很难的事情。
但是我想说的是,故障排查是一个可以自我学习,也是一个可以传授的技能。
新手们常常不能有效地进行故障排查,是因为要做到高效排查的门槛比较高。理想情况下我们需要同时具备两个条件:
- 对通用的故障排查过程的理解(不依靠任何特定系统)。
- 对发生故障的系统的足够了解。虽然只依靠通用性的流程和手段也可以处理一些系统中的问题,但这样做通常是很低效的。对系统内部运行的了解往往限制了 SRE 处理系统问题的有效性,对系统设计方式和构建原理的知识是不可或缺的。
让我们一起来看一下基本的故障排查流程。从理论上讲,我们将故障排查过程定义为反复采用假设-验证排除手段的过程:针对某系统的一些观察结果和对该系统运行机制的理论认知,我们不断提出一个造成系统问题的假设,进而针对这些假设进行测试和排除。
我们从收到系统问题报告开始处理问题。通过观察监控系统的监测指标和日志信息了解系统目前的状态。再结合我们对系统构建原理、运行机制,以及失败模型的了解,提出一些可能的失败原因。
接下来,我们可以用以下两种方式测试假设是否成立。第一种方式,可以将我们的假设与观察到的系统状态进行对比,从中找出支持假设或者不支持假设的证据。另一种方式是,我们可以主动尝试 “治疗” 该系统,也就是对系统进行可控的调整,然后再观察操作的结果。
第二种方式务必要谨慎,以避免带来更大的故障。但它的确可以让我们更好地理解系统目前的状态,排查造成系统问题的可能原因。
无论用上述两种方式中的哪一种,都可以重复测试我们的假设,直到找到根本原因。
真正意义上的 “线上调试” 是很少发生的,毕竟我们遇到故障的时候,首先不是排查故障而是去恢复它,这有可能会破坏掉部分的现场。所以,服务端软件的 “线上调试” 往往在事后发生,我们主要依赖的就是日志。这里的日志是广义的,它包括监控系统背后的各类观测指标的时序数据,以及应用程序的程序日志。
为了排查故障,我们平常就需要准备。如果缺乏足够的日志信息,我们很有可能就无法定位到问题的原因。
首先,我们必须能够检查系统中每个组件的工作状态,以便了解整个系统是不是在正常工作。
在理想情况下,监控系统完整记录了整个系统的监控指标。这些监控指标是我们找到问题所在的开始。查看基于时间序列的监控项的报表,是理解某个系统组件工作情况的好办法,可以通过几个图表的相关性来初步进行问题根源的判定。
但是要记住相关性Correlation不等于因果关系Causation。一些同时出现的现象例如集群中的网络丢包现象和硬盘不可访问的现象可能是由同一个原因造成的比如说断电。但是网络丢包现象并不是造成硬盘损坏现象的原因反之亦然。况且随着系统部署规模的不断增加复杂性也在不断增加监控指标越来越多。不可避免的一些现象会恰巧和另外一些现象几乎同时发生。所以相关性Correlation只能找到问题的怀疑对象但是它是否是问题根源需要进一步的分析。
问题分解Divide &amp; Conquer也是一个非常有用的通用解决方案。在一个多层系统中整套系统需要多层组件共同协作完成。最好的办法通常是从系统的一端开始逐个检查每一个组件直到系统最底层。
但真正协助我们找根因的关键无疑是日志。在日志中记录每个操作的信息和对应的系统状态,可以让你了解在某一时刻整个组件究竟在做什么。一些跟踪工具,例如 Google Dapper 提供了非常有用的了解分布式系统工作情况的一种方式。类似 Hadoop HDFS 之于 Google GFS当前开源领域中也有 Open Tracing 项目对应于 Google Dapper其项目主页如下
- [https://opentracing.io/](https://opentracing.io/)
文本日志对实时调试非常有用。将日志记录为结构化的二进制文件通常可以保存更多信息,有助于利用一些工具进行事后分析。
在日志中支持多级是很重要的,尤其是可以在线动态调整日志级别。平常如果我们记录太多日志会导致成本过高,但是如果需要及时通过调试来定位问题,我们不需要通过重新发布新版本的软件来追加跟踪日志。这项功能可以让你在不重启进程的情况下详细检查某些或者全部操作,同时这项功能还允许当系统正常运行时,将系统日志级别还原。
根据服务的流量大小,有时可能采用采样记录的方式会更好,例如每 1000 次操作记录一次。
不同服务之间怎么把请求给串起来的?
答案是用 Request ID。在整个 Tracing 的业务链中,我们给每个用户的 API 请求分配一个 Request ID该 API 请求的响应过程中所有相关的日志都会记录 Request ID以便我们可以便捷地通过它检索到与该 API 请求相关的所有日志。
有时,我们会通过 HTTP 回复包Respnose来快捷地得到 API 请求链的概要版本,比如通过 X-RequestTrace 这样的 HTTP 头来得到。这是一种更轻盈的 Tracing 实现,实现非常容易。当然缺点也比较明显,它只能真正去在线调试,而无法像日志那样去查看历史。
仅仅通过 Request ID 来查看请求链,对于故障排查是不够的。定位问题本身就是 “假设-验证排除-再假设-再验证排除” 这样的循环,直至最后定位到问题。所以基于时序数据的日志系统,往往查询支持非常多样化的过滤条件,功能非常强大。如果你对这方面的详细内容感兴趣,欢迎体验七牛云的 Pandora 日志管理系统。
第三种重要的排查问题手段,是提供服务状态查询 API 和监控页面来暴露当前业务系统的状态。通过监控页面可以显示最近的 RPC 请求采样信息。这样我们可以直接了解该软件服务器正在运行的状态,包括与哪些机器在通信等等,而不必去查阅具体的架构文档。另外,这些监控页面可能同时也显示了每种类型的 RPC 错误率和延迟的直方图,这样可以快速查看哪些 RPC 存在问题。
这种方法很像 X-RequestTrace 机制,它非常轻便。如果故障的现场还在,或者故障过去的时间还不长,那么它将非常有助于问题的排查。但是如果问题的现场已经被破坏,那我们就只能另寻他途。
## 现实的实操建议
在现实中,要想让业务系统的故障排查更简单,我们可能最基本要做的是:
- 增加系统的可观察性。不要等狼来了羊丢了才想着要补牢。在实现之初就给每个组件增加白盒监控指标和结构化日志。
- 使用成熟的、观察性好的 RPC 框架。保证用户 API 请求信息用一个一致的方法在整个系统内传递。例如,使用 Request ID 这样的唯一标识标记所有组件产生的所有相关 RPC。这有效地降低了需要对应上游某条日志记录与下游组件某条日志记录的要求加速了故障排查的效率。
代码中对现存错误的假设,以及环境改变也经常会导致需要故障排查。简化、控制,以及记录这些改变可以降低实际故障排查的需要,也能让故障排查更简单。
对于故障排查我们需要建立系统化的对故障排查的手段而不是依靠运气和经验。这将有助于限定你的服务的故障恢复时间MTTR。同时就算是团队里的新手也可以更加快捷有效地解决问题。
造成故障排查过程低效的原因,通常集中在 “假设-验证排除” 环节上。这主要还是由于对业务系统不够了解而导致的。它通常表现在:
- 在错误的方向上浪费时间。关注了错误的系统现象,或者错误地理解了系统现象的含义。
- 试图解决与当前系统问题相关的一些问题,却没有认识到这些其实只是巧合,或者这些问题其实是由于当前系统的问题造成的。比如,数据库压力大的情况下可能导致机房局部环境温度也有所上升,于是试图解决环境温度问题。
- 将问题过早地归结为极不可能的因素,或者念念不忘之前曾经发生过的系统问题,认为还是由之前的问题造成。
- 不正确地修改了系统的配置信息、输入信息或者系统运行环境,造成不能安全和有效地测试假设,导致验证的逻辑本身存在问题。
理解我们逻辑推理过程中的错误是避免这些问题发生的第一步,这样才能更有效地解决问题。区分我们知道什么,我们不知道什么,我们还需要知道什么可以让查找问题原因和修复问题更容易。
另外,尽管系统能够帮上很多忙,但是故障排查有赖于很多背景知识,需要你更详细地学习业务系统的运行原理,了解分布式系统运行的基本模式。
## 结语
今天我们聊的是线上故障的排查与根因分析。
从理论上讲,我们将故障排查过程定义为反复采用“假设-验证排除”手段的过程:针对某系统的一些观察结果和对该系统运行机制的理论认知,我们不断提出一个造成系统问题的假设,进而针对这些假设进行测试和排除。
为了有效排查故障,日志系统在里面起到了关键作用。定位问题本身就是 “假设-验证排除-再假设-再验证排除” 这样的循环,直至最后定位到问题。所以基于时序数据的日志系统,往往查询支持非常多样化的过滤条件,功能非常强大。如果你对这方面的详细内容感兴趣,欢迎体验七牛云的 Pandora 日志管理系统。
如果你对今天的内容有什么思考与解读,欢迎给我留言,我们一起讨论。下一讲我们聊聊 “过载保护与容量规划”。
如果你觉得有所收获,也欢迎把文章分享给你的朋友。感谢你的收听,我们下期再见。

View File

@@ -0,0 +1,144 @@
<audio id="audio" title="53 | 过载保护与容量规划" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/03/69/032193ccbb52ff5a2d2f65c0f0ed1c69.mp3"></audio>
你好,我是七牛云许式伟。
前面在 “[47 | 服务治理的宏观视角](https://time.geekbang.org/column/article/144803)” 一讲中,我们大体将故障类型分为以下几种。
- 软硬件升级与各类配置变更,即发布。
- 软硬件环境的故障。
- 终端用户的请求。比较典型的场景是秒杀类,短时间内大量的用户涌入,导致系统的承载能力超过规划,产生服务的过载。当然还有一些场景,比如有针对性的恶意攻击、特定类型的用户请求导致的服务端资源大量消耗等,都可能引发服务故障。
软硬件升级与各类配置变更,主要通过发布与升级系统完成。软硬件环境的故障,可以参考 “[51 | 故障域与故障预案](https://time.geekbang.org/column/article/155500)”。
今天我们聊的是故障的第三大类:由终端用户请求导致的故障。它最典型的现象是 “过载”。
## 过载的成因与后果
我们要思考的第一个问题是:过载的原因是什么?
所谓过载,最直白的理解,当然就是因为活跃的用户超过了资源的承载能力范围,导致某类资源耗尽,进而体现出系统过载。
本质上,这是一个容量规划的问题。
资源怎么会不够了?往往有以下这么几个成因:
其一,用户增长太快了,资源规划上的预期没有跟上,导致资源储备不足。
其二部分资源因为故障而下线导致线上活跃的资源不足。举个例子假设我们做了双机房容灾但是往往这仅仅是架构上的容灾。从资源储备角度来说我们需要按2倍的容量来做资源规划才有可能在某个机房下线后系统不出问题。
其三,系统的关键资源负载能力变低,比如数据库。随着线上服务时间的推移,数据库越来越大,到达了某个临界点,可能就会导致数据库整体的延时变长,响应变慢,同时能够支撑的并发变低,从而导致过载。
其四,某类故障导致系统的反应过激,这通常是因为重试导致的。
为了提高可用性,通常我们在向服务器请求某个 API 失败后,都会进行重试。这种重试行为,可能由负载均衡发起,也可能是发生在客户端。
我们假设,一个 API 失败会重试 2 次,那么对于所有失败的请求来说,请求数放大了 3 倍。如果客户端和服务端都进行了重试,就放大了 9 倍。如果我们再假设API 失败的原因并不是一上来就失败,而是执行到某个中间步骤,调用了另一个内部服务的 API 而失败,那么很可能内部 API 调用也重试了 39 次。这样对这个内部 API 来说,它失败重试的次数就是 981 倍。
这种因为重试而带来的请求次数放大,可能会导致系统的资源储备不足,进而引发了过载。
理解了过载的成因,我们第二个问题可能是:过载了,会带来什么样后果?
过载从表现上看,通常会体现为 “资源耗尽”。
比如CPU 负荷过高,持续接近 100% 下不来。如果 CPU 资源不足以应对请求负载一般来说所有的请求都会变慢。这个场景会造成一系列的副作用比如处理中的请求数量上升。因为处理请求需要较长的时间同一时间服务器必须同时处理更多的请求上升到一定数量可能会开始进入队列排队。这会影响其他所有的资源包括内存、socket连接以及其他后端服务器资源的消耗也相应增加。
所以需要强调的是,过载通常是会有连锁反应的。某类资源的耗尽,会导致其他资源出现问题。某个服务的过载,经常会出现一系列的资源过载现象,看起来都很像是根本问题,这会使得定位问题更加困难。
过载现象可能会是一个短时现象过一段时间就撑过去了。但也有很多时候会由于正反馈循环positive feedback导致恶化短时间内就快速形成雪崩效应击垮系统。
雪崩效应是如何形成的?
我们假设某个服务实例能够承受的正常 QPS 为 10000如果某个时刻正常业务所产生的自然请求数是 11000那么其中就有 1000 个请求会失败,如果失败重试导致的请求放大至 9 倍的话,那么系统的 QPS 就增加到 20000是正常负荷的 2 倍。
这样的高负荷,会直接压垮这个服务实例。在这种情况下,这个服务实例的所有请求会被转移到其他互备实例,从而导致这些互备实例承受了更为巨大的压力。故而,互备实例同样一个个很快被压垮。最终,该服务完全挂掉。
这就是雪崩效应。
## 过载的监控
过载的危害如此之大,我们怎么及早发现?
一种非常常见,也是很多公司都在做的方式,是给服务的 QPS 设置一个阈值,当 QPS &gt; 阈值时,就触发服务已经过载或即将过载的告警。
这个方式看起来不错,但是它的维护成本很高。就算这个指标在某一个时间段内看起来工作还算良好,但它早晚也会发生变化。有些变动是逐渐发生的,有些则是非常突然的。例如某个服务或客户端的新版本发布,突然就使得某些请求消耗的资源大幅减少。
更好的解决方案是直接基于该服务所依赖的关键资源如CPU和内存等来衡量服务的可用容量。我们为该服务预留了多少资源这些资源已经用了多少预计还能够用多久。
我们基于基础资源的用量来估算,比基于服务的 QPS 要稳定可靠得多。在绝大部分情况下(当然总会有例外情况),我们发现简单地基于 CPU 使用量作为服务容量的指标,效果已经非常好了。
## 过载的应对策略
最后一个问题,我们怎么才能够提前防范服务的过载,把过载可能造成的损失降到最低?
从大的思路来说,无非两个方向,一个是把过载发生的概率变低。另一个是即使发生了过载,也要杜绝雪崩效应,把因为过载产生的损失降到最低。
从技术手段来说,可以由服务的实现方来做,也可以由客户端,也就是服务的调用方来做。
我们先看服务端能够做什么。
首先,应该在过载情况下主动拒绝请求。服务器应该保护自己不进入过载崩溃状态。当前端或者后端进入过载模式时,应尽早尽快地将该请求标记为失败。
当然过载保护可以做得很粗,只有一个全局的负载保护。也可以很细,给每个用户设置独立的负载配额,部分特殊客户甚至可以单独调整负载配额。在理想情况下,当全局过载情况真的发生时,使服务只针对某些“异常”客户返回错误是非常关键的,这样其他用户就不会受影响。
过载保护可以基于 QPS也可以基于资源利用率实现。但如前文已经说过的那样基于资源的负载情况判断会比基于 QPS 更加稳定。
过载保护也可以由负载均衡来做。避免过载是负载均衡策略的一个重要目标。这是个双保险,万一业务服务器没有考虑这块的时候,还有人能够阻止因为过载而崩溃情况的发生。
其次,应该进行容量规划。好的容量规划可以降低连锁反应发生的可能性。容量规划应该伴随着性能测试进行,以确定可能导致服务失败的负载程度。
进行容量规划只能减少触发连锁反应的可能性,但是并不能完全避免。当一个计划内或者计划外的事件导致大部分集群容量同时下线时,连锁反应是不可避免的。负载均衡问题、网络分区事件,或者突发性流量增长,都会导致意料之外的负载问题。有些系统可以根据需要动态增加容量,这可能防止过载发生,但是适当地进行容量规划还是必要的。
最后,服务优雅降级。如果说前面主动拒绝请求,是一种无脑、粗暴的降级方式的话,根据请求的类型和重要性级别来降级,则是一种更为优雅的降级方式。
值得强调的是,优雅降级不应该经常被触发。否则就显示了我们在容量规划上的失误。
另外,代码中平时不太触发的代码分支有可能是不能正常工作的。在稳定运行状态下,优雅降级不会经常触发,这意味着在这个模式下的 SRE 的经验很少,对相关的问题也不够熟悉,这就会升高它的危险性。我们可以通过定期针对一小部分的服务进行压力测试,以便更多地触发这个模式,保证这个代码分支还能正常工作。
聊完了服务端应对过载的手段,我们再来看看客户端能够做什么。
第一个话题是重试。我们可以有这样一些方式来降低重试导致的过载概率。
- 限制每个请求的重试次数,比如 2 次。不要将请求无限重试。
- 一定要使用随机化的、指数型递增的重试周期。如果重试不是随机分布在重试窗口里的,那么系统出现的一个小故障,比如发生某个网络问题,就可能导致这些重试请求同时出现,进而引发过载。另外,如果请求没有成功,以指数型延迟重试。比如第一次是 3 秒后重试,那么第二次 6 秒,第三次 12 秒,以此类推。
- 考虑使用一个全局重试预算。例如每个进程每分钟只允许重试60次如果重试预算耗尽那么直接将这个请求标记为失败而不真正发送它。这个策略可以在全局范围内限制住重试造成的影响容量规划失败可能只是会造成某些请求被丢弃而不会造成全局性的故障。
第二个话题是请求的重要性级别criticality。可以考虑将发给服务端的请求重要性级别标记为 14 之间的数,它们分别代表 “可丢弃的”、“可延后处理的”、“重要的”、“非常重要的”。在服务端发生过载时,它将优先放弃 “可丢弃的” 请求,次之放弃 “可延后处理的” 请求,以此类推,直到系统负荷回归正常。
第三个话题是请求延迟和截止时间deadline。一个超长时间的请求只是会让一个客户慢。但是结构性的超长时间的请求它可能会导致系统持续恶化并引起雪崩效应。给 API 请求设置一个小但合理的超时时间,是大幅降低雪崩风险的有效手段。
如果处理请求的过程有多个阶段,比如每个阶段又是由一系列 API 请求组成,该服务器应该在每个阶段开始前检查截止时间,以避免做无用功。
第四个话题是客户端侧的节流机制,也就是是否可能在客户端做自适应的过载保护。客户端的过载保护有它天然的优势,在抛弃超过配额的请求时,它完全不会浪费服务端的资源。
当某个客户端检测到,最近的请求错误中的一大部分都是由于 “配额不足”错误导致时,该客户端就开始自行限制请求速度,限制它自己生成请求的数量。超过这个请求数量限制的请求直接在本地回复失败,而不会真正发到网络层。
我们使用一种称为自适应节流的技术来实现客户端节流。具体地说,每个客户端记录过去两分钟内的以下信息:
- 请求数量requests应用层代码发出的所有请求的数量总计。
- 请求接受数量accepts被服务端接受处理的请求数量。
在常规情况下,这两个值是相等的。随着后端任务开始拒绝请求,请求接受数量开始比请求数量小了。客户端可以继续发送请求直到 requests = K * accepts一旦超过这个限制客户端开始自行节流新的请求在本地会有一定概率被拒绝在客户端内部。客户端请求拒绝概率公式如下
<img src="https://static001.geekbang.org/resource/image/5d/0a/5d49b4d459d763b5c16ef99378c46e0a.jpeg" alt="">
当客户端开始自己拒绝请求时requests 会持续上升,而继续超过 accepts。这里虽然看起来有点反直觉因为本地拒绝的请求实际没有到达后端但这恰恰是这个算法的重点。随着客户端发送请求的速度加快相对后端接受请求的速度来说我们希望提高本地丢弃请求的概率。
自适应节流算法可以在整体上保持一个非常稳定的请求速率。即使在超大型的过载情况下,后端服务基本上仍然可以保持 50% 的处理率。这个方式的一大优势是客户端完全依靠本地信息来做出决定,同时实现算法相对简单:不增加额外的依赖,也不会影响延迟。
K 值决定了过载时服务端的拒绝率,默认为 K=2。这意味着服务端过载的时候有 50% 的处理率。如果我们调整为 K=1.1,那么算法变得非常激进,服务端有 90% 的处理率。什么情况下可以这么激进?对那些处理请求消耗的资源和拒绝请求的资源相差无几的系统来说,用 50% 的资源来发送拒绝请求可能是不合理的,这时就可以更激进。
## 结语
总结下我们今天的内容。我们聊的话题主要是关于过载。
所谓过载,最直白的理解,当然就是因为活跃的用户超过了资源的承载能力范围,导致某类资源耗尽,进而体现出系统过载。
当一个系统过载时,某些东西总是要被牺牲掉。一旦一个服务越过了临界点,服务一些用户可见错误,或者低质量结果,要比尝试继续服务所有请求要好。理解这些临界点所在,以及超过临界点系统的行为模式,是所有想避免因过载而引发雪崩效应的 SRE 所必需具备的。
如果不加小心,某些原本为了降低服务背景错误率或者优化稳定状态的改变,反而会让服务更容易出现事故。比如,在请求失败的时候重试、负载自动转移、自动杀掉不健康的服务器、增加缓存以提高性能或者降低延迟等等。这些手段原本都是为了优化正常情况下的服务性能,但是他们某种程度上来说,也是过载与雪崩效应的成因。
过载引发的雪崩效应可能时间很短,所以考虑怎么让我们的系统能够自动应对过载是非常必要而且重要的事情。
如果你对今天的内容有什么思考与解读,欢迎给我留言,我们一起讨论。下一讲我们将聊聊 “业务的可支持性与持续运营”。
如果你觉得有所收获,也欢迎把文章分享给你的朋友。感谢你的收听,我们下期再见。

View File

@@ -0,0 +1,105 @@
<audio id="audio" title="54 | 业务的可支持性与持续运营" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/0e/84/0e854c40cf268fde98138e4123f24584.mp3"></audio>
你好,我是七牛云许式伟。
保障业务的 7x24 小时不间断服务并不容易所以我们花费了非常长的篇幅来探讨这件事情。我们需要系统性的、结构化的解决方案它的涉及面非常广需要基础架构、中间件、SRE 工作平台等多个层次、多个工种之间的紧密配合。
## 客户支持
但就算线上没有出问题,我们的用户仍然会遇到各种麻烦。所以大部分公司都会成立客户支持团队来服务客户。客户支持团队可能使用工单、电话或者即时通讯工具来服务客户。
对于客户支持部门,我们如何评估他们的业务价值?
很多公司会关注服务质量,简单说就是客户对某个会话的服务满意度。这当然也是重要的,但对整个客户支持部门来说,最核心的还不是这一点。
我们最核心的关注点,是如何减少客户服务的人工成本。
通常来说,客户支持团队会收到客户各式各样的问题反馈。这些反馈大体可以分这样几类:
- 使用姿势类;
- 报障类;
- 投诉与建议类。这个不是今天关注的内容,不再展开。
我们首先看 “使用姿势类”。这又细分为两种情况:一种是完全不知道应该怎么用我们的产品,需要有一步步引导的向导或者示范的 DEMO。另一种是接入了我们的产品但是发生了非预期的结果于是客户迷茫了需要寻求帮助。
怎么才能避免用户拿到我们的产品,完全摸不到北,不知道应该怎样才能使用起来的情况发生?在产品中植入必要的引导是非常必要而且重要的。产品帮助文档虽然应该有,但是我们应该坚信的一点是,绝大部分客户的问题应该依靠产品自身来解决,而不是依靠产品文档来解决。
要想做到更好的产品引导,需要我们代入到客户的使用场景中去,预想客户各种可能遇到的问题是什么。
当然,在逐步迭代的场景中,历史的用户行为分析也能够指导我们找到需要改善的关键问题是什么。
客户接入了,但是发生了非预期的结果。在很多时候,出错的信息往往是很难理解的。这时候客户就被迫到产品文档网站或者搜索引擎去搜索出错对应的解决方案。如果迟迟无法得到解决,客户就会怀疑我们的产品,转向其他的替换方案。
所以错误信息的呈现,也是需要非常讲究的。我们非常有必要将错误信息的表达变得更加贴近用户的语言。甚至,我们在错误提示信息中,给出我们的建议,或者建议文档的链接。
还有一些错误的使用姿势,可能并不会在存在误用的现场直接表现为错误,而是在其他的某个场景下才发生错误。
这方面一个很好的例子是 Go 语言的 map也就是大家通常理解的哈希表。Go 语言的 map 并不是多线程安全(准确地说,在 Go 语言里面其实是多 goroutine 安全)的,如果在多个 goroutine 中共享这个 map 但是却忘记了加锁,这个时候结果就是不可预期的。
按大部分人常规的逻辑,这样的误用在 Go 的文档中给予必要的提醒就好了。从职责来说,用户误用那是用户自身的问题。
但是 Go 语言团队显然不是这么看的。它特意在代码逻辑中加入了这种使用姿势上误用的检测,检测到错误后直接抛出异常,让程序停止运行。在异常信息的提示中,它告诉你,你的代码存在了什么样的问题。
这让用户更快地找到错误的根因,并且及时去修复错误。
这个细节让我感受颇多。产品被开发出来,对于很多研发人员的认知来说,是个结束。但是从业务的持续运营角度来说,这只是开始。
持续运营中产生的各类成本,无论是 SRE 做服务保障的成本,还是客户支持服务客户的成本,还是客户为了寻找问题根源而花费的时间成本,都需要我们去认真思考怎么进行大幅度的优化。
我们再来看下 “报障类”,也就是用户认为我们服务出问题了。当然实际问题不一定真是我们的服务有问题,也有可能是用户自身的环境,比如 Wi-Fi 或者本地运营商的问题。但无论原因如何我们都需要给客户一个确切的问题说明和建议的解决方案。
为了应对这种场景,我们就需要有意识地收集用户请求的整个调用链。这个机制和我们前面介绍的 “[52 | 故障排查与根因分析](https://time.geekbang.org/column/article/157416)” 一讲中介绍的 Tracing 机制类似,都会依赖 request id 这样的东西作为线索。只不过,为了有更加完整的调用链,这个过程不只是在服务端,同时还需要应用到客户端。
有了 Tracing 机制,客户端基本上就可以做到一键报障。在报障时客户端会携带自己的 IP 和 Tracing 日志。这样客户支持人员就可以通过客户 IP 和 request id 知道这个客户最近都发生了什么事情。
但某种程度来说,一键报障也是繁琐的。所以很多应用程序会让你签署一份用户体验改进协议,在获得你的同意后,程序就可以把 Tracing 日志主动同步给服务器。这样我们的客户支持团队就可以不是针对某个具体的客户,而是针对相对全局性的问题来进行改进。
当然,这也意味着我们需要建立合适的数据运营的体系。通过这个体系,我们可以迅速找到用户最经常遇到的问题是什么,并持续加以改进。七牛云的 Pandora 日志平台可以协助做好这方面的改进工作的开展。
有一些改进我们来不及系统化进行改进,但是我们已经形成了一些最佳实践,那么对于 VIP 类客户我们可以先主动进行最佳实践的推广。
用户报障也有可能是我们的业务真实发生了故障。这时客户报障通常会有一个突然的爆发式增长,并且往往容易让我们的客户支持团队应接不暇。
当线上发生故障的时候,什么时候对外宣布事故,什么时候主动通知客户?
首先,先宣布事故发生,随后找到一个简单解决方案,然后宣布事故结束,要比在问题已经持续几个小时之后才想起告知客户要更好。
所以针对事故,我们应当设立一个明确的宣布条件。比如,如果下面任何一条满足条件,这次事故应该被及时宣布:
- 是否需要引入 SRE 之外的团队来一起处理问题?
- 在处理了一小时后,这个问题是否依然没有得到解决?
- 这次事故是否正在大范围地影响了最终用户?
如果平时不经常使用,事故流程管理的可靠性萎缩得很快。怎么才能使工程师不忘记他们的流程管理的技能呢?难道一定要制造更多事故吗?
一个可能的思路是,经常针对之前发生过的灾难进行角色扮演式的演习,比如演习另外一个地区处理过的问题,以更好地熟悉事故流程管理体系。
当然,事故流程管理框架其实也往往适用于其他的跨团队的常规运维变更过程。如果我们经常使用相同的流程管理方式来处理线上的变更请求,那么在事故来临时,就可以很好地利用这些流程管理手段来管理它。
## BOSS 系统
客户使用上的困扰、排错、事故告知、投诉与建议,这些是客户支持所面临的常规工作。但实际上我们在服务客户过程中,往往还有更常规的业务支持工作要做,比如客户的业务开通、财务与发票、业务管理等。这类系统我们往往把它叫 BOSS 系统。
BOSS 系统面向的用户是企业的内部员工,为员工能够正常开展业务服务。
什么样的事务,应该被加入到 BOSS 系统?一个基本的策略是越高频执行的业务动作,越应该被固化到系统中。
业务过程固化到系统会有很多好处。
首先,最常规的,是业务效率提升。员工执行业务有更高的便捷性。
其次,安全风险管理。有一些业务过程可能会有潜在的风险需要防范,通过固化业务过程,进行必要的风险检查,可以避免掉最常见的已知风险。
最后,业务过程进一步被数字化,业务行为被记录,这为系统性地进行业务优化提供了可能。
除了客户支持系统、业务 BOSS 系统之外,还有一类极其重要的业务运营需求,就是客户增长的运营了。但客户增长运营是一个复杂的话题,我们这里就不再展开。
## 结语
今天我们聊的主要是一个视野的问题。除了产品的功能外,实际上为了产品能够更好地服务好客户,我们需要关注售前、售后支持能力的构建。
为了更好地支持客户,以及进行后续用户体验的改善,我们往往需要将用户行为记录写到日志系统中,以便于进一步地分析和挖掘。
如果你对今天的内容有什么思考与解读,欢迎给我留言,我们一起讨论。下一讲我们将聊聊 “云计算、容器革命与服务端的未来”。
如果你觉得有所收获,也欢迎把文章分享给你的朋友。感谢你的收听,我们下期再见。

View File

@@ -0,0 +1,105 @@
<audio id="audio" title="55 | 云计算、容器革命与服务端的未来" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/c5/17/c5d8279b1d293358dce024628a043617.mp3"></audio>
你好,我是七牛云许式伟。
到今天这一讲我们服务治理相关的话题基本上接近尾声。通过前面的内容我们可以知道服务治理比软件治理要复杂很多。它的涉及面非常广需要有系统性的、结构化的解决方案需要基础架构、中间件、SRE 工作平台等多个层次、多个工种之间的紧密配合。
软件的服务化过程本身是互联网的胜利。从最初以泛娱乐场景为主,到今天影响国民经济的方方面面,场景越来越严肃和多样化。
软件服务化使得工程师有了新的职能on call。软件工程师并不是把软件开发出来就完了还需要保证软件上线后的服务品质比如稳定性。在线上出问题的时候软件工程师还需要随时响应线上的 on call 请求,参与到故障排查的过程中去。
但是提供靠谱的服务是如此之难,尤其在软件并不是自己团队开发的情况下,保证服务质量的过程增加了许多不可控的因素。
云计算的诞生是一次软件交付方式边界上的重新定义。在此之前IT 技术供应商通常的交付物是可执行程序或源代码。这种交付方式更多的是软件功能的交付,但是并不参与到软件线上的运行状况的管理。
云计算定义了全新的交付方式IT 技术供应商不再提供可执行程序或源代码,而是互联网服务。用户使用 IT 服务时并不需要了解背后运转机制。即使在线上出问题的时候,也是 IT 技术服务商安排技术人员去解决,而不是用户自己去想办法解决。
这意味着云计算改变了用户与 IT 技术服务商之间的配合方式。从之前的对交付过程负责,到对服务的质量结果负责,双方的职责更为简单清晰。
云计算发展到今天,大体经历了两个阶段。
## 资源交付革命
第一个阶段,是资源交付的云化。它的变革点在于 IT 资源交付的效率。
在云计算之前,业务上线过程大体上来说是这样的:
- 首先,购买基础的 IT 资源,比如服务器和交换机等;
- 然后,将服务器和交换机等提供物流系统运送到 IDC 机房并上架;
- 安装上基础的操作系统;
- 部署业务系统。
而云计算之后的业务上线被简化为:
- 购买云上虚拟的云主机若干台,这些云主机已经按用户选择预装了基础的操作系统;
- 部署业务系统。
这个阶段交付的 IT 产品形态并没有发生根本性的变化,以前是物理机,现在是云主机,两者使用上的用户体验并没有很大的差异性。但这里面 IT 资源交付效率发生了巨大的变化,它主要体现在以下几个方面。
- 资金优化:消除了物流的成本。
- 时间优化:物流时间、产品自助化水平(上架、操作系统安装)。
- 资源复用率提升:云计算通过将不同用户的 IT 资源聚集在一起,提升了 IT 资源的使用效率,减少了社会资源的浪费。
## 容器革命
云计算的第二个阶段,以容器革命为标志,以 Kubernetes 为事实标准,迭代的是服务治理的能力。
前面我们在 “[47 | 服务治理的宏观视角](https://time.geekbang.org/column/article/144803)” 提到过以下服务治理系统的模型:
<img src="https://static001.geekbang.org/resource/image/37/95/370482fbdc92c69bed1e74de122b4f95.png" alt="">
服务治理系统的影响因素非常复杂。我们大体将各类影响因素分为以下几种类型。
- 软硬件升级与各类配置变更,即发布。
- 软硬件环境的故障。
- 终端用户的请求。比较典型的场景是秒杀类,短时间内大量的用户涌入,导致系统的承载能力超过规划,产生服务的过载。当然还有一些场景,比如有针对性的恶意攻击、特定类型的用户请求导致的服务端资源大量消耗等,都可能引发服务故障。
即使是在今天,在容器技术大范围应用之前,我们大部分公司的服务治理系统都建立在物理机或云主机的基础上。这种做法的问题在于系统的复杂性过高,不同的影响因素交织在一起,让彻底解决问题变得非常困难。
我们举配置变更作为例子。通常来说,配置变更是我们主动对系统进行软硬件升级或配置参数调整导致的,它应该被认为属于计划性的活动。
变更是故障之源。
只有计划性的配置变更才会按发布计划和检查清单有序地执行。但是,显然各类软硬件环境的故障是非预期的,不知道什么时候会来一下。但是如果我们基于物理机来做服务治理,必然需要面临因为不可预期的软硬件故障而进行配置变更。
这种为了恢复故障的变更有更大的临时性,自然也更难形成高质量的检查清单来检查配置变更的靠谱度。如果线上已经通过流量调度进行了必要的恢复那倒是还好,最多是对这样的临时变更做更多的人工检查来确保质量。
如果某种配置变更方式经常被用于某种故障恢复的场景,那么这样的配置变更就很可能被脚本化下来,以便让故障恢复过程更加高效。
这样随着时间的日积月累,我们就得到了基于物理机的服务治理系统。
但是,这种服务治理系统虽然做到了很高的自动化,但是它并不能算是一个高度自治的系统。
高度自治的服务治理系统对软硬件环境的故障有天然的免疫:我们什么都不用做,系统自己完成自我修复过程。
这就是容器革命带来的变化。
今天 Kubernetes 基本已经成为 DCOS数据中心操作系统的事实标准。它带来了以下重要的改变。
- 用户操作的对象不再有机器这玩意,最核心的概念是服务。当然这是称之为数据中心操作系统概念的由来。
- 硬件资源池化。软件或服务与硬件环境解绑,可以轻松从一台物理机迁移到另一台物理机。
- 面向逻辑视图描述集群状态。配置中心的数据只体现业务系统的逻辑,不体现物理特性。
当然,今天容器革命仍然还在如火如荼地进行,完整的演进过程会很漫长。这背后的原因在于,容器技术虽然先进,但它从根本上改变了我们使用计算力的习惯。
## 服务端的未来
未来服务端技术的演进会走向何方?
服务治理系统的迭代,最终将让我们达到这样的状态。
- 任何业务都可以轻松达到 7x24 小时不间断服务。高并发?高可用?高可靠?小菜一碟。
- 做业务都足够的傻瓜化。服务端工程师?不存在的,我们要的只是 SQL 工程师。
- 做一个新的有状态的存储中间件虽然比做业务麻烦一点,但是,一方面也没有多少新的存储中间件需要做的,另一方面做一个存储中间件有足够便捷的辅助设施,也不会比今天做一个内存中的哈希表难多少。
所以在我看来,服务端工程师很有可能只是一个阶段性的历史背景下的产物。随着互联网应用开发的基础设施越来越完善,服务端开发的成本越来越低,最终和前端工程师重新合而为一。
是的,我说的是重新回到软件时期的样子。那时候,并不存在前端和后端工程师之分。
## 结语
今天我们聊的话题是服务端的未来。云计算和容器革命将服务端的基础设施化推向了高潮。未来,也许我们并不需要专门的服务端开发工程师来做业务开发。
如果你对今天的内容有什么思考与解读,欢迎给我留言,我们一起讨论。下一讲我们将对 “服务治理篇” 这一章的内容进行回顾与总结。
如果你觉得有所收获,也欢迎把文章分享给你的朋友。感谢你的收听,我们下期再见。

View File

@@ -0,0 +1,134 @@
<audio id="audio" title="56 | 服务治理篇:回顾与总结" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/c7/c5/c77a1be317562b58a3089fe0919254c5.mp3"></audio>
你好,我是七牛云许式伟。到今天为止,我们第四章 “服务端治理篇” 就要结束了。今天,让我们对整章的内容做一个回顾与总结。
服务端的话题被我分为了两章:“服务端开发篇” 与 “服务治理篇”。它们的边界在于,服务端开发致力于设计合适的业务架构来满足用户需求,而服务治理则致力于让服务端程序健康地为客户提供 7x24 小时不间断的服务。
从服务端开发的角度来看,服务端的迭代并不大。
<img src="https://static001.geekbang.org/resource/image/6f/77/6f3e72d36bdba378fd8382548a3cd677.png" alt="">
上面这幅图我们已经很熟悉了。作为架构师,我们需要清楚的一点是,这个服务端体系架构图并不是逐步迭代出来的,实际上,从服务端开发这个分工出现之后,这个架构就没有发生过改变。这些年迭代的是什么?迭代的是负载均衡、数据库/存储中间件能力的丰富与完善。
## 服务端操作系统的演进
但从服务治理角度看,服务端技术的迭代很快。让我们先从操作系统谈起。
作为最底层的服务端操作系统,最初从桌面操作系统而来。但桌面操作系统自身在发展,服务端操作系统自身也在发展,两者渐行渐远。
桌面的领域特征是强交互以事件为输入GDI 为输出。所以,桌面技术的迭代,是交互的迭代,是人机交互的革命。
而服务端程序有很强烈的服务特征。它的领域特征是大规模的用户请求,以及 24 小时不间断的服务。这些都不是业务功能上的需要,是客户服务的需要。
所以服务端操作系统的演进,并不是因为服务端业务开发的需要,是服务治理的需要。
所以服务端技术的迭代虽然一开始沿用了桌面操作系统的整套体系框架但它正逐步和桌面操作系统分道而行转向数据中心操作系统DCOS之路。
第一个里程碑的事件是 Docker 的诞生。容器技术诞生已经多年,但是把容器技术的使用界面标准化,始于 Docker。它完成了服务端软件的标准化交付与底层的服务端本地操作系统实现了解耦。
在 Docker 之前,不同服务端本地操作系统的软件交付有这样几个问题。
其一标准不同。MacOS 有 brewLinux 不同分支差别很大,有的是基于 rpm有的是 apt五花八门。
其二,不符合服务软件的交付规格需要。这些软件管理工具只实现了一个软件仓库,它虽然标准化了软件安装的过程,但并没有定义服务的运行规范。
其三,环境依赖。这些软件管理工具对软件的描述并不是自包含的。它们并没有非常干净的软件运行环境的描述,行为有较大的不确定性,甚至有大量的软件包在实际安装时会因为各种各样的系统环境问题而失败。
在这种情况下, Docker 诞生一下子就火爆了。随后OCI 标准组织也应运而生:
- [https://www.opencontainers.org/](https://www.opencontainers.org/)
它定义了有关于容器的两大规范:
- 运行时标准Runtime Specification
- 镜像标准Image Specification
Docker 出现后,紧接着 CoreOS 也推出了新的服务端操作系统。CoreOS 是专注于服务端的操作系统,它认为除了只读的操作系统内核外,所有的软件都应该是基于容器发布的。
这种思想很先进。但无奈的是,它一方面对用户习惯改变过大,另一方面也没有真正切中用户最关键的痛点,导致它一直没能够流行起来。
从商业角度来说,早期 Docker 和 CoreOS 表现得很互补的样子但是双方的商业目标其实相同都是希望能够成为数据中心操作系统DCOS的领导者。
所以Docker 推出了 Docker Swarm而 CoreOS 也有自己的集群版。这下,两家公司的友好协作的表象很快就被打破了。
但最后Google 牵头推 Kubernetes结束了 DCOS 之争。当然这事今天来重新回顾,它也在情理之中,毕竟容器技术实际上最早是在 Google 推动下被加入 Linux 内核,而它内部更是有 Borg 这样的 DCOS 系统,有着丰富的基于容器的服务治理实践经验。
无论是 Docker 还是 CoreOS两家公司都大大低估了 DCOS 这件事情的难度。当然这事低估的并不只是他们,也包括七牛云。在 Docker 诞生后,我们就意识到 DCOS 是未来,所以 2014 年我们就成立了 QCOS 项目组来做这事,但最终这个项目组转向了拥抱 Kubernetes。
## 服务端治理篇的内容回顾
不单单是服务端操作系统,整个服务治理的变化都非常之快。
虽然到今天服务端技术已经有二十多年的历史但是它仍然处于快速演进的状态因为它太复杂了。服务治理比软件治理要复杂很多。它的涉及面非常广需要有系统性的、结构化的解决方案需要基础架构、中间件、SRE 工作平台等多个层次、多个工种之间的紧密配合。
本章开篇的第一篇,我们是用下图来描述服务治理系统的:
<img src="https://static001.geekbang.org/resource/image/2c/c7/2cb6bf3dad4bdd4311025e3682a302c7.png" alt="">
这个图非常抽象,基本上只是概要描述了服务治理系统的输入和输出。但从系统抽象的角度来说,第一步我们要理清楚的就是输入和输出,它们代表了系统的规格。
紧接着我们把话题转到了 “什么是工程师思维”。
为什么我们要谈工程师思维?因为从 “基础平台(硬件架构/编程语言/操作系统)”,到 “业务开发(桌面开发/服务端开发)”,再到 “业务治理(服务治理/技术支持/用户增长)”,我们的话题一个比一个更加 “不稳定”。它们有更高的需求复杂性,有更多的事务性工作。
只有我们沉下心来,认真理解用户需求,并对这些需求进行结构化的梳理,形成系统性的解决方案,才能将问题最终彻底解决掉。
为了让大家对整个服务治理篇的内容有个宏观的了解,我画了一幅图,如下。
<img src="https://static001.geekbang.org/resource/image/55/e1/55a0f8c91fb136b335b765c118a447e1.png" alt="">
我们从服务的变更(包括:发布、升级与版本管理)、服务的健康状况(包括:日志、监控与报警)、服务的故障处理(包括:故障域与故障预案、故障排查与根因分析、过载保护与容量规划)等几个方面来探讨服务治理的具体细节。
最后,我们探讨了云计算诞生的历史意义、发展阶段,以及未来服务端技术发展的前景。简单来说,云计算的诞生,标志着服务端分工的正式形成。未来,所谓服务端工程师很可能不再存在。要么,你往基础设施走,变成一个云计算基础设施的研发工程师。要么你深入行业,变成某个领域的研发工程师,但是这时,就别抱着自己是服务端工程师的态度不放,好好琢磨清楚业务需求。
## 架构思维
最后,我想从 “架构思维” 的角度来看服务治理篇。
首先,这是自信息科技诞生以来,最宏大的架构设计案例,没有之一。为什么我们这么说?因为需求太复杂。
从架构实践的角度来说,它显然并不是太适合架构新手学习的案例。但是从体会架构分解的核心指导思想来说,它却又是极好的学习材料。
我们举个例子,服务治理的一个核心话题是 “变更”。单就 “变更” 这一个需求点来说,它就涉及非常多样化的情况。包括:
- 软硬件的升级;
- 配置项调整;
- 数据库表结构的调整;
- 增加/减少机器;
- 数据中心的搬迁;
- 入口域名、IP 的调整;
- ......
从具体的变更情况来说,太多太多,难以穷尽。
那么,怎么对其进行结构化的梳理,形成系统性的体系架构来抽象 “变更” 需求?
首先,我们对 “变更” 需求进行正交分解,分为 “主动性变更” 与 “被动变更”。“主动性变更” 是指有计划的变更行为,例如软硬件升级、数据库表结构的调整等等。“被动变更” 是指由于线上用户请求、业务负载、软硬件环境的故障等非预期的行为导致的变更需求,比如扩容、由于机房下线而导致的 DNS 配置项变更等等。
为了应对 “被动变更”,服务治理系统对服务的软硬件环境的依赖进行了系统性的梳理。最终,硬件被池化。业务系统的逻辑描述与硬件环境彻底解耦。
然后,我们对 “主动性变更” 进行进一步的正交分解,分为 “软件变更” 与 “软件数据的变更”。“软件变更” 通过版本化来表达。每个 “软件” 版本必须是自包含的,它自身有完整的环境,不会出现跑在 A 机器和 B 机器不一致的情况。
版本化是非常重要的概念。它意味着每个独立版本的数据都是确定性的、只读的、行为上可复现的。大家最熟悉的版本化的管理思想,就是源代码管理系统,比如 Git。在服务治理系统中“软件变更” 和我们熟悉的源代码管理系统如出一辙。
最后,就只剩下 “软件数据的变更”。它是和具体业务关联性非常强的变更,没法进一步去抽象和简化,但好的一点是这类变更是非常低频的,可以统一通过软件升级的流程管理系统来管理它带来的风险。
我相信未来我们的大部分读者都会和服务治理系统打交道。它终将成为我们所依赖的基础设施新一代的服务端操作系统数据中心操作系统DCOS。在学习和研究服务治理系统的过程中我们每一个人可以认真体会服务治理这个宏大的架构案例并以此来提升我们对架构设计的感悟。
## 结语
今天我们对本章内容做了概要的回顾,到此为止,我们 “基础平台”、“桌面开发”、“服务端开发”、“服务治理” 这四大模块就结束了。从工程师架构设计角度来说,它们基本上涵盖了我们会打交道的绝大部分通用业务场景。
理解了这几章的内容,整个软件大厦的骨架就可以明了了。
下一步应该学什么?架构思维原则?或者是设计模式?
架构思维的确是有很多共性的东西,值得我们总结出来细细体会。比如 “开闭原则”,多么有力的架构思维的总结,值得我们时时拿出来提醒自己。
不过,我个人不太喜欢常规意义上的 “设计模式”。或者说,我们对设计模式常规的打开方式是有问题的。理解每一个设计模式,都应该放到它想要解决的问题域来看。所以,我个人更喜欢的架构范式更多的是 “设计场景” 的总结。“设计场景” 和设计模式的区别在于它有自己清晰的问题域定义,是一个实实在在的通用子系统。
是的,这些 “通用的设计场景”,才是架构师真正的武器库。如果我们架构师总能把自己所要解决的业务场景分解为多个 “通用的设计场景” 的组合,这就代表架构师有了极强的架构范式的抽象能力。而这一点,正是架构师成熟度的核心标志。
如果你对今天的内容有什么思考与解读,欢迎给我留言,我们一起讨论。下一讲我们正式开始进入第五章:架构思维篇。
如果你觉得有所收获,也欢迎把文章分享给你的朋友。感谢你的收听,我们下期再见。

View File

@@ -0,0 +1,152 @@
<audio id="audio" title="加餐 | 怎么保障发布的效率与质量?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/72/4f/72b0d46ce876271ac735ef67e701394f.mp3"></audio>
你好,我是七牛云许式伟。
## 为什么要有发布流程?
在 “[49 | 发布、升级与版本管理](https://time.geekbang.org/column/article/150346)” 一讲中我们提到过:
>
变更是故障之源。
这种由于业务需要而主动发起的软硬件升级与各类配置变更,我们可以统一称之为发布。例如:
- 更换交换机的类型,或升级版本。
- 更换所依赖的基础软件,或升级版本。基础软件包括操作系统、负载均衡、数据库等等。
- 升级业务软件本身。
- 调整软硬件环境的配置项。
特殊地,如果集群的服务对扩容缩容有很好的自动化支持,可以非常便捷地增加或减少服务器,那么这种情况虽然发生了集群的变化,我们可以不把它看作变更,不走发布相关的流程。尤其在硬件已经完全池化时,增加、减少服务器可能是个非常标准化且低成本的操作。
我们通常说的 “版本发布”,往往侧重点是在升级业务软件的版本,这是发布中最常发生的情况,当然也是我们最为关注的。
传统的软件公司的发布周期往往很长,有几个月甚至有的是按年来计算。而互联网公司的发布周期则非常不同。之所以快速发布、快速迭代变得简单的原因是,它们仅仅需要在服务器端发布,而不需要发布到每个使用者的电脑上。
一个每三年发布一次新产品的公司不需要详细的发布流程。因为发布的频率太低了,发布流程的优化能够带来的收益太小。
但是如果我们每天都在发布,甚至每天发布很多次,那么如此高频的发布速度,就要求我们创建和维护一个效率与质量都能够兼顾的精简的发布流程。
一个精简的发布流程,通常需要有发布平台这样的基础设施,把发布过程中反复遇到的问题对应的解决方案固化到系统中。
但是系统并不能解决所有的发布问题。变更终究是存在未知的新东西需要人工进行检查判断。为此SRE 部门往往还建立了一个专门的团队负责发布,即发布协调小组。团队成员称为 “发布协调工程师Launch Coordination EngineeringLCE”。
发布协调小组会针对每个业务,维护一个该业务的 “发布检查列表”,包括针对每次发布需要检查的常见问题,以及避免常见问题发生的手段。只有在发布检查表中的检查点都得到了确认,才会给版本发布放行。
这个列表在实践中被证实,它是保障发布可靠性的重要工具。
## 建立在系统之上的灰度发布
除了 “发布检查列表”,我们还有一个至关重要的保障发布质量的做法:灰度发布。
不管你如何小心,发布检查做得多全面,仍然只是在尽可能减少发布的风险,而不是消除。任何改动都具有一定的危险性,而任何危险性都应该被最小化,这样才能保障系统的可靠性。
在小型的测试环境上测试成功的变更,不见得在生产环境就没有问题,更何况从 SRE 的角度,测试的覆盖率也是不能假设的。
任何发布都应该灰度进行,并且在整个过程中还需要穿插必要的校验步骤。刚开始,新的服务可能会在某个数据中心的一台或几台机器上安装,并且被严密监控一段时间。如果没有发现异常,新版本会在更多台机器上安装并再次监控,直至最后完成整个发布过程。
发布的第一阶段通常被称为 “金丝雀”。这和煤矿工人带金丝雀下矿井检测有毒气体类似,通过使用这些“金丝雀” 服务线上流量,我们可以观察任何异常现象的发生。
“金丝雀” 测试适用于正常的软件版本发布,也适用于配置项的变更。负责配置变更的工具通常都会对新启动的程序监控一段时间,保证服务没有崩溃或者返回异常。如果在校验期间出现问题,系统会自动回退。
灰度式发布的理念甚至并不局限于软件和服务的发布。例如,我们商业上的高成本的运营活动,往往会先选择一到两个地区先做实验,然后再把成功经验复制到全国各地。
所以灰度发布思想的一个自然延伸是做功能开关,也就是大家熟悉的 AB 测试。很多东西在测试环境中无法模拟时,或者在真实环境中仍然存在不可预知的情况时,灰度机制就非常有用了。
不是所有的改动都可以一样对待。有时我们仅仅是想检查某个界面上的改动是否能提升用户感受。这样的小改动不需要几千行的程序或者非常重量级的发布流程。我们可能希望同时测试很多这方面的改动。
有时候我们只是想要知道是否有足够多的用户会喜欢使用某个新功能,就通过发布一个简单的原型给他们测试。这样我们就不用花费数个月的时间来优化一个没人想要使用的功能。
通常来说,这类 AB 测试框架需要满足以下几个要求:
- 可以同时发布多个变更,每个变更仅针对一部分服务器或用户起作用。
- 变更可以灰度发布给一定数量的服务器或用户,比如 1%。
- 在严重Bug发生或者有其他负面影响时可以迅速单独屏蔽某个变更。
- 用数据来度量每个变更对用户体验的提升。
## LCE 的职责
LCE 团队负责管理发布流程以确保整个发布过程做到又快又好。LCE 有如下这些职责:
- 审核新产品及相关的内部服务,确保它们的可靠性标准达到要求。如果不达预期,提供一些具体的建议来提升可靠性。
- 在发布过程中作为多个团队之间的联系纽带。
- 负责跟进发布系统相关的所有技术问题。
- 作为整个发布过程中的一个守门人,决定某次发布是否是 “安全的”。
整体来说LCE 的要求其实是相当高的。LCE 的技术要求与其他的 SRE 成员一样,但这个岗位打交道的外部团队很多,需要有很强的沟通和领导能力。他需要将分散的团队聚合在一起达成一个共同目标,同时还需要偶尔处理冲突问题,还要能够为软件开发工程师提供建议和指导。
## 发布检查列表
我们前面已经提过,发布检查列表可以用来保障发布质量,它是可靠发布产品与服务的重要组成部分。一个完备的检查列表通常包含以下这些方面的内容。
其一,架构与依赖相关。针对系统架构的评审可以确定该服务是否正确使用了某类基础设施,并且确保这些基础设施的负责人加入到发布流程中来。为什么要引入基础设施的负责人,是因为需要确认相关依赖的服务都有足够的容量。
一些典型的问题有:
- 从用户到前端再到后端,请求流的顺序是什么样的?
- 是否已经将非用户请求与用户请求进行隔离?
- 预计的请求数量是多少?单个页面请求可能会造成后端多个请求。
其二,集成和公司最佳实践相关。很多公司的对外服务都要运行在一个内部生态系统中,这些系统为如何建立新服务器、配置新服务、设置监控、与负载均衡集成,以及设置 DNS 配置等提供了指导。
其三,容量规划相关。新功能通常会在发布之初带来临时的用量增长,在几天后会趋于平稳。这种尖峰式的负载或流量分布可能与稳定状态下有显著区别,之前内部的压力测试可能失效。
公众的兴趣是很难预测的,有时甚至需要为预计容量提供 15 倍以上的发布容量。这种情况下灰度发布会有助于建立大规模发布时的数据依据与信心。
一些典型的问题有:
- 本次发布是否与新闻发布会、广告、博客文章或者其他类型的推广活动有关?
- 发布过程中以及发布之后预计的流量和增速是多少?
- 是否已经获取到该服务需要的全部计算资源?
其四,故障模式相关。针对服务进行系统性的故障模式分析可以确保发布时服务的可靠性。
在检查列表的这一部分中,我们可以检查每个组件以及每个组件的依赖组件来确定当它们发生故障时的影响范围。
一些典型的问题有:
- 该服务是否能够承受单独物理机故障?单数据中心故障?网络故障?
- 如何应对无效或者恶意输入是否有针对拒绝服务攻击DoS的保护
- 是否已经支持过载保护?
- 如果某个依赖组件发生故障,该服务是否能够在降级模式下继续工作?
- 该服务在启动时能否应对某个依赖组件不可用的情况?在运行时能否处理依赖不可用和自动恢复情况?
其五,客户端行为相关。最常见的客户端滥发请求的行为,是配置更新间隔的设置问题。比如,一个每 60s 同步一次的新客户端会比600s 同步一次的旧客户端造成10倍的负载。
重试逻辑也有一些常见问题会影响到用户触发的行为,或者客户端自动触发的行为。假设我们有一个处于过载状态的服务,该服务由于过载,某些请求会处理失败。如果客户端重试这些失败请求,会对已经过载的服务造成更大负载,于是会造成更多的重试,更多的负载。客户端这时应该降低重试的频率,一般需要增加指数型增长的重试延迟,同时仔细考虑哪些错误值得重试。例如,网络错误通常值得重试,但是 4xx 错误(这一般意味着客户端侧请求有问题)一般不应该重试。
自动请求的同步性往往还会造成惊群效应。例如,某个手机 APP 开发者可能认为夜里2点是下载更新的好时候因为用户这时可能在睡觉不会被下载影响。然而这样的设计会造成夜里 2 点时有大量请求发往下载服务器,每天晚上都是如此,而其他时间没有任何请求。这种情况下,每个客户端应该引入一定随机性。
其他的一些周期性过程中也需要引入随机性。回到之前说的那个重试场景下某个客户端发送了一个请求当遇到故障时1s 之后重试,接下来是 2s、4s 等。没有随机性的话,短暂的请求峰值可能会造成错误比例升高,这个周期会一直循环。为了避免这种同步性,每个延迟都需要一定的抖动,也就是加入一定的随机性。
一些典型的问题有:
- 客户端在请求失败之后,是否按指数型增加重试延时?
- 是否在自动请求中实现随机延时抖动?
其六,流程与自动化相关。虽然我们鼓励自动化,但是对于发布这件事情来说,完全自动化是灾难性的。为了保障可靠性,我们应该尽量减少发布流程中的单点故障源,包括人在内。
这些流程应该在发布之前文档化,确保在工程师还记得各种细节的时候就完全转移到文档中,这样才能在紧急情况下派上用场。流程文档应该做到能使任何一个团队成员都可以在紧急事故中处理问题。
一些典型的问题有:
- 是否已将所有需要手动执行的流程文档化?
- 是否已将构建和发布新版本的流程自动化?
其七,外部依赖相关。有时候某个发布过程依赖于某个不受公司控制的因素。尽早确认这些因素的存在可以使我们为它们的不确定性做好准备。
例如服务依赖于第三方维护的一个类库或者另外一个公司提供的服务或者数据。当第三方提供商出现故障、Bug、系统性的错误、安全问题或者未预料到的扩展性问题时尽早计划可以使我们有办法避免影响到直接用户。
一些典型的问题有:
- 这次发布依赖哪些第三方代码、数据、服务,或者事件?
- 是否有任何合作伙伴依赖于你的服务?发布时是否需要通知他们?
- 当我们或者第三方提供商无法在指定截止日期前完成工作时,会发生什么?
## 结语
今天我们探讨 “发布与升级” 的实践,如何既保证质量,又能够兼顾效率。正确的做法当然不是为了快而去忽略流程,而是在不断的发布经历中总结经验教训,把每个环节干得更快更有效率。
如果你对今天的内容有什么思考与解读,欢迎给我留言,我们一起讨论。下一讲我们聊聊 “故障域与故障预案”。
如果你觉得有所收获,也欢迎把文章分享给你的朋友。感谢你的收听,我们下期再见。