第一章是很容易被跳过的一章,因为概念较多,容易泛泛而谈。但其给出的三个概念,确实是构建系统避不开的三个重点方向。 ps. 开源中文版本[1]有些地方翻译的不是很地道,读起来可能会有些难受,不过这是所有翻译难免的。
数据系统(data system)是一种模糊的统称。在信息社会中,一切皆可信息化,或者,某种程度上来说——数字化。这些数据的采集、存储和使用,是构成信息社会的基础。我们常见的绝大部分应用背后都有一套数据系统支撑,比如微信、京东、微博等等。
数字化社会
因此,作为 IT 从业人员,有必要系统性的了解一下现代的、分布式的数据系统。学习本书,能够学习到数据系统的背后的原理、了解其常见的实践、进而将其应用到我们工作的系统设计中。
这些概念如此耳熟能详以至于我们在设计系统时拿来就用,而不用去想其实现细节,更不用从头进行实现。当然,这也侧面说明这些概念抽象的多么成功。
但这些年来,随着应用需求的进一步复杂化,出现了很多新型的数据采集、存储和处理系统,它们不拘泥于单一的功能,也难以生硬的归到某个类别。随便举几个例子:
我们面临一个新的场景,以某种组合使用这些组件时,在某种程度上,便是创立了一个新的数据系统。书中给了一个常见的对用户数据进行采集、存储、查询、旁路等操作的数据系统示例。从其示意图中可以看到各种 Web Services 的影子。
DDIA 书中一个典型的数据系统的例子
但就这么一个小系统,在设计时,就可以有很多取舍:
因此,有必要从根本上思考下如何评价一个好数据系统,如何构建一个好的数据系统,有哪些可以遵循的设计模式?有哪些通常需要考虑的方面?
书中用了三个词来回答:可靠性(Reliability)、可伸缩性(Scalability)、可维护性(Maintainability)
如何衡量可靠性?
可用性也是可靠性的一个侧面,云服务通常以多少个 9 来衡量可用性。
两个易混淆的概念:Fault(系统出现问题) and Failure(系统不能提供服务)
不能进行 Fault-tolerance 的系统,积累的 fault 多了,就很容易 Failure。
如何预防?混沌测试:如 Netflix 的 chaosmonkey[2]。
在一个大型数据中心中,这是常态:
数据系统中常见的需要考虑的硬件指标:
解决办法,增加冗余度:
机房多路供电,双网络等等。
对于数据:
单机:可以做RAID 冗余。如:EC 编码。
多机:多副本 or EC 编码。
相比硬件故障的随机性,软件错误的相关性更高:
在设计软件时,我们通常有一些环境假设,和一些隐性约束。随着时间的推移、系统的持续运行,如果这些假设不能够继续被满足;如果这些约束被后面维护者增加功能时所破坏;都有可能让一开始正常运行的系统,突然崩溃。
系统中最不稳定的是人,因此要在设计层面尽可能消除人对系统影响。依据软件的生命周期,分几个阶段来考虑:
事关用户数据安全,事关企业声誉,企业存活和做大的基石。
可伸缩性,即系统应对负载增长的能力。它很重要,但在实践中又很难做好,因为存在一个基本矛盾:只有能存活下来的产品才有资格谈伸缩,而过早为伸缩设计往往活不下去。
但仍是可以了解一些基本的概念,来应对可能会暴增的负载。
应对负载之前,要先找到合适的方法来衡量负载,如负载参数(load parameters):
书中以 Twitter 2012年11 披露的信息为例进行了说明:
Twitter 数据库表
单就这个数据量级来说,无论怎么设计都问题不大。但 Twitter 需要根据用户之间的关注与被关注关系来对数据进行多次处理。常见的有推拉两种方式:
推到关注者各个 Feed 流
前者是 Lazy 的,用户只有查看时才会去拉取,不会有无效计算和请求,但每次需要现算,呈现速度较慢。而且流量一大也扛不住。
后者实现算出视图,而不管用户看不看,呈现速度较快,但会引入很多无效请求。
最终,使用的是一种推拉结合的方式,这也是外国一道经典的系统设计考题。
注意和系统负载区分,系统负载是从用户视角来审视系统,是一种客观指标。而系统性能则是描述的系统的一种实际能力。比如:
响应时间通常以百分位点来衡量,比如 p95,p99和 p999,它们意味着95%,99%或 99.9% 的请求都能在该阈值内完成。在实际中,通常使用滑动窗口滚动计算最近一段时间的响应时间分布,并通常以折线图或者柱状图进行呈现。
在有了描述和定义负载、性能的手段之后,终于来到正题,如何应对负载的不断增长,即使系统具有可伸缩性。
负载伸缩的两种方式:
针对不同应用场景:
首先,如果规模很小,尽量还是用性能好一点的机器,可以省去很多麻烦。
其次,可以上云,利用云的可伸缩性。甚至如 Snowflake 等基础服务提供商也是 All In 云原生。
最后,实在不行再考虑自行设计可伸缩的分布式架构。
两种服务类型:
不可能啥都要,没有万金油架构! 但同时:万变不离其宗,组成不同架构的原子设计模式是有限的,这也是本书稍后要论述的重点。
从软件的整个生命周期来看,维护阶段绝对占大头。
但大部分人都喜欢挖坑,不喜欢填坑。因此有必要,在刚开就把坑开的足够好。有三个原则:
有效的运维绝对是个高技术活:
系统具有良好的可维护性,意味着将可定义的维护过程编写文档和工具以自动化,从而解放出人力关注更高价值事情:
软件设计哲学——复杂度的管理
推荐一本书:A Philosophy of Software Design[3], 讲述在软件设计中如何定义、识别和降低复杂度。
复杂度表现:
需求很简单,但不妨碍你实现的很复杂 😉:过多的引入了额外复杂度(accidental complexity ) ——非问题本身决定的,而由实现所引入的复杂度。
通常是问题理解的不够本质,写出了“流水账”(没有任何抽象,abstraction)式的代码。
如果你为一个问题找到了合适的抽象,那么问题就解决了一半,如:
如何找到合适的抽象?
总之,一个合适的抽象,要么是符合直觉的;要么是和你的读者共享上下文的。
本书之后也会给出很多分布式系统中常用的抽象。
系统需求没有变化,说明这个行业死了。
否则,需求一定是不断在变,引起变化的原因多种多样:
应对之道:
[1]中文开源版本: http://ddia.vonng.com/
[2]chaosmonkey: https://netflix.github.io/chaosmonkey/
[3]豆瓣 A Philosophy of Software Design: https://book.douban.com/subject/30218046/
[4]聊聊代码命名: https://www.qtmuniao.com/2021/12/12/how-to-write-code-scrutinize-names/