通俗解释「为什么数据库难以拓展」
英文原文: Why is it hard to scale a database, in layman’s terms?
Paul King , Facebook 数据科学家(3.6K 赞)
要扩展数据库有四大挑战:搜索、并发性、一致性和速度。
假设你有一张清单,上面有10个人名。如果你想要查找某个人,只需要看一眼清单就行。
但如果清单上有100万个人名呢?这时,你就需要一些策略了。电话簿把人名按照字母顺序排列,这样你就可以略过不需要的部分了。这就是针对 搜索问题的一种解决方案。
如果有100万人在同时使用这本电话簿呢?这就是 并发性的问题。要么大家在市政厅排长队等待使用电话簿,要么把电话簿复印100万份——“ 主从复制”策略。如果你把这100万份复印件放到每个人的家里——“ 分布式”策略——你同样可以得到快速响应。
如果有人电话号码更换了呢?主从复制策略造成了一个问题:现在必须对100万本电话簿作出更改。而且它们还在被使用呢,何时才能进行更改呢?如果一本一本的更改,就可能造成数据 一致性问题。如果全部回收并印发新的,就可能造成 可用性问题。
如果每小时都有成百上千的人更换他们的电话号码呢?这时你就面临由于“ 资源争用”引起的严重的信息堵塞,这种堵塞还会导致“ 竞态条件”(不可预知的输出)和“ 死锁”(数据库的僵局)。
以上所有问题都有解决方案,但这些解决方案可能会非常复杂。比如,可以通过发放电话簿的附录(称为“ 修改日志”)而不是重新打印它们,不过你得时时刻刻检查你的附录。你可以按照修改日期发布电话簿的新版本,这样人们可以同一时间交换它们以获得更高的一致性,不过这样的话,电话簿就总会稍微有一点过时。
现在,把规模扩展到有上百万的用户使用,数十亿条数据分布式的存储在世界各地的数据中心中。
数据库基本目标是维护一种假象,即它只有一份拷贝,同一时间只有一个人在修改它,所有人看到的都是最新的数据,并且能立即响应。当数据库扩展到全球有上百万的人使用和修改上以万亿计的数据时,这个目标就无法达到了。
因此数据库设计的任务是使用环环相扣的算法技巧以尽可能的接近这一假象。
黄易山,Reddit 前 CEO,(2k 赞)
这里有一个专门针对门外汉,例如完全不懂数据库的非技术人员的解释。
(对于懂数据库的人,请自行忽略这句话以及接下来这个类比中的一些小技术错误。)
“扩展”在很多方面都是很难的,但是首先我想要从根本上讲为什么扩展很难——原因是“扩展”并不是一个单一方向的活动。总的来说,它的本质是把一个复杂的系统变得“更好”——通常是更大或更多,而且通常很快就不得不这么做。关键在于一个复杂的系统不能通过一个简单的方法就能变得更大或更多产或更高效——通常系统的各个方面相互影响,所以如果你想要拓展某一部分,那么其它部分通常会失效,从而你无法得到想要的扩展——你几乎总要做一些重构
打个比方:
把数据库想象成一个图书馆。你在那里存放书籍或者丛书(比如全套的哈利波特系列丛书)。特别的,你的web应用也是一个图书馆,它存放图书,方便的提供人们阅读。想象一下,这个图书馆出现在了TechCrunch上,变得非常受欢迎,如此一来你就突然面临了一系列的扩张问题。让我们列举几个问题,用简单的术语进行解释:
例一:很多、很多书
你的图书馆越来越受欢迎。因此,你比当初开始建立图书馆的时候多了很多书,很长一段时间里,你只是把它们放在房间的新书架上。但是目前的房子里无法放下全部的书了。它们已经超出了你的小图书馆可以承载的规模。你必须购买或者租赁相邻的建筑物,把书放在里面。这可能会带来一些问题,因为你附近的建筑物是有限的,或者房地产价格非常高以至于无法持续的租赁隔壁那个很贵的房子。因此你必须认真思考租赁哪个房子,如何找到那些距离很近又很合算的用于存放书籍的房子。
这是真实的模拟,数据库通常存储在硬盘上,硬盘存放在有限的空间里,你只能在一台计算机(一个数据中心,一个机架)中存放许多的硬盘。为了抗衡这个普遍的问题数据中心设计的很大,但是如果你是一个非常大的图书馆,你可能还是会遇到这种限制,数据中心的机架无法承受如此之多的硬盘,甚至你需要废弃掉这个数据中心并建立一个新的(这种情况很少见)。尽管如此,问题的核心是不管你最开始有多少空间,你总是需要扩展它,并且你没法一直线性的在空间中增加“单元”(好比在图书室里增加书架),你终会需要做出跨越性的改变,例如租赁隔壁的房子或者租赁又一个机架或者建立又一个数据中心。
例二:在书的海洋里找书
当你的图书馆只占用一个房间的时候,你只需要把所有的书按照字母顺序排列放好就行。如果有人想要某本书,他只需要在房间里顺序查找到他想要的那本就好。这大约需要花费30分钟。
现在你的图书馆很大,你租赁了很多房子。如果有人想要看某本书的话,他可能需要走遍所有的房子。人们根本不能接受找一本书需要花费这么多的时间(和人们承受网页的加载时间类似)。人们只想要直接走到正确的房子里,走到正确的楼层,走到正确的那个书架,直接拿到那本想要的书。他们根本不愿意花费超过半小时的时间。
为了实现这一目标,你得创建一个新的关联系统,叫做 索引。现实中的图书馆确实遇到了这个问题,他们使用的解决方案是卡片目录。如下图所示:
年轻一代可能不知道这是什么,因为这是计算机时代之前的产物。卡片目录简直就是个使用小抽屉和小纸片(卡片)的数据库。因为它们太过笨重,所以我们把它们数字化并放进了计算机中。如果你小于25岁的话,你可能根本没见过它们。
卡片目录(即索引)为每本书建造了一个卡片,把卡片放进抽屉,按照标题、作者、主题等排序。如果你想要查找某本书,就直接使用卡片目录——放在单独的一个房间里——查找你要的书的卡片,卡片中会有这本书的具体所在位置。所以你只需花费10分钟来检索卡片,10分钟走到正确的房子,5分钟走到确切的楼层和书架前,再花5分钟找到确切的书。
如果一个图书馆变得太大了,它就需要引进卡片目录,使得查找一本书需要的时间保持在合理的范围内(例如半小时),否则就需要花好几天来搜索遍所有房子以找到一本书,这样人们就不会使用这个图书馆了。这和仅仅只是租赁更多的房子并全部搜索有本质上的不同——这个例子说明当你在扩展中遇到门槛时,必须要想出一个新的解决方案以克服问题——不仅仅是增加书架,你还必须整理书目,打印出所有的卡片(这很困难,因为你必须遍历所有的书,把他们按照作者、标题和主题等排序,这很痛苦,因为你的书堆满了好几个房子),然后你得在图书馆靠门口的位置空出一个特殊的房间用于存放你的卡片目录,告诉每个人先检查卡片。
例三:很多人同时查找同一本书
让我们举一个超级简单的例子来说明这个问题:你的图书馆现在很受欢迎,每天有成千上万的人同时光临。人山人海,摩肩接踵。这可不是听起来那么荒谬——这是当一个web应用突然火起来时遇到的最普遍的问题。
现在有好多人想要看同一本书以至于他们卡住了大门。这听起来很荒谬,因为现实中这很少发生。但是想想这个——现实中的大门可以允许一秒钟通过一个人。因此如果每秒钟有20个人想要进入你的图书馆, 那么很快门口会排起长队。越来越多的人到来,队伍越来越长。最终,门口排队的人数会超过在图书馆中的人数,大量等待的人群口口相传他们只能一直在门口等待而永远也无法读到一本书。看不到书而恨你的人比看到书而得到满足的人多的多,坏口碑就产生了。
一个显而易见的解决方案是在墙上开更多的门。你现在又开了一道门,两倍的人涌入了!你开了更多的门,最终有上百个门,每面墙都挤满了门。嘿,你只需移除所有的墙!这样,更多的人可以使用图书馆了!数量级的激增!
但很快你就会遇到另一个问题,每本书所在的书架前只有有限的空间,只能给有限的人站立,也许他们速度可以很快——他们浏览书架找到想要的书然后就离开。但是他们依然需要在书架前站立几秒钟,但最终因为你的图书馆太受欢迎,成百上千的人都在查找同一本书(或者被查找的两本书放在同一个垂直空间),他们没法都挤在书架前的一小块空地里。
再一次,书架前排起了长队——也许是所有的书架,也许只是某一个放着畅销书的书架。以下是一些可能的解决方案:
如果人群只是聚集在畅销书架附近,那你只需要把畅销书分散放在图书馆即可。但这样,书本就不再是按序摆放的了,它们变得随机分布,所以你就需要重构你的卡片目录以使得人们可以快速找到它们。这不是那么痛苦——因为你仅仅只需要更新所有的畅销书的卡片而已。
如果到处都人挤人,例如,所有的书都很受欢迎,或者仅仅是人太多,你可以尝试增加副本。也就是说,复制你的整个图书馆然后在城市的另一边(或者下一个街区)租赁一些新的房子,把一般的人迁移到新图书馆去。你可以这样重复做几次,增加一些副本。这样做的话需要确保备份都是最新的,你必须确保所有的新书在多个图书馆都有最新的备份。有一个解决方案是把其中一个图书馆称为“主”图书馆,所有的新书都只进入这一个图书馆,每次有新书进入时,你得派人复印这些书并排一个快递员把这些备份送到其它图书馆中。这些快递员也需要占用交通资源,这又限制了可以使用你的图书馆的人数,因此你需要限制每个图书馆的接待人数,当人数过多的时候,你就要再新建一个图书馆了。
总结如下,最开始的时候你想要扩展你的图书馆以应对大量的人群,你仅仅只是增加新的门,通过人数瞬间翻倍。你可以再开一扇门再次增加通过人数。在一段时间里,你都可以通过增加新的门来扩展,直到你在所有的墙上都开满了门——即你移除了所有的墙。在此之后你还想继续扩展(记住,来你图书馆的人永不停息的在增长),你就得想一个全新的解决方案了,比如建立几个备份图书馆。这样做造成很多影响——你得复印你所有的书,租赁新的房子,然后还得想出一个合理分流的方案使得每个图书馆的人数都很合理。所有的这些都是新的基础设施,你没法等到你意识到需要开辟足够多的门的时候再做这些,因为建设它们的时候访问人数依然在不断增加,等待的人群会很不满意。所以你得根据访问量的增长速率来预测当你开辟新门策略可能无效的时候就早早开始增加图书馆的数量。
例四:增加很多、很多新书
所有的图书馆都得与时俱进,也就意味着得不停的增加新书。让我们假设你有一个很活跃的图书馆,每时每刻都有成百上千的新书增加进来。
如此一来,你得安排人购买新书,在主图书馆复印它们,把这些书分散放到书架上。你有一个超级通畅的交通和足够数量的图书馆们,即使快递员们在各个图书馆之间游走送达新书,也不会影响你的图书馆的访问速度。太棒了,一切看起来都很赞,你决定今天终于可以休个假了。
好的,你图书馆中所有的书都是排好序的,也就是说他们不是随机的放在书架上,而是按照作者或者标题或者别的什么排序摆放(在现实中,是按照杜威十进分类法,当你在小学第一次接触到它的时候肯定觉得很荒谬,但一旦你明白了这些数据库问题之后,很奇怪的,你就能明白它其实是很有意义的)无论如何,书本按序排放,当有人通过卡片目录找到某本书在某个书架上的时候,他不需要浏览整个书架来找到那本书——书架上的每本书都是按序排放的,所以可以查阅书架中间的书,选取他的左侧或者右侧继续查阅,以此递归找到特定的书。无论何时,所有的书都得排好序让使用者可以照此方法找书。
书架上的书按需排列,书架也走摆的满满的。也就意味着当快递员想要把新书放进书架的时候,需要把排在末尾的书移到下一个书架上,下一个书架上的末尾的书移到又一个书架上,以此往复。最终,你得重新设计书架,使其有一些空隙可以放书,而不再需要把书移到另一个书架,这个过程是很恼人的,也很花费时间,更重要的是,当你重新设计书架排放的时候,你不希望有其他的使用者或者快递员从这些书架上拿书或者放书。所以你得“锁住”这些书架以及周围的所有书架,以防止你需要把书挪到别的书架上去,别的书架上的书又得挪到又一个别的书架上去等等。
这种锁定造成了巨大的交通问题。问题从找书的时候需要等待其他站立在书架前的人离开变成了大量的人群需要等待在书架区的外围,等待快递员插入新书以及移动书籍的位置。如果快递员很多,那这个问题就会经常发生。大量的人群需要等待某一个快递员完成操作,更糟的是,一个快递员可能需要等待另一个快递员。在现实中,新书并不常常增加,但如果你运行一个网站有很多人同时上传很多东西,这就等同于每时每刻都有有很多快递员给图书馆快递新书,你就会遇到上述的问题。
同时,如果快递员动作不够快,例如没法把所有的备份同时送到所有的图书馆,那么有些人不能找到某本书但是其他人可以,他们就会得到相互矛盾的信息。
该问题的解决方案就给读者留作练习吧。
要记住,解决该问题就得帮快递员们想出一个全新的解决方案(也许可以将临近的书放在同一批次一起更新,这样一次就可以在一个书架上更新很多书了),或者改变图书馆的布局方式(也许你可以为所有的书周围都留下空位;但如果这些空位又被填满了呢?)——总而言之,我们可以得到如下的教训:如果你的快递员数量不多,你每天新增的书本数量也不多,那这就不是什么大问题,但如果你超过一个阀值(比如多如潮水的快递员和新书)你就得改变你现在的整个解决方案和物理布局,而这往往需要想象力和创造力。
这就是为什么数据库难以扩展的原因。
事实上,这也是为什么扩展任何一个稍许复杂的系统(比如web应用,它包含了数据库和其他服务器及其交互)很难的原因。参考一所完整的研究性大学吧,它包含了实验室、教室和宿舍等等,图书馆及其用途只是整个大学的组成之一。这些组成部分的任何一个的使用量的快速增长都会引发扩展问题。为了克服这种问题,你通常都得重构整个动态底层协议和重排操作协议(即你代码里的算法)或者你的资源布局。实现这些总是需要创造性的努力并且不同系统的底层细节各不相同,因此,只有一些通用的解决方案——上文提到了其中一些——你常常得调整这些通用解决方案以适应你的情况:也许你很难租到房子用来当做复制图书馆;也许你的书都是大百科全书,快递员没法一次快递很多本书;也许你那儿环境太冷,你没法开很多扇门,否则顾客就冻死了。适用于这个环境的解决方案没法完全适用于那个环境,所以你每次都不得不调整解决方案以适应眼前的环境,敏锐的观察力是帮助你发现问题并找到最适宜该问题的解决方案的必备品。
最后要记住,当你重新设计解决方案的时候,会有越来越多的顾客敲你的门,冲你叫嚷,因为队伍太长他们没法看到想看的书了!
哦,对了。你的图书馆可能会让你的钱包边的空空,让你的心流血,因为你要购买足够多的书架,租赁足够多的房子,那你怎么样才能开始赚钱呢?所有的风险投资者都不想资助你,因为你居然想让顾客免费借书?你居然不愿意插页内广告?
谢谢欣赏!
本文由 伯乐在线 - 顾星竹 翻译, sunbiaobiao 校稿。