设计一个支持百万用户的系统是具有挑战性的,这是一段需要不断改进和不断提升的旅程。在本章中,我们将构建一个支持单个用户的系统,并逐渐扩展以服务于数百万用户。阅读本章后,您将掌握一些技巧,帮助您解决系统设计面试问题。
AI不会取代你,使用AI的人会。欢迎关注我的公众号:更AI。以程序员的视角来看AI能带给我们什么~
单服务器设置
千里之行始于足下,构建一个复杂的系统也是如此。为了从简单的东西开始,我们将所有内容都运行在一个单独的服务器上。图1显示了一个单服务器设置的示意图,其中所有内容都在一个服务器上运行:Web应用程序、数据库、缓存等。
为了理解这个设置,有助于调查请求流程和流量来源。让我们首先看一下请求流程(图1-2)。
- 用户通过域名访问网站,例如api.mysite.com。通常,域名系统(DNS)是由第三方提供的付费服务,而不是由我们的服务器托管。
- Internet协议(IP)地址返回给浏览器或移动应用。在本例中,返回的IP地址是15.125.23.214。
- 一旦获得IP地址,超文本传输协议(HTTP)[1]请求将直接发送到您的Web服务器。
- Web服务器返回HTML页面或JSON响应进行渲染。
接下来,让我们来看一下流量来源。流向您的Web服务器的流量来自两个来源:Web应用程序和移动应用程序。
- Web应用程序:它使用一组服务器端语言(Java、Python等)来处理业务逻辑、存储等,以及客户端语言(HTML和JavaScript)来进行展示。
- 移动应用程序:HTTP协议是移动应用程序与Web服务器之间的通信协议。由于其简单性,JavaScript对象表示法(JSON)通常用于传输数据的API响应格式。下面是一个以JSON格式显示的API响应示例:
GET /users/12 – 检索id为12的用户对象
数据库
随着用户群体的增长,一个服务器已经不够用了,我们需要多台服务器:一台用于处理网站和移动端的流量,另一台用于数据库(图1-3)。将网站和移动端流量(Web层)与数据库(数据层)服务器分离,可以使它们能够独立扩展。
使用哪种数据库?
您可以选择传统关系型数据库或非关系型数据库。让我们来看看它们的区别。
关系型数据库也被称为关系数据库管理系统(RDBMS)或SQL数据库。最流行的有MySQL、Oracle数据库、PostgreSQL等。关系型数据库使用表和行来表示和存储数据。您可以使用SQL在不同的数据库表之间执行联接操作。
非关系型数据库也被称为NoSQL数据库。流行的有CouchDB、Neo4j、Cassandra、HBase、Amazon DynamoDB等[2]。这些数据库分为四类:键值存储、图存储、列存储和文档存储。非关系型数据库通常不支持联接操作。
对于大多数开发者来说,关系型数据库是最佳选择,因为它们已经存在了40多年,并且在历史上表现良好。然而,如果关系型数据库不适合您特定的使用情况,探索超越关系型数据库是至关重要的。如果满足以下情况,非关系型数据库可能是正确的选择:
- 您的应用程序需要超低的延迟。
- 您的数据是非结构化的,或者您没有任何关系数据。
- 您只需要对数据进行序列化和反序列化(JSON、XML、YAML等)。
- 您需要存储大量的数据。
垂直扩展与水平扩展
垂直扩展,也称为“纵向扩展”,是指向服务器添加更多的处理能力(CPU、RAM等)的过程。水平扩展,也称为“横向扩展”,允许您通过向资源池中添加更多的服务器来进行扩展。
当流量较低时,垂直扩展是一个很好的选择,垂直扩展的简单性是其主要优点。不幸的是,它也存在一些严重的局限性。
- 垂直扩展有一个硬性限制。不可能向单个服务器添加无限的CPU和内存。
- 垂直扩展没有故障转移和冗余。如果一个服务器宕机,网站/应用将完全无法访问。
由于垂直扩展的限制,对于大规模应用程序来说,水平扩展更加理想。
在之前的设计中,用户直接连接到Web服务器。如果Web服务器脱机,用户将无法访问网站。在另一种情况下,如果许多用户同时访问Web服务器并达到Web服务器的负载限制,用户通常会遇到响应变慢或无法连接到服务器的问题。负载均衡器是解决这些问题的最佳技术。
负载均衡器
负载均衡器会将传入的流量均匀分配给在负载均衡集合中定义的Web服务器。图1-4展示了负载均衡器的工作原理。
如图1-4所示,用户直接连接负载均衡器的公共IP。通过这种设置,Web服务器不再能直接被客户端访问。为了更好的安全性,私有IP用于服务器之间的通信。私有IP是一个只能在同一网络中的服务器之间访问的IP地址,无法通过互联网访问。负载均衡器通过私有IP与Web服务器进行通信。
在图1-4中,当负载均衡器和第二个Web服务器添加后,我们成功解决了故障切换的问题,并提高了Web层的可用性。具体细节如下:
- 如果服务器1下线,所有流量将被路由到服务器2。这样可以防止网站宕机。我们还可以向服务器池中添加一个新的健康Web服务器来平衡负载。
- 如果网站流量迅速增长,两个服务器无法处理流量,负载均衡器可以优雅地解决这个问题。您只需要向Web服务器池添加更多服务器,负载均衡器将自动开始将请求发送给它们。
现在Web层看起来很好,那数据层呢?当前的设计只有一个数据库,因此不支持故障转移和冗余。数据库复制是解决这些问题的常见技术。让我们来看一下。
数据库复制
引用自维基百科:“数据库复制可以在许多数据库管理系统中使用,通常在原始数据库(主数据库)和副本(从数据库)之间建立主/从关系。” [3]
主数据库通常仅支持写操作。从数据库从主数据库获取数据的副本,仅支持读操作。所有的插入、删除或更新等修改数据的命令必须发送到主数据库。大多数应用程序需要更高比例的读操作与写操作,因此系统中从数据库的数量通常大于主数据库的数量。图1-5显示了一个具有多个从数据库的主数据库。
数据库复制的优势:
- 更好的性能:在主从模型中,所有的写操作和更新操作都发生在主节点上,而读操作分布在从节点上。这种模型改善了性能,因为它允许更多的查询并行处理。
- 可靠性:如果你的数据库服务器之一被自然灾害(如台风或地震)摧毁,数据仍然得以保留。你不需要担心数据丢失,因为数据被复制到多个位置。
- 高可用性:通过在不同的位置复制数据,即使一个数据库离线,你的网站仍然可以运行,因为你可以访问存储在另一个数据库服务器中的数据。
在前一节中,我们讨论了负载均衡器如何帮助提高系统的可用性。我们在这里提出同样的问题:如果其中一个数据库离线了会怎么样?图1-5中讨论的架构设计可以处理这种情况:
- 如果只有一个可用的从数据库,并且它离线了,读操作将暂时指向主数据库。一旦问题被发现,一个新的从数据库将取代旧的数据库。如果有多个可用的从数据库,读操作将被重定向到其他健康的从数据库。一个新的数据库服务器将取代旧的数据库。
- 如果主数据库离线了,一个从数据库将被提升为新的主数据库。所有的数据库操作将在新的主数据库上暂时执行。一个新的从数据库将立即取代旧的数据库进行数据复制。在生产系统中,提升新的主数据库更为复杂,因为从数据库中的数据可能不是最新的。需要通过运行数据恢复脚本来更新缺失的数据。虽然一些其他的复制方法,如多主和环形复制,可以提供帮助,但这些设置更加复杂,它们的讨论超出了本书的范围。有兴趣的读者可以参考所列的参考资料[4] [5]。
图1-6显示了添加了负载均衡器和数据库复制后的系统设计。
让我们来看一下设计:
- 用户从DNS获取负载均衡器的IP地址。
- 用户使用该IP地址连接到负载均衡器。
- HTTP请求被路由到服务器1或服务器2。
- Web服务器从从数据库读取用户数据。
- Web服务器将任何修改数据的操作路由到主数据库。包括写入、更新和删除操作。
- 现在,你已经对Web和数据层有了扎实的理解,是时候通过添加缓存层并将静态内容(JavaScript/CSS/图像/视频文件)转移到内容分发网络(CDN)来提高负载/响应时间了。
缓存
缓存是一个临时存储区,用于在内存中存储昂贵的响应结果或经常访问的数据,以便后续的请求可以更快地得到服务。如图1-6所示,每次加载新的网页时,会执行一个或多个数据库调用来获取数据。反复调用数据库会严重影响应用程序性能。缓存可以缓解这个问题。
缓存层
缓存层是一个比数据库快得多的临时数据存储层。拥有独立的缓存层有以下好处:系统性能更好、能够减少数据库工作负载以及能够独立扩展缓存层。图1-7显示了一个可能的缓存服务器设置:
收到请求后,Web服务器首先检查缓存中是否有可用的响应。如果有,则将数据发送回客户端。如果没有,则查询数据库,将响应存储在缓存中,并将其发送回客户端。这种缓存策略称为读取穿透缓存。根据数据类型、大小和访问模式,还可以使用其他缓存策略。一项以前的研究解释了不同的缓存策略如何工作[6]。与缓存服务器的交互非常简单,因为大多数缓存服务器为常见的编程语言提供API。以下代码片段显示了典型的Memcached API:
使用缓存的注意事项
以下是使用缓存系统时应考虑的几个问题:
-
决定何时使用缓存。在数据经常被读取但很少被修改时,考虑使用缓存。由于缓存数据存储在易失性内存中,缓存服务器不适合用于持久化数据。例如,如果缓存服务器重新启动,内存中的所有数据都会丢失。因此,重要的数据应保存在持久化数据存储中。
-
过期策略。实施过期策略是一个好的做法。一旦缓存数据过期,它将从缓存中删除。当没有过期策略时,缓存数据将永久存储在内存中。建议不要将过期日期设置得太短,否则系统会过于频繁地从数据库重新加载数据。同时,也不建议将过期日期设置得太长,以免数据变得陈旧。
-
一致性:这涉及保持数据存储和缓存的同步。由于数据存储和缓存上的数据修改操作不在单个事务中,所以可能发生不一致。在跨多个地区进行扩展时,保持数据存储和缓存之间的一致性是具有挑战性的。有关详细信息,请参考Facebook发表的题为《Scaling Memcache at Facebook》的论文[7]。
-
减轻故障:单个缓存服务器代表潜在的单点故障(SPOF),维基百科对其的定义如下:“单点故障(SPOF)是系统的一部分,如果它发生故障,将导致整个系统停止工作”[8]。因此,建议在不同的数据中心中使用多个缓存服务器,以避免单点故障。另一个推荐的方法是通过一定百分比进行过量配置所需的内存。这样可以提供一个缓冲区,以应对内存使用量增加的情况。
-
淘汰策略:一旦缓存已满,任何向缓存中添加项的请求都可能导致现有项被移除。这被称为缓存淘汰。最近最少使用(LRU)是最常见的缓存淘汰策略。其他淘汰策略,如最不经常使用(LFU)或先进先出(FIFO),可根据不同的使用情况采用。
内容分发网络(CDN)
CDN是一个由地理分布的服务器组成的网络,用于传送静态内容。CDN服务器缓存像图像、视频、CSS、JavaScript文件等静态内容。
动态内容缓存是一个相对较新的概念,超出了本书的范围。它使得可以缓存基于请求路径、查询字符串、cookie和请求头的HTML页面。有关更多信息,请参考参考资料[9]中提到的文章。本书重点介绍如何使用CDN来缓存静态内容。
以下是CDN的高级工作原理:当用户访问网站时,距离用户最近的CDN服务器将传送静态内容。直观来说,用户离CDN服务器越远,网站加载速度就越慢。例如,如果CDN服务器位于旧金山,洛杉矶的用户将比欧洲的用户更快地获取内容。图1-9是一个很好的示例,显示了CDN如何提高加载时间。
图1-10展示了CDN的工作流程。
- 用户A尝试使用图像URL获取image.png。URL的域名由CDN提供商提供。以下两个图像URL是用来演示Amazon和Akamai CDN上图像URL的样例:
- 如果CDN服务器没有image.png的缓存,CDN服务器会从源(可以是Web服务器或像Amazon S3这样的在线存储)请求文件。
- 源将image.png返回给CDN服务器,其中包括可选的HTTP头部Time-to-Live(TTL),描述图像被缓存的时间。
- CDN缓存图像并将其返回给用户A。图像会在CDN中缓存,直到TTL过期。
- 用户B发送请求以获取相同的图像。
- 只要TTL未过期,图像将从缓存中返回。
CDN 使用的考虑因素
- 成本:CDN 由第三方提供商运营,您需要支付 CDN 内外的数据传输费用。对于不经常使用的资源,缓存并没有显著的好处,所以您应该考虑将其移出 CDN。
- 设置适当的缓存过期时间:对于时间敏感的内容,设置缓存过期时间非常重要。缓存过期时间既不能太长也不能太短。如果时间太长,内容可能已经不新鲜。如果时间太短,可能会导致重复从源服务器重新加载内容到 CDN。
- CDN 回退:您应该考虑您的网站/应用程序如何应对 CDN 故障。如果出现临时的 CDN 中断,客户端应该能够检测到问题并从源获取资源。
- 使文件失效:您可以在文件过期之前从 CDN 中移除文件,具体操作有以下几种:
- 使用 CDN 供应商提供的 API 使 CDN 对象失效。
- 使用对象版本控制来提供不同版本的对象。要对对象进行版本控制,可以向 URL 添加参数,比如版本号。例如,查询字符串中添加版本号 2:image.png?v=2。
图 1-11 展示了在添加 CDN 和缓存后的设计。
- 静态资源(JS、CSS、图片等)不再由 Web 服务器提供。它们从 CDN 获取以获得更好的性能。
- 通过缓存数据减轻了数据库的负载。
无状态的Web层
现在是考虑水平扩展Web层的时候了。为此,我们需要将状态(例如用户会话数据)从Web层中移出。一个很好的做法是将会话数据存储在持久性存储中,如关系型数据库或NoSQL数据库。集群中的每个Web服务器都可以从数据库中访问状态数据。这被称为无状态的Web层。
有状态架构
有状态服务器和无状态服务器有一些关键区别。有状态服务器会记住从一个请求到下一个请求的客户端数据(状态)。无状态服务器不保存任何状态信息。
图1-12显示了一个有状态架构的示例。
在图1-12中,用户A的会话数据和个人资料图片存储在服务器1中。要对用户A进行身份验证,HTTP请求必须路由到服务器1。如果请求被发送到其他服务器,如服务器2,身份验证将失败,因为服务器2不包含用户A的会话数据。同样,来自用户B的所有HTTP请求必须路由到服务器2;来自用户C的所有请求必须发送到服务器3。
问题在于同一客户端的每个请求必须路由到同一台服务器。在大多数负载均衡器中,可以通过粘性会话来实现这一点[10];然而,这会增加开销。使用这种方法更加困难地添加或删除服务器。处理服务器故障也是一项挑战。
无状态架构
图1-13展示了无状态架构。
在这种无状态架构中,用户的HTTP请求可以发送到任何Web服务器,这些服务器从共享数据存储中获取状态数据。状态数据存储在共享数据存储中,并且不保存在Web服务器中。无状态系统更简单、更健壮和可扩展。
图1-14展示了带有无状态Web层的更新设计。
在图1-14中,我们将会话数据从Web层移出,并将其存储在持久数据存储中。共享数据存储可以是关系数据库、Memcached/Redis、NoSQL等。选择NoSQL数据存储是因为它易于扩展。自动扩展意味着根据流量负载自动添加或删除Web服务器。在状态数据从Web服务器中移除后,根据流量负载添加或删除服务器轻松实现Web层的自动扩展。
您的网站快速增长,并吸引了大量国际用户。为了提高可用性并在更广泛的地理区域提供更好的用户体验,支持多个数据中心至关重要。
数据中心
图1-15显示了一个拥有两个数据中心的示例设置。在正常运行时,用户会根据地理位置通过geoDNS路由到最近的数据中心,其中在美国东部的流量占x%,在美国西部的流量占(100 - x)%。geoDNS是一种DNS服务,根据用户所在地将域名解析为IP地址。
如果发生任何重大数据中心故障,我们会将所有流量引导到一个正常运行的数据中心。在图1-16中,数据中心2(美国西部)离线,100%的流量被路由到数据中心1(美国东部)。
要实现多数据中心设置,需要解决一些技术挑战:
- 流量重定向:需要有效的工具将流量引导到正确的数据中心。根据用户所在地,可以使用geoDNS将流量引导到最近的数据中心。
- 数据同步:来自不同地区的用户可能使用不同的本地数据库或缓存。在故障转移情况下,流量可能会路由到一个数据中心,该数据中心的数据不可用。一种常见的策略是在多个数据中心之间复制数据。一项先前的研究展示了Netflix如何实现异步多数据中心复制[11]。
- 测试和部署:对于多数据中心设置,重要的是在不同的位置测试您的网站/应用程序。自动化部署工具对于保持所有数据中心的服务一致至关重要[11]。
为了进一步扩展我们的系统,我们需要解耦系统的不同组件,使它们可以独立扩展。消息队列是许多实际分布式系统用于解决这个问题的关键策略。
消息队列
消息队列是一种持久性组件,存储在内存中,用于支持异步通信。它作为缓冲区并分发异步请求。消息队列的基本架构很简单。称为生产者/发布者的输入服务创建消息,并将其发布到消息队列中。其他服务或服务器,称为消费者/订阅者,连接到队列并执行消息定义的操作。模型如图1-17所示。
解耦使消息队列成为构建可扩展和可靠应用程序的首选架构。使用消息队列,当消费者无法处理消息时,生产者可以将消息发布到队列中。即使生产者不可用,消费者也可以从队列中读取消息。
考虑以下用例:您的应用程序支持照片定制,包括裁剪、锐化、模糊等操作。这些定制任务需要时间来完成。在图1-18中,Web服务器将照片处理作业发布到消息队列中。照片处理工作者从消息队列中接收作业,并异步执行照片定制任务。生产者和消费者可以独立扩展。当队列的大小变大时,可以添加更多工作者以减少处理时间。然而,如果队列大部分时间为空,工作者的数量可以减少。
日志记录、指标、自动化
在处理只运行在几台服务器上的小型网站时,日志记录、指标和自动化支持是良好的实践,但并非必需。然而,现在你的网站已经发展成为一个为大型企业提供服务的网站,投资于这些工具是必不可少的。
日志记录:监控错误日志非常重要,因为它有助于识别系统中的错误和问题。您可以在每个服务器级别监控错误日志,也可以使用工具将它们聚合到一个集中式服务中,以便进行简单的搜索和查看。
指标:收集不同类型的指标有助于我们获取业务见解并了解系统的健康状况。以下是一些有用的指标:
- 主机级别的指标:CPU、内存、磁盘I/O等。
- 聚合级别的指标:例如整个数据库层、缓存层等的性能。
- 关键业务指标:每日活跃用户、留存率、收入等。
自动化:当系统变得庞大而复杂时,我们需要构建或利用自动化工具来提高生产效率。持续集成是一种良好的实践,通过自动化验证每次代码提交,使团队能够早期发现问题。此外,自动化构建、测试、部署流程等可以显著提高开发人员的生产力。
添加消息队列和其他工具
图1-19显示了更新后的设计。由于空间限制,图中只显示了一个数据中心。
- 设计中包括一个消息队列,有助于使系统更松散耦合和具有容错性。
- 包括了日志记录、监控、指标和自动化工具。
随着每天数据的增长,您的数据库负荷越来越重。是时候对数据层进行扩展了。
数据库扩展
数据库扩展有两种主要方法:垂直扩展和水平扩展。
垂直扩展
也称为纵向扩展,是通过向现有机器添加更多的性能(CPU、RAM、DISK等)来进行扩展。有一些功能强大的数据库服务器。根据亚马逊关系数据库服务(RDS)[12],你可以获得一台具有24 TB RAM的数据库服务器。这种强大的数据库服务器可以存储和处理大量的数据。例如,2013年的stackoverflow.com每月有超过1000万的独立访问者,但它只有1个主数据库[13]。然而,垂直扩展也存在一些严重的缺点:
- 你可以为数据库服务器添加更多的CPU、RAM等,但存在硬件限制。如果你有大量的用户,单个服务器是不够的。
- 单点故障的风险增加。
- 垂直扩展的总体成本较高。强大的服务器更加昂贵。
水平扩展
也称为分片,是添加更多服务器的做法。图1-20比较了垂直扩展和水平扩展。
分片将大型数据库分割成更小、更易管理的部分,称为分片。每个分片共享相同的模式,尽管每个分片上的实际数据是唯一的。
图1-21展示了分片数据库的示例。用户数据根据用户ID分配到数据库服务器上。每当你访问数据时,都会使用散列函数来找到相应的分片。在我们的示例中,user_id % 4被用作散列函数。如果结果等于0,则使用分片0来存储和获取数据。如果结果等于1,则使用分片1。其他分片的逻辑也是相同的。
图1-22展示了分片数据库中的用户表。
在实施分片策略时,最重要的因素是选择分片键。分片键(也称为分区键)由一个或多个列组成,用于确定数据的分布方式。如图1-22所示,“user_id”是分片键。分片键允许你通过将数据库查询路由到正确的数据库来高效地检索和修改数据。在选择分片键时,最重要的一个标准是选择一个能够均匀分布数据的键。
分片是扩展数据库的一种很好的技术,但远非完美的解决方案。它给系统引入了复杂性和新的挑战:
重新分片数据:当1)单个分片由于快速增长而无法再容纳更多数据时,需要重新分片数据。2)某些分片可能由于不均匀的数据分布而更快地耗尽分片。当分片耗尽时,需要更新分片函数并移动数据。一种常用的解决此问题的技术是一致性哈希,将在第5章中讨论。
热点键问题:也称为明星问题。对特定分片的过度访问可能导致服务器超载。想象一下,Katy Perry、Justin Bieber和Lady Gaga的数据都最终存储在同一个分片上。对于社交应用来说,该分片将被读操作淹没。为了解决这个问题,我们可能需要为每个名人分配一个分片。甚至每个分片可能还需要进一步分区。
连接和去规范化:一旦数据库被分片到多个服务器上,执行跨数据库分片的连接操作就变得困难。一个常见的解决方法是对数据库进行去规范化,以便可以在单个表中执行查询。
在图1-23中,我们对数据库进行分片以支持快速增长的数据流量。与此同时,一些非关系型功能被移到NoSQL数据存储中,以减轻数据库负载。这是一篇涵盖了NoSQL许多使用案例的文章[14]。
超过数百万用户的规模
系统的扩展是一个迭代的过程。根据本章学到的知识进行迭代可能会使我们走得更远。为了超越数百万用户,需要更多的优化和新策略。例如,您可能需要优化您的系统并将系统解耦为更小的服务。本章学到的所有技术应该为应对新的挑战提供了良好的基础。为了总结本章,我们提供了如何扩展我们的系统以支持数百万用户的摘要:
- 保持 Web 层无状态
- 在每个层面上构建冗余性
- 尽可能地缓存数据
- 支持多个数据中心
- 在 CDN 中托管静态资源
- 通过分片扩展数据层
- 将层级拆分为独立的服务
- 监控您的系统并使用自动化工具
恭喜您取得了如此大的进展!现在给自己一个鼓励。做得好!
AI不会取代你,使用AI的人会。欢迎关注我的公众号:更AI。以程序员的视角来看AI能带给我们什么~
参考资料
[1] 超文本传输协议(Hypertext Transfer Protocol): https://zh.wikipedia.org/wiki/超文本传输协议
[2] 你是否应该超越关系型数据库?:
https://blog.teamtreehouse.com/should-you-go-beyond-relational-databases
[3] 复制(Replication): https://zh.wikipedia.org/wiki/复制_(计算机)
[4] 多主复制(Multi-master replication):
https://zh.wikipedia.org/wiki/多主复制
[5] NDB 集群复制:多主和环形复制:
https://dev.mysql.com/doc/refman/5.7/en/mysql-cluster-replication-multi-master.html
[6] 缓存策略及如何选择合适的策略:
https://codeahoy.com/2017/08/11/caching-strategies-and-how-to-choose-the-right-one/
[7] R. Nishtala,“Facebook 的缓存扩展”,第十届 USENIX 网络系统设计与实现研讨会(NSDI '13)。
[8] 单点故障(Single point of failure): https://zh.wikipedia.org/wiki/单点故障
[9] Amazon CloudFront 动态内容传送:
https://aws.amazon.com/cloudfront/dynamic-content/
[10] 配置经典负载均衡器的黏性会话:
https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-sticky-sessions.html
[11] 多区域容错性的主动-主动(Active-Active):
https://netflixtechblog.com/active-active-for-multi-regional-resiliency-c47719f6685b
[12] Amazon EC2 高内存实例:
https://aws.amazon.com/ec2/instance-types/high-memory/
[13] 运行 Stack Overflow 的所需条件:
http://nickcraver.com/blog/2013/11/22/what-it-takes-to-run-stack-overflow
[14] 你到底在使用 NoSQL 做什么:
http://highscalability.com/blog/2010/12/6/what-the-heck-are-you-actually-using-nosql-for.html
本文翻译自《System Design Interview: An Insider’s Guide》第一章,如有侵权,请联系本人删除