V2EX rayeaster
 rayeaster 最近的时间轴更新
rayeaster

rayeaster

V2EX 第 74493 号会员,加入于 2014-09-19 22:00:26 +08:00
今日活跃度排名 4044
rayeaster 最近回复了
4 小时 42 分钟前
回复了 rayeaster 创建的主题 阅读 看到本有趣的书 准备过年的时候读一读
第 3 章 解耦应用和数据库

第 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 )只能拿着顾客的新订单等在门口,但主厨正因为混乱的食材进出储藏室之事而忙得不可开交,甚至连看服务员的一眼的机会都没有。这就是为什么网站变得很慢,最终完全停止了响应。
为了解决这个问题,我们首先需要搞懂一个系统设计中的关键概念:互联网应用的不同组件分工确有所不同。
深入技术细节:互联网应用和数据库的负载
很明显并不是所有分工都有同等的重要性。一个互联网应用主要有两个核心任务:

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

而我们面临的问题就在于我们正在强迫我们优秀的大厨( CPU )还得全职兼任图书馆管理员。好比是要一个大厨在一个繁忙喧杂的图书馆烹饪一份精致的大餐。从书架来回奔命(磁盘读写)让大厨根本无暇顾及他的本职工作:烹饪(执行代码)。这就导致两个工作都没法完成。
解决方案理论上很简单,但是实际操作上却很麻烦。
“我们需要把数据库和网站应用彻底分开。” 我告诉苏米特。“我们需要给数据库一个独立的空间。就像一个规范的图书馆总得有个专职的图书馆管理员。而且我们也得给我们的主厨弄一间独立的厨房。”
这就意味着我们从一台服务器需要扩展到两台服务器。这是我们架构上的第一个重要改进。


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


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


这是当时我做出的优化方案。一次彻底的解耦。听上去符合逻辑,应该是个正确的选择。但这也意味着我们需要在保持 Dukaan 网站可被访问的条件下进行一次复杂的开胸外科手术。我们需要把存储着我们所有用户,所有商品以及我们创业公司所有数据的完整数据库从一台服务器迁移到另外一台服务器。
如果这个过程稍有纰漏,我们的数据将万劫不复。比如订单数据可能会丢失。我们将辜负数以千计刚刚开始尝试信任我们的卖家。这是个无比巨大的风险。
8 小时 3 分钟前
回复了 rayeaster 创建的主题 阅读 看到本有趣的书 准备过年的时候读一读
第 2 章第 3 节 打好地基

理论部分的讨论随着我们技术栈的选型敲定可以暂告一段落。现在该花点时间去实作了。我们需要为代码在互联网运行找一个落脚点,也就是说我们需要一台服务器。
如果说我们编写的代码是设计图纸,我们选择的技术栈是建筑材料,那服务器就是我们的建筑工地。在盖房的例子中,就是我们浇筑地基之所在,也是世人最终看到我们所建房子的地方。
在创业早期选择服务器提供商事关成本,复杂度和功能之间的平衡。我们在一开始并不需要类似亚马逊云( Amazon Web Service )或者歌云( Google Cloud )这样顶级大厂的服务。这就好像买下了一整个工业园区的土地但是只为了盖一栋居所。这对于我们的 MVP 需求而言过于复杂也过于昂贵。我们只需要一块性价比相对更合适的居住用地。
基于此,我们最终选择了 DigitalOcean 这家云服务商。
深入技术细节:配置第一台服务器
DigitalOcean 成为众多开发者青睐选择的原因很直接:足够便宜。DigitalOcean 将其提供的服务器命名为“水滴”( Droplets ),听上去很接地气,不会让人产生闻之却步的惧怕。
DigitalOceam 水滴的简明指南
拿到属于我们的第一台 DigitalOcean 服务器只花了不到 5 分钟,对,就是仅有 512MB 内存且后来导致很多尴尬事故的那台服务器。整个流程非常简单:


创建一个 DigitalOcean 账号:就是很标准的注册步骤。


创建一个水滴服务器:这就是我们施展的舞台。创建时你会看到一个简洁干净的引导页面。


选择一个操作系统镜像:“镜像”( Image )是预先构建好的软件模版,通常用于服务器的操作系统以及相关基础软件。我们选择了标准的 Ubuntu 镜像,这是一个 Linux 的发行版,也是互联网应用服务器端的主流选择。Ubuntu 拥有强大的社区,安全且免费。我们选择是长期支持版本( Long Term Support ),这样可以在若干年内都得到安全方面的更新。


选择付费套餐:这决定了服务器的性能(也就是盖房子的地块大小)。我们直接拉到页面最底部选择了最便宜的那一款:512MB 内存,1 个虚拟 CPU ,20GB 的 SSD 硬盘。每个月只需要 5 美元。对于一个零用户零收入的 MVP 项目而言,这应该是一个理性的选择。狗窝虽小,但也是属于我们自己的狗窝。


选择服务器区域:这决定了服务器在物理层面机房所属的地点。我们选择了班加罗尔。原因?因为我们知道首批用户肯定来自印度。就近选择服务器会让应用访问更快延时更小(因为数据传输耗时更短)。


最后点击“创建水滴”按钮。


这几步做完之后,我们等待了大概 1 分钟,DigitalOceam 就把我们的服务器准备好了。我们现在骄傲的成为了一个公网 IP 地址(也就是我们服务器在互联网的专属地址)的主人。我们准备用来盖房的小小地块已经准备就绪。
SSH:进入服务器的钥匙
有了土地,我们需要能够进入这个地块开始盖房子。不能只靠普通的网页浏览器管理服务器,还需要一个特殊的工具 SSH ( Secure Shell )。
可以把你的服务器想象成一个远处密闭无窗的建筑。SSH 就是你加密过的魔法钥匙。你将使用类似 ssh [email protected] 这样的终端命令打开房门进入建筑。一旦 SSH 成功连接,你就拥有了一个可以直接发送指令给服务器的文本命令行:可以安装软件,创建文件目录,运行应用程序等等。
这就相当于我们的工作场所。闪烁的光标跳动在黑色的背景中,安全地连接到我们位于班加罗尔的服务器。现在可以完成我们地基的最后一块拼图了:让整个世界能够看到我们网站的软件。
Nginx+Gunicon: 服务员和后厨人员
仅仅运行一个 Django 应用是没法让网站能够被访问的。Django 本身只是用于构建互联网应用的编程框架,就好比是菜谱和主厨。但它不是为了应对来自互联网成千上万的原始网络请求而生。如果让 Django 直面网络请求,就如同让成千上万饥饿的顾客直接冲进后厨,对着主厨喊出各自的订单。这无疑是巨大的混乱。
所以你需要一个专用于管理网络请求洪流的体系。对于一个基于 Python 的互联网应用,这个体系包括两个部分:一个网络服务器( Web Server )以及一个应用服务器( Application Server )。

网络服务器( Nginx ):对应服务员。我们采用的网络服务器是 Nginx (英语发音同"Engine-X")。它是你的餐厅里一位友善且高效的服务员。每一位顾客进来后首先打招呼就是它。它在同时处理数以千计的网络连接以及简单快速任务的表现堪称惊艳。Nginx 的主要职责在于:

返回静态文件:比如用户请求访问一张图片,一个 CSS 或 Javascript 文件,Nginx 都可以从储藏室(硬盘)直接读取然后返回给用户。这不需要麻烦大厨亲自处理这些请求。这对于系统的整体效能提升具有重大意义。
作为反向代理:对于需要后厨料理(比如加载店铺的商品列表)的请求,Nginx 自己并不会直接处理。它会迅速记录好这个订单(请求),径直来到后厨,把请求交给应用服务器来处理。


应用服务器( Gunicorn ):后厨经理。我们选择的应用服务器是 Gunicorn ,就类似我们的厨房经理。它从 Nginx 得到网络请求然后把请求转换成我们主厨( Django )能够理解的某种形式。它会同时管理着多个负责不同工序的厨师(就是服务器中的工作进程 Worker Process )。应用服务器是外部世界(由 Nginx 负责打交道)和我们的应用代码(由 Django 编写)之间的关键连接部分。

这样,整个流程看上去虽然简单但潜力十足;互联网的用户请求首先抵达 Nginx 。Nginx 要么直接返回静态文件,要么吧请求转发给 Gunicorn 。然后 Gunicorn 运行 Django 代码处理请求,生成 HTML 页面,最后把处理好的结果返回给 Nginx ,最终得以让用户看到网站。
要把这些都搞定需要在我们的服务器上安装 Nginx 和 Gunicorn 软件并提供一些简单的配置文件把两者正确串联起来。这些准备就绪之后,我们的地基就已经浇筑完毕,网站已初具雏形。我们把域名 mydukaan.io 指向了我们服务器的公网 IP 。
我激动地在浏览器中输入这个域名(网址)并按下了回车键。
成了。我们每月 5 美元的服务器成功返回了一个简单的 Hello World 页面。
我们的 MVP 简陋滑板大功告成。48 小时的时间所剩无几。得找一些真正的用户来看看他们是否喜欢我们的 MVP 。
第 2 章关键知识点总结

MVP 不是最终产品的初级版本,而是一场验证核心假设问题的科学实验。它的目标是尽可能获取有用的反馈和经验,不是打造完美的产品。先花时间想清楚验证所需的最简陋“滑板”是什么再开始动手写代码。
根据时间紧迫程度选择最熟悉的技术栈。创业伊始,“多久能上线”是最重要的关注点。所以尽量选择那些能帮你干“脏活”的开箱即用框架比如 Django 或者 Rails 。
从长期考虑,选择靠谱的底层数据库。即使不会立马需要全部功能,但是从一开始就直接上马健壮强大的数据库比如 PostgreSQL 可以有助于迭代过程中的难题解决和功能扩展。
谨慎控制起步阶段的架构规模。5 美元一个月的服务器应该足够应对早期的数千位用户。除非确实有必要,否则不要过度设计系统或者在云服务商开销太多。
搞清楚网络服务器( Nginx )和应用服务器( Gunicorn )的职责角色。他们之间类似餐厅服务员和后厨经理的分工模式是现代互联网应用的核心基石。
第 2 章第 2 节 选择技术栈

两天的时间不能浪费一分一秒。MVP 已经定义了我们将要做的“事情”, 也就是我们的数字版滑板。现在还需要找到“如何”完成它的工具,我们有哪些选择呢?

在软件行业,你选择的一系列工具被称作“技术栈”。就好比是盖房子。首先你得决定采用什么主建材。是用砖块,木材还是钢材?采用何种地基?需要哪些建筑工具?这些选择将决定造房子需要花多长时间,造好的房子有多坚固,以及以后想增加一个房间是否容易。

对于一个周末的黑客松项目来说,只有一个最重要的因素决定着类似的选择:构建的速度。我们暂时不需要考虑那些高大上的可扩展架构,或者最先进最时髦的技术栈。我们需要的是能在最短时间内帮助我们从零打造出可用产品的技术栈。

这意味着我们需要一个熟悉的,可靠的,能帮我们完成很多“脏活”的工具箱。

深入技术细节:编程语言和框架

这是我们第一个也是最重要技术选择:选哪个编程语言以及何种编程框架?

为什么选择 Python ?

编程语言就是你往计算机下达任务的指令“词汇表”。我们选择 Python 是因为它赖以成名的简洁语法。代码读上去就像是普通的英文。当你跟时间比赛时,最好不要在调试工具上浪费时间,比如试图搞明白某个复杂的编码机制或者漏写了一个分号。Python 代码写起来毫无障碍,让我们可以专注在 MVP 问题本身。而且 Python 社区相当活跃,每一种需要的功能都可以找到现成的库来调用。

为什么选择 Djanjo:开箱即用的框架

编程语言仅仅是”词汇表”。好的框架就是完整的指令手册。框架提供了合理的编码结构,顶层设计,以及一系列预先构建好的组件,这些都可以让你无需从零起步。

还是以盖房子为例。你当然可以自己去砍树,自己去加工板材,甚至自己打磨钉子。或者,你也可以购买一个预制的房屋套装,包含所有的墙壁和门窗。你只需要把它们组装起来,然后根据个人喜好做些微调。

这就是 Django 的价值。它是构建互联网应用的预制盖房套装。它的设计哲学就是著名的开箱即用( Batteries-Included )。这意味着你直接立马可以上手开始构建。对于我们需要在 48 小时内完成的 MVP 而言,Django 的两项功能绝对是大杀器:

Django 管理员后台:这是 Django 的杀手锏。只需要几行代码,Django 就可以生成一个完整的安全的并且看上去非常专业的管理员后台。这主要提供给我们作为创始团队登录用以查看全部业务数据。 当有新用户创建一个网店,我们就会在管理后台收到通知。我们可以查看相关商品名录,帮助修改错误的输入(仅在必要时)或者排查用户在使用过程中碰到的其它问题。但如果从零开始,弄出这样一个管理员后台可能需要花费一天的时间。Django 不花我们一分钱,只需要 15 分钟就可以准备好。这个管理员后台就是我们的任务指挥中心。
ORM (对象关系映射器):这个听上去好像有点复杂,但其实很简单。如果要从数据库读取数据,一般来说你得通过编写 SQL 语句完成。就是类似 SELECT * FROM products WHERE store_id=123;这样。SQL 很强大,但也容易出错,且跟我们主要采用的 Python 代码风格迥异。Django 的 ORM 模块就扮演了翻译的角色。它允许我们用 Python 代码跟数据库打交道。上述 SQL 指令在 Django 中可以改写成:Product.objects.filter(store_id=123)。这让代码不仅易于编写和阅读,也能够避免很多比如 SQL 注入的安全隐患。采用自带 ORM 的 Django 框架让我们的代码更简洁,开发速度也更快。

深入技术细节:我们考虑过的其它语言和框架

Django 当然不是唯一的选择。技术层面看,总有很多不同的方法可以达到同一个目的。关键是要根据任务性质和特点选择最合适的工具。

Node.js 以及 Express 框架:这也是一个非常流行的组合。Node.js 允许使用 Javascript 语言编写服务端代码,而 Javascript 通常是运行在客户端浏览器中。这对于很多全栈型创业团队非常友好。Express 框架则非常灵活,也遵循极简设计的原则。所以对于我们而言,这反而是它的不足。与其说 Express 是一个预制的盖房套装,不如说更像是一盒子高质量的乐高积木。它提供了很多很底层的功能,但是需要自行组装更高阶的模块。由于我们只有 48 小时,所以这样的自由度不是我们所需,我们需要的是 Django 开箱即用的丰富组件和编码结构。
Ruby on Rails:这可能是一个更接近 Django 的备选。Rails 与 Django 的设计哲学高度相似。它同样推崇“约定优先于配置”的理念,意味着它内置的很多合理决策能加速软件开发的过程。实话说,Rails 也的确是个不错的选择。最终选择 Django 可能更多是基于我个人的喜好和熟悉程度。因为我过去更多使用过 Python 和 Django ,所以在时间紧迫的情况下,押注最熟悉的工具总没错。
深入技术细节:数据库选型

有了合适的编程框架,我们需要决定在哪持久化保存我们的业务数据,比如店铺名称,商品明细,价格等等。我们需要一个数据库。如果说编程框架是盖房套装,那数据库无疑就是房子的地基。必须得稳定,可靠并且易于查找数据。

为什么选择关系型数据库?

我们决定使用一款关系型数据库( Relational Database )。原因很简单:我们的数据将保存在数据库的表( Table )中,就像一张巨型且功能强大的 Excel 表格。店铺信息存储在店铺表中,产品信息存储在产品表中。更关键的是,可以在这些表之间建立关系( Relationship ),比如每一款产品肯定属于某一个店铺。

这样的关系结构对于电商业务是完美的选择。数据间有了明确的联系和规则。肯定没人想看到一个不属于任何店铺的产品信息,或者一个没有顾客信息的订单。关系型数据库会强制约束数据之间的关系结构,保证数据始终一致,不多也不少。

为什么选择 PostgreSQL ?

在众多的关系型数据库中(比如 MySQL ,微软的 SQL Server 等等),我们最终选择了 PostgreSQL (常被简称为 Postgres )。

原因呢?其实对于 MVP 而言,Postgres 及其主要竞争对手 MySQL 都是不错的选择。但我们出于以下几点更倾向于 Postgres 。Postgres 在开发社区中的口碑相当不错,令人称赞地健壮,可靠并且符合通用的技术标准规范。绝对是开发中可以放心使用的主力选择。更重要的是,我知道 Postgres 有一些后面我们可能能用上的高级功能。比如其中一个功能是订阅通知机制( LISTEN/NOTIFY ),这将是我们在第 8 章提到的实时缓存系统背后的秘密武器。对于我们要完成的 MVP ,这个特性还用不上,但即使暂时用不到全部功能,从一开始就选择一个功能强大的地基,在将来无疑也是会受益颇多。

综上,我们的盖房图纸已经就绪。技术栈选择如下:

编程语言:Python
编程框架:Django
数据库:PostgreSQL
万事俱备,可以开工了。现在需要开始浇筑地基,准备建造承重墙了。换句话说,我们需要开始准备服务器了。
第 2 章: WhatsApp 应用里的 PDF 难题 (故事起源)

第 1 节 主意和滑板

每家创业公司都是解决某个问题诞生。我们的公司( Dukkan )起步则源自一个粗糙的 PDF 文档以及全国(印度)因疫情封禁导致的混乱。

那是 2020 年。整个世界仿佛按下了一个巨大的暂停键。印度曾经熙熙攘攘充斥着喇叭声(车辆)和各种小商贩的喧闹街头彻底陷入了沉寂。日常生活的熟悉节奏被打乱了。对于数以百万计的小企业主而言,比如社区便利店的店主大叔,街坊里蔬菜档口的卖菜老板,售卖手作印度传统纱丽的老阿姨,这绝对是一场浩劫。他们的店铺关门,他们的顾客只能呆在家里,他们的生计正在慢慢消失。

唯一的出路是互联网,或者更具体一点,是 WhatsApp 这款移动应用。它变成了新的交易市场,新的店铺甚至新的特价柜台。但是这个新市场乱糟糟的,也不够高效,还常令人感到万分沮丧。

这就是我的联合创始人苏米特登场的时刻。他并不是想要打造一家多么高大上的大公司,他只是想帮助他的一个邻居杂货铺能够不至于关门大吉。在亲眼目睹了那家杂货铺通过 WhatsApp 进行了多次有些凌乱的对话最终完成了所有交易流程之后,他被这样的场景深深吸引,但又感到深深震惊。

因为整个交易过程在低效方面堪称登峰造极:


步骤 1:建立商品名录。店主需要给每一位潜在的顾客发送一个很多页的 PDF 文档。但是这个 PDF 文档多半是通过微软的 Word 应用制作出来的,并且格式混乱,分辨率很低,甚至充斥着各种错误。产品名称乱七八糟,价格也模糊不清,更无法进行搜索。比如,如果想知道这个商家有没有你最爱的那个牌子的饼干,你得不停滚动鼠标从长达五页的粗糙图片中试图找到答案。


步骤 2:顾客下单。在经过长时间仔细定睛搜寻想要买的商品之后,顾客不得不通过逐字逐句输入长长一串订单的信息,而这显然极易出错。比如:“一包 Maggi 牌面条,两公斤面粉,半公斤糖,然后再来一包那个蓝色的乐事。。。”

步骤 3:商家确认订单。店家通常同时在处理数十个类似的与不同顾客的对话,需要逐一对顾客输入的订单文字信息进行确认。“不好意思,女士,蓝色的乐事没货了,但是我们有绿色包装的。”于是新一轮的商品挑选和订单信息又得再来一遍。

步骤 4:支付货款。好不容易等到订单信息终于确定,店家就会把自己的 UPI (印度的统一支付平台)账号或者类似的二维码发送给顾客。顾客就可以用自己手机的谷歌支付( GPay )或者是 PhonePe 应用完成支付,最重要的是,会同时把支付成功的截图作为凭证一起发给商家。可想而知,店家的手机相册堪称一个支付凭证截图的坟场,根本没有办法从数千张图片中高效地找到某个顾客为某个订单支付的款项。

这完全是一场噩梦。无非是印度人从疫情的绝望中想出来的一个临时数字解决方案( jugaad )


于是有天晚上我的手机响了。是苏米特打来的。我通过手机能感受到他当时炸裂的激情。因为他当时不仅仅是普通的聊天,而显然是处于灵感迸发的亢奋。

“苏巴什,我跟你说,有件事太疯狂了。”他开门见山,甚至连日常的招呼寒暄都省掉了。“我刚亲眼目睹了某个大叔通过 WhatsApp 开店卖货的过程,简直不忍直视。有时候把订单搞忘了,有时候有把顾客的支付搞混了,整个流程到处出错。我们得做点什么。”

他绘声绘色地对我描述了上述提到的令人头疼的几个步骤,也提到了 PDF 和截图满屏飞的混乱场景。

“我们得给这些商家做个什么东西让他们做生意更容易。” 他的声音开始变得严肃起来,“就是一个手机应用,可以让商家上传商品名录,顾客可以直接下单,这样大家就都很轻松,就是给商家准备的线上店铺。”

一个名字呼之欲出:Dukaan ,为“商店”之意。

苏米特说的没错。问题不是市面上缺少足够的技术解决这样的难题,而是没有足够简单高效的解决方案。这些卖家其实并不需要诸如亚马逊或者 Shopify 这样复杂的大型电商系统。他们既没有时间也没有技术搞明白那些。他们需要一个就像使用 WhatsApp 那般简单的电商专属解决方案。

那通电话点燃了星星之火。但如果不付诸行动,再好的点子也是空想。得投入精力用心打造,才能把点子变成现实。而在创业赛道,想做成点什么事情也必须“兵贵神速”。所以我们没有花费 6 个月的时间去慢慢打磨出一款完美产品的资格。我们必须得在短暂的几天时间里就知道是否值得依循这个点子继续做下去。

这就引出了对于任何跃跃欲试的创业者或技术宅最重要的一个概念:最小可行产品( MVP ,Minimum Viable Product )

深入理解:最小可行产品( MVP )

MVP 这个词在技术领域经常被谈起。很多人认为 MVP 就是打造一个最终产品的早期版本,可能错误比较多,功能比较少。不过这个想法是错误的。

因为 MVP 不是打造一个产品,而是做一场试验。

MVP 的最终目标并不是赚钱或者收获百万用户。MVP 最主要的目标是学习。它是为了以最小的开销验证最重要的预期假设而设计的一款具有严肃科学性质的工具。对于我们而言,这个预期假设是:“如果我们为那些小企业主打造一款超简单的开网店工具,他们会愿意使用嘛?”

为了验证这个假设,我们并不需要一款完美无缺的产品。我们只需要找到一个能回答上述问题的最简方案。这就是 MVP 的哲学出发点。

可以设想这个场景:比如你的目标是解决“出行”的问题,那你不需要从制造一辆汽车开始。造车那可太麻烦了,需要引擎,轮子,座椅,底盘,电力系统等等。这需要耗费太长时间。而等到你造好汽车,那时候你才悲催的发现你的顾客其实想要的是一辆摩托车。

MVP 的思路是,先整个滑板吧。这就简单多了,但也能解决核心的问题所在:能够把人从 B 点运到 A 点。这足以让你验证核心的预期假设,即人们是否愿意通过某种带有轮子的载具出行?

如果这个假设可以通过滑板得到确认,那就可以利用用户反馈打造下一个产品版本:带扶手的滑板车。然后是自行车,然后是摩托车。最后,才是汽车。每个阶段,你可以不断学习改进并给用户输出他们确实需要的价值产品。

所以对我们的 Dukaan 来说,需要找到属于我们自己的最初滑板。那到底什么才是可以用来测试我们点子的最基本形态呢?我们删除了所有想得到的各种附加功能。不需要支付网关的集成,不需要货运追踪,只提供给商家,没有花里胡哨的皮肤模板,没有销售数据分析。我们最后在最精简的核心诉求上达成共识。


这是我们为 Dukaan 定义的最核心业务流程:

店家创建店铺:只需要一个单页面,用户输入手机号,接收一条短信验证码,然后允许为店铺去个自定义的昵称名字。齐活。店家的网店就生成了。无需邮箱,无需密码,无需各种复杂的表格填写。
添加商品:采用极简的产品录入表单。产品名,价格,再来一个按钮让用户从手机相册选择一张照片作为产品卖家秀就行了。不需要定义产品类别,不需要定义产品规格(比如尺寸,颜色等等),不需要记录库存数量。这些就是建立一个商品名录的最基本所需。
分享店铺链接:店家添加若干商品之后,Dukaan 应用就会自动为这个店铺生成一个独一无二的可分享的链接(比如 mydukaan.io/mystore 这样)。店家可以在 WhatsApp 聊天框里直接使用这个链接分享给客户。
以上就是我们的滑板。

很显然,这根本称不上是一个功能齐备的“平台”,也不是什么“电商解决方案”。不过就是个用兼容手机端的单页面来替换丑陋 PDF 的简单小工具。这是我们想到的用来解决目前 WhatsApp 商家难题的最简单方法。

确定好这个 MVP 之后,我们决定给自己上上难度。不要很长的开发周期,不要繁琐的需求讨论。我们打算只用一周末的时间完成开发并发布上线。

于是,48 小时的两人黑客松正式启动。时间不等人。现在需要做出第一个重大的技术决策:用什么打造我们的 Dukaan 滑板?
第 1 章第 3 节: 我们伟大而危险的大单体应用

如果说服务器就像厨房, 那如何描述我们大厨( CPU )正在使用的菜谱(代码)呢? 在软件行业的术语里,我们称之为服务架构。我们采用的架构非常经典, 是那种几乎每一个初创公司早期都会采用的架构。
是的,我们用的是一个巨大的单体应用( Monolith )。

这个名字听上去像某种远古巨石阵一般巨大,有点唬人。但事实上,它非常简单。单体应用是指你所有的代码逻辑都在一处一起运行。比如我们的用户注册,商品名录,订单管理,商户仪表盘,支付,所有功能都同处在一个 Django ( Python 流行的 web 框架)项目里面。

可以把它想象成一本巨大的百科全书般的食谱。它包含从开胃小食,主食,甜品,以及饮品在内的所有菜谱,很显然这本书会超级厚。

为什么我们要选择从大单体应用起步(为什么它是正确的上手选择!)
我想直截了当地明确这一点:从大单体应用开始构建没有问题。对于初创企业而言,它大部分时候都是最优可行方案。在创业初期,你唯一的目标就是尽快构建并推出产品,越快越好。因为你需要知道你打算推出的产品的确有人愿意买单。
大单体应用天然就是为速度而生:
1. 易于代码开发: 所有逻辑都在一处。无需操心复杂的服务间通信问题。只需要编写一个函数然后调用它就行了。
2. 易于测试: 可以在笔记本电脑上运行整个应用并轻松地联调测试所有功能。
3. 易于部署: 只需把所有代码打包放到服务器上就完工了。
我们构建的大单体应用允许仅靠苏米特和我两个人在 48 小时内就上线了一个可以正常运行的电商平台。如果我们采用更复杂的“微服务”架构(本书后面的章节会详述), 我们现在可能还在为系统设计的细节争论不休。
所以大单体应用就是我们的超能力,给予我们极速试错的机会。但就像所有超能力一样,它也有一个不易察觉的危险副作用。
大单体应用的隐型危险
随着我们的菜谱增加,问题逐步开始显露。

● 变得愈发沉重以致难以使用。 查找某个食谱可能耗时更长。搞清楚为什么甜品部分的一个改动可能影响到开胃餐部分变得几乎不可能。在软件行业的术语里,我们称之为紧耦合。
● 一个小错误可能会毁了整本食谱书。 一个菜谱里面的一个小小的笔误理论上可能导致整本书都难以理解. 类似的,一个小小的代码错误可能会让整个网站都崩掉。
● 雇佣一个各司其职的专业团队变得毫无意义。比如即使你聘请了一个专业的糕点大厨, 它可能仍然需要理解一整本巨无霸食谱书才能开始工作。类似的,大单体应用会让新加入的开发者很难迅速融入以致降低效率。

最重要的是(也是那天晚上导致我们网站崩溃的原因), 大单体应用让你别无选择:如果你仅仅想为食谱书中某一部分(对应软件应用的某个功能)扩容( Scale ),那你只能先把整本书都扩容。


比如我们电商客户的商铺主页面访问量很高 (好比是食谱上的主食部分很受欢迎)。但另一方面我们的销售仪表盘(好比是食谱上的开胃餐部分) 被访问的次数就要少得多。可是由于他们同处于一个大单体应用中,我们的服务器资源得同时提供给所有功能。 处理主食的巨大消耗(厨师/CPU )让其它厨房任务处于资源嗷嗷待哺的状态,最终导致整个系统的崩盘。


我们那运行在小得可怜的服务器上的大单体应用成了一个潜在的完美毁灭风暴:一个软件层面的单点( Single Point of failure )运行在一个硬件层面的单点。 这简直就是一枚时间炸弹,最终在周二凌晨 3 点 14 分被引爆了。


第 1 章的关键知识点总结

● 你创业历程中第一台服务器肯定会有崩溃的一天。这不是一个会不会崩溃的问题,而是何时崩溃的问题。所以我们的目标应该是如何从崩溃中迅速恢复并从中总结经验, 而不是绞尽脑汁试图去避免崩溃。
● 深入理解架构基础。 在学习复杂架构之前, 彻底搞明白有关服务器运行的原理。 思考这些概念:CPU (大厨的手速), 内存 (操作台面空间), 以及磁盘 (储藏室).
● 掌握基本诊断工具库。如果看不到问题,那也没办法修复问题。学会使用 ssh 和 htop(或 top) 这些命令行工具。这些就是系统管理员的听诊器。
● 从大单体应用开始构建是一个实用选择( feature ),不是错误( bug )。创业初期,速度就是王道。尝试打造完美的早期产品属于用力过度。
● 认识到你的初期技术选择存在保质期。 为你赢得头 1 万个用户的架构很可能没办法帮你拿下接下来的 10 万个用户。为迭代进化做好准备。
第 1 章第 2 节 庖丁解服务器(一个厨师的厨房)

让我们先把这场半夜 3 点的惊慌无措搁在一边。在我们解决问题之前,得先理解问题。所以问题来了:“服务器”到底是啥?


请暂时忽略过于技术的解释。忘记在冰冷机房闪烁的指示灯。在本书剩余部分,我建议你用一个更简单浅显的概念来看待“服务器”:它就是只有一个厨师的餐厅后厨,所以很明显那个厨师会非常忙碌。

这个类比将会是有关服务器架构的最重要基础,其它东西都将在这个类比的基础上构建。

CPU: 厨师的速度

中央处理器 (Central Processing Unit) 就是掌勺的大厨。它是所有处理逻辑的核心大脑。这位厨师把原料 (数据)按照菜谱(代码)制作成一道可口的菜肴(一张网页,一个搜索结果,一笔完成的订单)。


● 更快的 CPU (用千兆赫作为单位, 即 GHz) 意味着你拥有手脚更麻利的厨师。 它可以用更快的速度进行切菜,炒菜和装盘。


● 多核 CPU 就像厨师多了几个机械臂。 比如 4 核 CPU 就像一位能够同时操作切菜,炒菜,煎炸和调味的厨师。它可以在同一时间处理多项任务。

我们那 512MB 的服务器只有一个单核 CPU 。按照这个厨师的类比,就如同我们只有一位独臂大厨但我们却要求他为 1 万人做一顿大餐。




RAM: 厨房的操作台面空间

内存( Random Access Memory )就是厨房的操作台面。这是理解服务器最关键的一步。内存就是厨师可以利用的工作空间。厨师( CPU )会使用内存来保存当前正在被制作的菜肴所需的所有原料以及锅碗瓢盆。

从厨房台面拿取所需非常快速。厨师甚至不需要思考, 想要什么直接伸手去拿就可以了。 更多的内存意味着更大的操作台面。如果拥有很大的操作台面,那厨师可以立马处理很多不同的点餐需求因为所有食材都在它面前的台面上摆放着可供随时取用。

但是如果台面放不下了,那厨师就会有棘手的麻烦。他得放下手头正在处理的活儿, 一头钻进后面的储藏室,从中花时间找到并拿回他所需的食材,还需要从台面上挪走一些东西以便腾出空间。这样一个操作明显会让所有事情都变得更慢。




这就是之前发生在我们服务器上的事情。我们仅有 512MB 的服务器就好比一个只有砧板大小的厨房台面。而我们自己的应用( Dukka ),我们的数据库以及服务器上的操作系统本身 全都在争夺这一块小小的砧板空间。所以当台面放不下的时候,服务器开始使用 "SWAP"空间可以看成是储藏室中一个特殊的区域,在临时救急时也可用作台面空间。这肯定是效率极低的方案。厨师不得不花时间来回奔走于储藏室与台面之间,必然耽误了它真正用于烹饪的功夫。


磁盘: 厨房的储藏室

磁盘,无论是传统机械硬盘或者是固态硬盘,都可以看做厨房的储藏室和冰箱,是用来长期存储菜谱(代码),食材(数据)以及厨房用具(操作系统)的地方。

储藏室比操作台面( RAM )显然要大很多,但存取耗时也要慢很多很多. 你肯定不希望你的厨师即使为了拿一点点食盐也得跑去储藏室一趟。 最理想当然是食盐就摆在它的操作台面上。


资源竞争: 厨房里的无序


现在请设想一下我们面对的场景。在一个迷你的厨房里,我们的独臂厨师只有一个砧板大小的操作台面, 然后我们苛求它完成:

● 运行应用程序: 厨师需要看明白菜谱(我们的 Python 代码) 并完成菜肴的烹制。

● 管理数据库: 大厨还得兼任储藏室管理员,不停地把食材(我们的用户数据)分门别类记录好 ,保存好并按食谱准确地取用。

● 处理网络流量: 除了以上, 大厨还需要完成服务生的工作:跑到餐厅前台以最快速度为数千用户点单。

这就是资源竞争。每一项任务都在同一时刻高呼大厨希望优先得到处理。应用程序需要 CPU 来处理运算逻辑,数据库需要把数据写入磁盘,还有刚接收到的用户请求需要内存来暂时记录。所有这些都在竞争同一个有限的资源,所以结果就是完全的死局,无法动弹。

如果想看到厨房里发生的这些无序竞争,你可能需要安装一个监控摄像头。在服务器的世界里,我们的摄像头只是一个简单的命令:htop. 它是命令行工具 top 的优化版本,可以让你犹如现场直播般看到大厨正在忙啥。

虽然可能看上去挺复杂,但你只需要了解以下几点:

● 顶部的 CPU 柱: 如果它显示红色的 100%,那你的大厨就是过载了。


● 内存柱: 如果这个数字满了,说明你的操作台面已经放不下了。


● SWAP 柱: 如果这个数字开始增加,这不是好的信号。说明你的大厨正在尝试使用储藏室作为额外的操作台面。


● 进程列表: 这里展示了大厨正在处理的每一个任务并且能让你看到哪一个占用了最多的资源。

学会读懂这个 htop 的输出只是成为 CTO (要么像我这样误打误撞,要么按部就班)的第一步。从这里开始,我们将进行科学的诊断而不是盲目的瞎猜。对于我个人而言, 那晚半夜 3 点的服务器屏幕尖锐地喊出了一个无可辩驳的事实: 我们的厨房对于我们的抱负而言,实在是太小了,小到足以致命。
第 1 章 凌晨 3 点响起的手机

第一节: 服务器崩了


我的手机突然响了起来,但不是那种普通的来电铃声,而是一种类似尖叫的声音。

那声音伴随着一种特定的手机振动(通常被手机厂商用于提示最高等级的恐慌情绪)。振动的手机在我床边廉价的木桌上发出了剧烈而仿佛暴怒的嗡嗡声。那是一种不光能让你从睡梦中惊醒并且能够让你立马进入高度戒备状态的声音。此刻,时钟上的时间微微透着令人不安的红光:凌晨 3 点 14 分。

其实甚至在我睁开眼之前,我的心就已经开始砰砰直跳。因为一般来说,在这样的午夜时分收到来电只有两个原因:要么是家里人有急事,要么就是公司业务出大事了。手机显示的来电号码确认了后一种情形。一个再熟悉不过的名字闪入我的视线:苏米特( Suumit )。

苏米特.萨( Suumit Shah )是我的至交和公司合伙人,他敏锐的商业头脑对于我这样的码农而言非常重要。他在凌晨 3 点打电话给我那只会有一个原因。公司出大事了。


我迅速在手机上滑动接听,虽然我的声音听上去有些沙哑和疲惫:“咋啦,兄弟?”


“苏巴什( Subhash )! 网站全挂了!” 透过手机麦克风,苏米特的声音听上去就像是一声令人肾上腺素飙升和无比紧张的枪响:“快起来!网站全都不能用了!”


他不用再多说什么。我已经翻身下了床,光脚踩在冰凉的地板上,全身就像被电击一样。我摸索着找到了我的笔记本电脑, 熟悉的苹果标志泛着白光就像黑暗的房间里的一座灯塔。 我此刻思绪万千,一大堆可能出现的服务灾难场景在脑海中无序地飘过:


是被黑客攻击了嘛? DDoS 攻击?难道是哪个外国写脚本的小屁孩闲着无聊来搞垮我们的网站用以取乐?

或者是我们自己的程序员上传了一段有问题的代码?难道是某个多余的分号导致网站完全崩了?

是不是我们的云服务商挂了?如果是这种情况,那我们还能做啥?

“网站页面显示不出来。手机端应用也在报错。全都挂了。完完全全的彻底崩了。”苏米特用紧张并且焦虑的声音继续说着。我甚至能听到他在电话那头来回踱步。

“好的好的,我已经在处理了。哥们淡定。” 我尽量让自己听上去比真实的内心更稳定一点。 一定要保持冷静。 救火第一条原则就是不要火上浇油去添乱。

略带睡意,我用笨拙的手指开始在键盘敲击。我打开终端( terminal )准备登录。这个黑底绿字的界面是通往我们所有线上服务的指令中枢。

ssh [email protected]


我敲下了回车键。光标开始闪烁,继续闪烁,仍在闪烁。

正常来说,登录提示符应该很快就出现。但现在这个情况。。。感觉不妙。非常不妙。这意味着服务器不只是出现问题了,而是像一个已经进入生命倒计时的垂危病人甚至无法从病床下来应门。在等待了很长很长一段时间之后(长到我甚至觉得仿佛比一辈子还要久),登录提示符终于出现了。还好,至少服务器还没有完全挂,但也差不远了。

我开始思考原因:如果服务器响应如此缓慢,那应该不是简单的应用代码问题( bug )。应该是有更深层的问题。可能出在系统层面。就像是服务器本身在苟延残喘。

我输入了我的第一个诊断命令。这是用来检查服务器基本体征的一个简单工具:


htop


屏幕上显示的检查指令输出结果令我脊背发凉。满屏的红色。

每一个进程( process )似乎都处于异常状态。CPU 使用率已经飙升到 100%。结果显示服务器几乎所有内存都已经被占用。甚至连服务器临时救急的 SWAP 存储空间都已经满了。

看上去服务器并不是在苟延残喘。 它其实已经凉透了,而我们只不过目睹了它超负荷状态下的最后几次抽搐(登录和检查指令响应)。


紧接着我马上知道了我们网站彻底歇菜的原因。简单得甚至有些尴尬:显示屏上方清楚地展示着服务器的总内存容量是 512MB 。

五百壹拾贰兆字节。

我的智能手机拥有 8GB 的内存,比这台承载了我们公司所有业务的服务器要大 16 倍。数千家电商客户以及他们数以百万记的商品名录,Dukaan (我们的公司)全部的希望和梦想都运行在这台比我口袋里的手机还要弱的服务器上。

这不是一次复杂的黑客行为或者代码错误。原因很简单:就是服务器不够用了。 就好像我们在一个小小的电话亭举办一场大型的摇滚演唱会,最终电话亭被挤爆了。

盯着屏幕,手机仍响在耳侧,我突然瞬间清醒。像我这样一个不是计算机专业科班出身,没有正儿八经大规模系统扩容经验的人,到底是如何承担起这一切的?

要弄明白这个问题,先得弄懂我们正尝试驯服的这头野兽。也就是需要如庖丁解牛一样深入理解此刻正在作妖的家伙:服务器。
翻译自 Subhash Choudhary 的 Accidental CTO 一书
关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2069 人在线   最高记录 6679       Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 16ms UTC 14:18 PVG 22:18 LAX 06:18 JFK 09:18
Do have faith in what you're doing.
ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86