第3章 解耦应用和数据库

0 阅读7分钟

第1节 上线次日

Dukaan就这样稀里糊涂的上线了。周末过后,我们在几个小企业主的WhatsApp聊天群里分享了指向我们刚发布的MVP的网址链接。我们其实并不知道会发生什么。也许会有零星的注册,一些礼貌性的反馈,然后就慢慢被大家遗忘。

但实际发生的事情出乎意料,我们的产品瞬间爆火。

事实证明我们假设完全正确。前文所述的在WhatsApp使用PDF做生意的问题切实捕捉到了商家们的痛点,他们亟需一个更好的解决方案。而我们构建的简单无脑小工具正好符合他们的需求。网址链接被分享到一个又一个WhatsApp聊天群。几天内,我们的用户就从几十个到了几百个,然后是几千个。每个店铺商家加好商品名录之后,会把他们的mydukaan.io链接分享给各自的顾客,而那些顾客自己往往也是小企业主。这简直是完美的用户增长。

世上没有比这更美妙的感觉了。每次刷新Django的管理员后台都可以看到来自印度全国各地的新店铺被创建出来。我们正实时注视着我们的简陋滑板一步步变成现实。但激动之余,一股不安悄然滋生。

因为Dukaan应用正变得越来越慢。

刚开始可以瞬间打开的网页现在需要花好几秒的时间。管理员后台有时候甚至会卡住不动。我们也开始接收到来自用户的抱怨:“网站打不开了,” 或是 “服务器崩了吗?”。我们就如同抓狂的消防员,每隔几小时就得重启一次服务器(这种临时解决办法正在失效)。MVP的意外火爆正在压垮我们的小小后厨。

终于那一刻到来了。就是本书开头提到的半夜3点的电话。服务器彻底崩溃。

那晚是自我们MVP发布以来,第一波用户增长的巅峰。那台5美元每月承载了我们全部所托的DigitlOcean水滴服务器最终不堪重负倒下了。这其实是我们初版架构不可避免的必死终局。

第二天清晨,经历了辗转难眠的一夜之后,我和苏米特再次通话。虽然眼前大火已灭(我们再次重启了服务器),但我们知道这只是权宜之计。几小时之内服务器可能会再次崩溃。

苏米特紧张的说道:“苏巴什,反复重启也不是个事儿啊。“我们得找个更好的方案。到底是啥问题?”

过去几个小时我都在查看服务器的日志,即使眼睛感觉都要看废了也不敢放松从htop的输出数据中找出答案的努力。问题根源正浮出水面。

“应该是数据库的问题。” 我回答道,“数据库是导致崩溃的瓶颈所在。”

找到瓶颈:厨房里的鏖战

让我们再来回顾一下第1章提到的“只有一位厨师的后厨”比喻。我们的服务器空间狭小,主厨(CPU),操作台面空间(内存)以及储藏室(硬盘)全都挤在一处。

正如第1章所述,当时服务器崩溃之后,我们的分析曾关注到一个关键的细节。主厨(CPU)耗费时间最多的地方并不是在烧菜(执行应用的Python代码)。主厨(CPU)正忙着往返于储藏室之间,慌乱地到处翻找或者整理食材(写入数据到数据库或者从数据库读取数据)。

数据库操作如此频繁以至于它们几乎耗尽了原本可用于Dukaan应用其它部分的资源。服务员(Nginx)只能拿着顾客的新订单等在门口,但主厨正因为混乱的食材进出储藏室之事而忙得不可开交,甚至连看服务员的一眼的机会都没有。这就是为什么网站变得很慢,最终完全停止了响应。

为了解决这个问题,我们首先需要搞懂一个系统设计中的关键概念:互联网应用的不同组件分工确有所不同。

深入技术细节:互联网应用和数据库的负载

很明显并不是所有分工都有同等的重要性。一个互联网应用主要有两个核心任务:

  1. 应用逻辑层面的工作(厨师的操作):这大部分是需要“思考”的工作,主要由Django代码实现并由CPU负责执行。所以这项逻辑执行工作的瓶颈在于CPU:比如找到正确的产品用于展示,计算订单的总价,判定用户是否完成了登录。就类似主厨会不停忙着查看菜谱,切配食材,炒制以及试味等等。这个类型的工作需要动作麻利的主厨(一个比较高端的CPU)以及一个相对较大的操作台面空间(内存)才能比较高效地完成。
  2. 数据库层面的工作(储藏室/图书馆):这是典型的仓储和跑腿苦力活。数据库的主要职责就是从磁盘读取数据以及向磁盘写入数据。这项工作的瓶颈在于输入输出的速度。不需要太多“思考”,更倾向于物理世界的信息获取任务。可以设想一位图书馆管理员跑到书架去找某本顾客需要的书籍,或者一个储藏室经理正在往货架上摆放物品。这种类型的工作需要一个高效的储藏室(高端的SSD硬盘)以及配套的合理存取体系。

而我们面临的问题就在于我们正在强迫我们优秀的大厨(CPU)还得全职兼任图书馆管理员。好比是要一个大厨在一个繁忙喧杂的图书馆烹饪一份精致的大餐。从书架来回奔命(磁盘读写)让大厨根本无暇顾及他的本职工作:烹饪(执行代码)。这就导致两个工作都没法完成。

解决方案理论上很简单,但是实际操作上却很麻烦。

“我们需要把数据库和网站应用彻底分开。” 我告诉苏米特。“我们需要给数据库一个独立的空间。就像一个规范的图书馆总得有个专职的图书馆管理员。而且我们也得给我们的主厨弄一间独立的厨房。”

这就意味着我们从一台服务器需要扩展到两台服务器。这是我们架构上的第一个重要改进。

  • 服务器1:应用服务器。这台服务器将专为依赖CPU的任务进行优化。它将运行Nginx,Gunicorn和Django代码。它只负责进行“思考”。

  • 服务器2:数据库服务器。这台服务器将专为依赖输入输出的任务进行优化。它将运行我们的PostgreSQL数据库。它只负责“记录”。

这是当时我做出的优化方案。一次彻底的解耦。听上去符合逻辑,应该是个正确的选择。但这也意味着我们需要在保持Dukaan网站可被访问的条件下进行一次复杂的开胸外科手术。我们需要把存储着我们所有用户,所有商品以及我们创业公司所有数据的完整数据库从一台服务器迁移到另外一台服务器。

如果这个过程稍有纰漏,我们的数据将万劫不复。比如订单数据可能会丢失。我们将辜负数以千计刚刚开始尝试信任我们的卖家。这是个无比巨大的风险。