关于后端:对软件系统的一些理解

3次阅读

共计 4805 个字符,预计需要花费 13 分钟才能阅读完成。

简介:总了了一些集体对软件构建过程的一些了解
前言
这篇文章是想表白我对系统软件的一些了解,格调跟之前的不太一样,整体偏“务实”。我本人其实是不太善于“务实”的,甚至是有点排挤。就跟相比起看论文,我更喜爱看 code,当然我也看论文,只不过相对来说少些。

毕业以来始终在数据库存储引擎畛域工作,过来 5 年次要精力集中在阿里自研 LSM-Tree 存储引擎 X -Engine 研发上,并且在过来两年多工夫咱们实现了 X -Engine 的云原生架构降级和商业化,在私有云上承接肯定规模的客户并稳固运行,在业界应该也是首个基于 LSM-Tree 架构实现云原生能力的 TP 存储引擎。残缺经验一个 TP 存储引擎的架构布局、设计研发、落地上线,稳定性运维的全周期,并且得益于从我进入数据库畛域一路以来经验的高水平团队、technology leader 以及整个团队成员的杰出工程能力和技术视线,加上我本人在此过程中的一些思考,阶段性的造成了一些本人的心得体会。

另外,跟业界一些优良的架构师和工程师交换,发现对于系统工程的了解有很多的共鸣,也收到很多十分有价值的输出,当然也存在一些不同的观点。这也是促使我写这篇文章的次要起因,心愿能将我本人的一些了解表白分明,这些观点并不 fashion,更谈不上翻新,更多的是一些本人的思考和经验之谈。

对系统软件的认识
观点 1:软件的实质是对硬件资源的耗费。不同软件的区别在于,耗费硬件资源去解决什么问题以及如何调配硬件资源的耗费。软件架构设计中常常提到 ” 形象 ” 和“trade-off”,形象实质上的就是 ” 解决什么问题 ”,”trade-off” 其实就是 ” 如何调配硬件资源 ”。

举个例子 TP 存储引擎和 AP 存储引擎,从实现上能够列举出一大堆不同的中央,行存 VS 列存、二级索引 VS ZoneMap 索引、强事务 VS 弱事务等等。这些不同之处其实都是后果,导致这些的根本原因是:

1)两者解决的问题不同,TP 场景次要是 online 实时业务,这些业务的特色是整体数据规模绝对较小(真正须要 online 解决的数据,历史数据可能很多)、申请短平快、数据 locality 显著、高并发低时延等,而 AP 场景整体的数据规模大、计算密度高、高吞吐等。(解决什么问题)

2)TP 引擎的残缺事务反对使得业务的并发管制简化很多,其实就是把业务零碎原本须要做的事件,TP 引擎本人做了,当然也就意味着 TP 引擎须要为此耗费一部分硬件资源。而 AP 引擎为了放慢数据入库的速度,事务的反对比拟弱,这部分工作还是由业务零碎来实现(比方 ETL),也就不须要为此耗费硬件资源。(如何调配硬件资源)

观点 2: 系统软件的重大改革,背地根本都是硬件倒退所推动的。这跟观点 1)是相响应的,系统软件畛域的实践在进入 21 世纪之前,学术界曾经做了宽泛深刻的钻研。从最开始计算机的呈现,到大型机和小型机,再到家庭 PC 和便宜通用服务器,以及当初的云计算 IAAS 服务,基本上系统软件倒退也是追随这个脉络在倒退。系统软件的再次炽热,实质上也是因为 IAAS 这个“新硬件”所推动的。整个 IAAS 的 on-demand 获取,突破了系统软件之前在物理资源受限的背景下很多设计,这也就是为什么云原生系统软件会迎来新的机会。

观点 3: 简直不存在某一种零碎架构全面当先另外一种架构。这跟观点 1)2)是相响应的,不同的架构抉择背地都是不同的 trade-off,所谓有得必有舍。常常听到一些说法,你看这篇论文、这篇文章,他们这种架构就没有某问题,咱们这种架构就有这个问题。我听到这些观点的第一反馈是质疑,这里边次要有三个起因:1)很多论文和文章的试验后果是没法复现的,也就说很有可能他的论断就有问题;2)很多时候只会强调“得”的局部,而“舍”的局部是没有讲的。3)咱们零碎所存在的问题到底影响有多大,是不是能够解决的,这些须要量化的数据能力确定。轻易地被各种论文和文章的论断影响,很有可能会做出一个不三不四的零碎。就像习武之人各个门派的文治都学学,最终很容易走火入魔。

观点 4: 条条大路通罗马,最终零碎对外出现的区别,更多的是工程实现的起因,而非架构的起因。不同的零碎架构须要解决的大部分问题实质上其实是一样的,并且组成一个零碎的零部件都差不多,只是依据须要抉择哪些零部件来构建零碎。只有躬身入局,真正地去面对问题、剖析问题、解决问题,能力认分明其中的实质,否则很容易变成夸夸其谈。

举个例子:常常有人问我 LSM-Tree 架构中继续写入数据时,compaction 问题对性能影响很大。这个问题我是这么看的,首先 LSM-Tree 架构上写入吞入劣势的其中一个起因是,相比于 innodb 这种磁盘 B + Tree 在写入的时候间接 sort on write(page 内有序,全局有序),LSM-Tree 架构抉择将一部分 sort 转移到 sort on compaction、sort on read,实质上是将写入时排序的资源耗费,转移到了 compaction 或 read。刷脏其实是蕴含两个动作:生成脏页,将脏页刷盘。innodb 相当于是在写入的时候生成脏页,在刷脏的时候就是单纯的 io 操作。而 compaction 其实是同时做了生成“脏页”和“脏页”刷盘。innodb 如果继续写入的话,也会有刷脏来不及时导致影响写入性能的问题。因为 innodb 刷脏和 compaction 之所以成为问题,实质上都是因为内存和磁盘写入速度的差别,导致生产者消费者模型失衡。所以 innodb 的刷脏和 LSM-Tree 的 compaction 实质上是雷同的问题,只是通过不同的办法来将这个过程对系统的影响降到最低。

系统软件构建的七个面向
接下来的内容,次要是在进行具体设计的时候我认为比拟重要的准则。这些准则的情理其实很容易了解,并且“软件工程”这门学科曾经钻研的很充沛,然而实际操作的时候其实是蛮艰难的,可能是历史包袱的起因,也有可能是外界环境的起因,须要依据理论状况做出不同的 trade-off。值得注意的是,咱们做出的 trade-off 肯定是要通过认真思考的,而不是粗率的,否则很容易呈现“有舍没有得”。另外恪守这些准则设计实现进去的零碎和不齐全恪守这些准则设计实现进去的零碎,后果其实是“好和更好的区别”,然而“好多少”这个量在零碎做进去之前,其实很难掂量。这七个准则不是独立存在的,而是相辅相成的。

面向场景: 首先咱们须要明确要解决什么问题,这是整个零碎构建的出发点。one size fit all 的零碎在过来是不存在的,在将来也不肯定存在。零碎的欠缺,必然是要靠一直的迭代来实现的,那么如何迭代实质上就是咱们在那些阶段解决哪些问题。一个零碎能够有远大的指标去解决很多问题,然而所有问题的路标须要有绝对清晰的布局,以达到既能够疾速满足需要,同时保留向将来演进和扩大的根底。

理论研发过程中,可能产生的两类谬误是:1)想采纳麻利开发的形式来进行工程治理,以满足整个迭代的需要。麻利开发实质上先定义最小功能集,也就是首先想分明解决什么问题,而后疾速的迭代裁减性能,有点像小步快走。在实操上,很容易把麻利开发搞成了 ” 快、糙、猛 ”,有点大干 30 天赶英超美的滋味。2)问题定义不分明,零碎的“不变式”设置就容易粗率。每个零碎都有一些“不变式”,随后很多设计都是基于这些不变式进行开展的,比方在 LSM-Tree 零碎中一个常见的“不变式”是更新版本的数据在更低的档次,同一行的数据的多个版本如果同时在 memtable、level0、level1 中存在,那么必然 memtable 中对应的版本是最新的,level0 中的版本也比 level1 中的更新。如果在迭代的过程中发现之前设置的“不变式”不合理的,那么进行改变的代价是十分之大的。

面向解耦:无论是自上而下的去设计零碎,还是自下而上的去设计零碎,很重要的一个思考逻辑就是将各个模块间的耦合度降到最低。解耦做地比拟好的零碎,往往意味着:1)每个模块的性能是思考的比较清楚,计划的残缺度是比拟高的;2)有利于专一的将某个模块实现的更加高效,防止其余模块的影响;3)有利于之后的迭代,影响面可控;4)出了问题好排查,单个模块的问题是比拟好排查,真正那些难搞的问题往往是问题在各个模块间传导后才裸露进去,比方 A 模块出问题,通过模块 B、C、D,最初在模块 E 裸露进去。

有些质疑的观点会说,面向解耦的思路去设计,有可能会就义零碎的整体性能。其实这个跟不要一开始就为性能做适度的设计是一样的情理,真到了某些解耦的设计影响了性能,那么该耦合的就去耦合。把两个模块耦合在一起的难度往往是低于把耦合在一起的两个模块拆开。

面向进攻:这个就是防御性编程的逻辑,要假如调用的函数都是有可能出错的,,比方内存调配可能出错,io 可能出错,根底库的调用可能出错等等,基于此来思考如果出错,零碎的行为是什么。有一个非常简单的准则就是 ”fail stop”, 如果没有残缺的进攻,那么即便 fail 了也很难立刻 stop,最终造成一些很奇怪的表象。

通常的质疑是:1)你看这个函数的逻辑必定不会失败的。兴许从以后来看这个函数的确不会失败,然而很难保障随着迭代减少逻辑,之后没有失败的可能性。2)加了这么多进攻,进攻代码比理论逻辑的代码还多,会影响性能。首先,当初 cpu 的分支预测能力,基本上能够做到绝大部分状况下进攻代码不会影响性能。另外跟对于面向耦合的质疑一样,真到某些进攻代码成为了性能瓶颈,该优化就优化。优化一个进攻,总比去解决一个因为没有进攻而导致的问题代价更低吧。

面向测试:在测试阶段修复问题的代价是远低于在生产环境修复问题的代价,因而让零碎变得可测试是十分重要的。零碎可测试的规范就是,能不便的进行单元测试、集成测试,并笼罩绝大部分的代码门路。可测试的零碎,随着一直的迭代,会累积越来越多的测试 case,一直的夯实稳定性根底。面向测试跟面向解耦、面向进攻是相辅相成的。只有模块间耦合度足够的低,才有可能做更多的测试,否则做一个模块的测试须要 mock 很多乌七八糟的货色。面向进攻会使得测试的行为能够更好的预期,不然输出了一个异样的参数,具体怎么失败是不确定的,那测试 case 就很难写了。

面向运维:bug 是肯定会有的,对于简单的零碎,不论后期做多少筹备都很难防止生产环境中遇到未知的问题。面向运维的次要目标是,遇到问题的时候,能用代价最低的伎俩去及时止损。遇到线上问题,动静调参数就能解决比须要重启能力解决的代价更低,重启能解决比须要发版能力解决的代价更低。面向运维不仅仅是加几个参数,加几个开关那么简略,而是须要把“面向运维”作为设计方案的重要组成部分来思考,保障出了问题有运维伎俩,有运维伎俩敢用,用了当前有成果。

面向问题实质:当去解决一个问题的时候,肯定要多思考这个问题的实质起因是什么,简略的问题复杂化和简单的问题简单化,都是因为没有抓住实质。如果能思考分明其背地的实质起因,从源头防止掉是更加彻底的解决形式,否则很容易陷入一直打补丁的状态,我始终有个观点:“没有抓住问题实质去解决问题,后果往往是在制作问题”。另外一个教训是,如果一个模块间断出了好几次问题,那么就要想想是不是在最开始的设计上就有须要改良的中央。

面向可视化:可视化的指标次要是以更加直观的模式,来展示零碎运行状况,这对于零碎调优和诊断是十分重要的。当零碎异样时,可视化的形式能够帮忙疾速定位到零碎哪里出了问题。另外一方面是,能够提供接口给监控零碎做历史状态的追踪。比方 oracle 的诊断监控就是一个十分优良的案例,而 SnowFlake 对于外部状态的打点监控也是近乎疯狂。

总结
说了这么多,最终零碎还是靠一行行的 code 实现进去的,放弃匠心、谨严、较真的态度去打造零碎是十分奢侈正确,但又很难做到的事件,共勉!

原文链接:http://click.aliyun.com/m/100…

本文为阿里云原创内容,未经容许不得转载。

正文完
 0