本文是对"从零扩展到数百万用户(Scale From Zero To Millions Of Users)"的全文翻译。
设计一个支持数百万用户的系统具有挑战性,这是一个需要不断完善和不断改进的旅程。在本章中,我们构建一个支持单个用户的系统,并逐步扩展以服务数百万用户。读完本章后,你将掌握一些技巧,帮助您解决系统设计的面试问题。
单体服务器架构
千里之行始于足下,构建复杂的系统也不例外。从简单的事情开始,一切都在单台服务器上运行。
图 1 展示了单台服务器的架构,其中所有内容都在一台服务器上运行:Web 应用程序、数据库、缓存等。
图1
可以通过分析请求的流程,以及流量的来源,来帮组我们理解这个架构。
首先来看一下请求流程
图2
1、用户通过域名访问网站,如 api.mysite.com。通常,域名系统 (DNS) 是由第三方提供的付费服务,而不是由我们的服务器托管。
2、DNS解析出IP地址并返回给浏览器或移动应用程序。在示例中,返回的 IP 地址是 15.125.23.214。
3、获取 IP 地址之后,通过超文本传输协议 (HTTP) [1] 将请求直接发送到 Web 服务器。
4、Web 服务器返回 HTML 页面或 JSON 响应内容进行客户端渲染。
接下来我们来看看流量来源
Web 服务器的流量有两个来源:Web应用程序和移动端应用程序。
- Web应用程序:它使用一些服务器端语言(Java、Python等)来处理业务逻辑、存储等,并使用客户端语言(HTML和JavaScript)进行呈现。
- 移动端应用程序:HTTP 协议是移动应用程序与 Web 服务器之间的通信协议。由于JSON格式的简单性,它已经成为常用的 API 响应格式来传输数据。下面是 JSON 格式的 API 响应示例:
GET /users/12 (检索 id = 12 的用户对象)
json
{
"id":12,
"firstName":"John",
"lastName":"Smith",
"address":{
"streetAddress":"21 2nd Street",
"city":"New York",
"state":"NY",
"postalCode":10021
},
"phoneNumbers":[
"212 555-1234",
"646 555-4567"
]
}
数据库
随着用户量的增长,一台服务器已经不够了,我们需要多台服务器:一台用于 Web/移动端流量,另一台用于数据库(图 3)。将 Web/移动端流量(Web 层)和数据库(数据层)服务器分开,使得它们能够进行独立扩展。
图3
使用哪些数据库?
你可以在传统关系数据库和非关系数据库之间进行选择。让我们来看看它们的差异。
关系数据库也称为关系数据库管理系统 (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服务器。
图 4 展示了负载均衡器的工作原理。
图4
如图4所示,用户直接连接到负载均衡器的公网IP。增加了负载均衡器之后,客户端将无法再直接访问 Web 服务器。为了提升安全性,服务器之间的通信都使用私有IP。私有IP是只能在同一网络中的服务器之间才能访问的IP地址,因此无法通过互联网访问到这个IP地址。负载均衡器通过私有IP与Web服务器进行通信。
在图 4 中,增加了负载均衡器和第二台Web服务器后,我们成功解决了"不支持故障转移"的问题,并提高了 Web 层的可用性。详细说明:
- 如果服务器1离线,则所有流量都会路由到服务器2。这样可以防止网站访问不了。同时,我们还将向服务器池中增加一个新的健康的 Web 服务器,来重新平衡负载(替换已经离线的服务器1)。
- 如果网站的流量增长得很快,两台服务器不足以支撑这些流量,那么通过负载均衡器可以优雅地解决这个问题。我们只需要向Web服务器池中增加更多的服务器,负载均衡器就会自动开始向新增的服务器发送请求。
现在 Web 层看起来不错,那么数据层呢?当前的设计只有一个数据库,因此不支持故障转移和冗余。数据库复制是解决这些问题的常用技术。让我们来看看吧。
数据库复制
引自维基百科:"数据库复制技术可以在许多数据库管理系统中使用,通常在原始(主数据库)和副本(从数据库)之间具有主/从关系"[3]。
主数据库一般只支持写操作。从数据库从主数据库获取数据的副本,并且仅支持读操作。所有的数据修改命令(例如插入、删除或更新)都必须发送到主数据库。
由于大多数应用程序通常读取操作要远大于写入操作,因此,系统中从数据库的数量通常要大于主数据库的数量。图5展示了一个主数据库和多个从数据库的架构。
图5
数据库复制的优点:
-
更好的性能:在主从模型中,所有写入和更新都发生在主节点上;而读操作分布在从节点上。该模型提高了性能,因为它允许并行处理更多的查询。
-
可靠性:如果一台数据库服务器因自然灾害(例如台风或地震)而损坏,数据仍然可以被保存下来。所以无需担心数据会丢失的问题,因为数据是跨多个存储位置进行复制的。
-
高可用性:将数据复制到不同位置的数据库之后,即使数据库离线了,网站也可以保持正常运行,因为网站可以访问存储在另一数据库服务器中的数据。
在上一节中,我们讨论了负载均衡器是如何帮助提高系统可用性的。在这里我们提同样的一个问题:如果其中一个数据库不可用了怎么办?图 5 中讨论的架构设计可以解决这种情况:
-
如果只有一个从库可用,且从库不可用了,则读操作将会暂时转向访问主库。一旦发现了从库不可用的问题,新的从库将会取代旧的从库。当有多个从库可用时,读操作会重定向到其他正常运行中的从库。新的数据库服务器将会取代旧的数据库服务器。
-
如果主数据库不可用了,则从数据库将会被提升为新的主数据库。所有的数据库操作都将暂时访问新的主数据库。新的从库将会立即替换旧的从库,并复制数据到新的从库。在生产环境中,升级新的主数据库会更加复杂,因为从数据库中的数据可能不是最新的。需要通过运行数据恢复脚本来更新丢失的数据。尽管使用其他的一些复制方法(例如多主复制和循环复制)可能会有所帮助,但是这些方案会更加复杂;关于这些方案的讨论已经超出了本文的范围。有兴趣的读者可以阅读文末的参考资料[4][5]。
图 6 展示了增加负载均衡器和数据库复制之后的系统架构。
图6
让我们看一下这个设计方案:
- 用户从 DNS 获取负载均衡器的 IP 地址。
- 用户使用此 IP 地址连接负载均衡器。
- HTTP 请求被路由到服务器 1 或服务器 2。
- Web 服务器访问从数据库,读取用户数据。
- Web 服务器将任何数据变更的操作都路由到主数据库。数据变更操作包括写入、更新和删除。
现在,你对 Web层和数据层已经有了深入的了解,是时候改进加载/响应时间了。这可以通过增加缓存层,并将静态内容(JavaScript/CSS/图像/视频文件)转移到内容分发网络(CDN)来完成。
缓存
缓存是一个临时的存储区域,通常将操作成本较高的响应结果或频繁访问的数据存储在内存中,以便更快地处理后续的请求。如图 6 所示,每次访问新的网页时,都会执行一个或多个数据库调用来获取数据。但是重复调用数据库对应用程序的性能影响很大。而使用缓存可以来缓解这个问题。
缓存层
缓存层是一个临时的数据存储层,比数据库快很多。设计独立的缓存层的好处,包括提供更好的系统性能、减少数据库工作负载以及具备独立扩展缓存层的能力。图 7 展示了缓存服务器的一种方案:
图7
收到请求后,Web 服务器首先检查缓存中是否有可以响应的数据。如果有,则直接将数据返回给客户端。如果没有,则先查询数据库,再将查询结果存储在缓存中,最后将数据返回给客户端。这种缓存策略称为穿透读。根据数据类型、大小和访问模式,还可以使用其他缓存策略。不同的缓存策略是如何工作的请参考[6]。
使用缓存服务器很简单,因为大多数缓存服务都提供了常用编程语言的API。以下代码片段展示了Memcached的基本API:
vbnet
SECONDS = 1
cache.set('myKey, 'hi there', 3600 * SECONDS)
cache.get('myKey')
使用缓存的注意事项
以下是使用缓存系统的一些注意事项:
- 决定何时使用缓存:当数据被频繁读取但不频繁修改时,可以考虑使用缓存。由于缓存数据存储在易丢失的内存中,因此缓存服务并不适合持久保存数据。例如,如果缓存服务器重启,那么内存中的所有数据都会丢失。因此,重要的数据应该保存在具备持久存储能力的数据存储中。
- 过期策略:实现过期策略是一个很好的实践。一旦缓存中的数据过期了,就会从缓存中删除该数据。如果没有采用过期策略,那么缓存中的数据将永久保存在内存中。建议不要将过期日期设置得太短,因为这会导致系统过于频繁地从数据库中重新加载数据。同时,也不要将过期日期设置得太长,否则数据可能会过时(无法及时从数据库中同步最新的数据)。
- 一致性:这涉及保持数据存储和缓存之间的同步。由于数据存储和缓存上的数据修改操作不在单个事务中,因此可能会发生不一致。在跨多个区域之间进行扩展时,如何确保数据存储和缓存之间的一致性将是一项挑战。有关更多的详细信息,请参阅 Facebook 发表的题为"Scaling Memcache at Facebook"的论文[7]。
- 缓解故障:单个缓存服务器意味着存在潜在的单点故障 (SPOF)风险,维基百科中的定义如下:"单点故障 (SPOF) 是,如果系统的一部分发生故障,将导致整个系统停止工作"[8]。因此,为了避免发生单点故障,建议在不同的数据中心部署多个缓存服务器。另一种推荐的方法是,按照一定百分比超额配置所需的内存。这在内存使用量增加时,提供了一个缓冲区。
图8
- 淘汰策略:当缓存已满时,任何向缓存中添加元素的请求都可能导致现有元素被移除,这被称为缓存淘汰。最近最少使用(LRU)是最受欢迎的缓存淘汰策略。其他淘汰策略,如最不频繁使用(LFU)或先进先出(FIFO),可以根据不同的使用场景进行采用。
内容分发网络 (CDN)
CDN是一个地理分散的服务器网络,用于传递静态内容。CDN服务器缓存如图片、视频、CSS、JavaScript文件等静态内容。
动态内容缓存是一个相对较新的概念,超出了本课程的范围。它允许缓存基于请求路径、查询字符串、cookie 和请求头的 HTML 页面。有关更多信息,请参阅参考资料 [9] 中提到的文章。本课程重点介绍如何使用CDN来缓存静态内容。
以下是CDN的基本工作原理:当用户访问某个网站时,最接近用户的CDN服务器将会提供静态内容。直观地说,用户与CDN服务器的距离越远,网站的加载速度就越慢。例如,如果CDN服务器位于旧金山,那么洛杉矶的用户会比欧洲的用户更快地获取到内容。图9是一个很好的例子,展示了CDN是如何改进加载时间的。
图9
图 10 展示了 CDN 的工作流程。
图10
- 用户A试图通过使用图片URL获取image.png。其中,URL的域名由CDN厂商提供。下面的两个图片URL,用来演示在Amazon和Akamai的CDN上的图片URL示例:
https://mysite.cloudfront.net/logo.jpg
https://mysite.akamai.com/image-manager/img/logo.jpg
-
如果 CDN 服务器的缓存中没有 image.png,则 CDN 服务器会向源站请求该文件,该源站可以是 Web 服务器或 Amazon S3 等在线存储服务。
-
源站将 image.png 返回给 CDN 服务器,其中包含了可选的 HTTP 头"生存时间 (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。
图11显示了添加CDN和缓存后的架构。
图11
- 静态资源(如JS, CSS, 图片等)不再由Web服务器提供。为了获得更好的性能,静态资源直接从CDN中获取。
- 通过缓存数据,数据库的负载得到了减轻。
无状态 Web 层
现在是时候考虑水平扩展 Web 层了。为此,我们需要将状态(例如用户会话数据)移出 Web 层。一个好的实践是将会话数据存储在持久存储中,例如关系数据库或 NoSQL。集群中的每个 Web 服务器都可以访问数据库中的状态数据。这被称为无状态 Web 层。
有状态架构
有状态服务器和无状态服务器有一些关键区别。有状态服务器会记住从一个请求到下一个请求的客户端数据(状态)。无状态服务器则不保留状态信息。
图 12 展示了一个有状态架构的示例。
图12
在图 12 中,用户 A 的会话数据和个人图片存储在服务器 1 中。要对用户 A 进行身份验证,必须将 HTTP 请求路由到服务器 1。如果将请求发送到其他的服务器,例如服务器 2,则身份验证将会失败,因为服务器 2 不包含用户 A 的会话数据。同样,来自用户 B 的所有 HTTP 请求都必须路由到服务器 2;来自用户 C 的所有请求都必须发送到服务器 3。
问题是,来自同一个客户端的每个请求都必须路由到同一台服务器。这可以通过大多数负载均衡器中的粘性会话来实现[10];然而,这会增加开销。而且使用这种方法之后,在添加或删除服务器时会变得更加困难,在处理服务器故障方面也是一项挑战。
无状态架构
图 13 展示了无状态架构。
图13
在这种无状态架构中,来自用户的 HTTP 请求可以发送到任何 Web 服务器,这些服务器从共享数据存储中获取状态数据。状态数据存储在共享数据存储中,并且保存在 Web 服务器之外。无状态系统更简单、更健壮,且可扩展。
图 14 展示了具有无状态 Web 层的设计架构。
图14
在图 14 中,我们将会话数据移出 Web 层并将它们存储在持久数据存储中。共享数据存储可以是关系数据库、Memcached/Redis、NoSQL等。选择NoSQL数据存储是因为它易于扩展。自动伸缩是指根据流量负载自动增加或删除 Web 服务器。从 Web 服务器中删除状态数据后,可以通过根据流量负载增加或删除服务器来轻松实现 Web 层的弹性伸缩。
你的网站发展迅速,并吸引了大量的国际用户。为了提高可用性并在更广泛的地理区域提供更好的用户体验,支持多个数据中心至关重要。
数据中心
图 15 展示了具有两个数据中心的架构示例。在正常操作中,用户通过 geoDNS 路由(也称为地理路由)到最近的数据中心,美国东部的流量分配为x% ,美国西部的流量分配为 (100 -- x)% 。 geoDNS 是一项 DNS 服务,允许根据用户的地理位置将域名解析为相应的 IP 地址。
图15
如果数据中心发生了严重的中断,我们会将所有流量引导至运行正常的数据中心。在图 16 中,数据中心 2(美国西部)离线了,流量被 100% 路由到了数据中心 1(美国东部)。
图16
要实现多数据中心的部署方案,必须解决几个技术挑战:
- 流量重定向:需要提供有效的工具将流量引导到正确的数据中心。GeoDNS 可用于根据用户所在位置将流量引导至最近的数据中心。
- 数据同步:不同地区的用户可以使用不同的本地数据库或缓存。在发生故障转移的情况下,流量可能会被路由到数据不可用的数据中心。一个常见的策略是跨多个数据中心复制数据。先前的一项研究展示了Netflix如何实现异步多数据中心复制[11]。
- 测试和部署:使用多数据中心进行部署,在不同的地理位置测试网站/应用程序非常重要。自动化部署工具对于保持所有数据中心的服务一致性至关重要[11]。
为了进一步扩展我们的系统,我们需要将系统的不同组件解耦,以便可以独立地对它们进行扩展。消息队列是许多现实中的分布式系统用来解决这个问题的一个关键策略。
消息队列
消息队列是一个消息传递组件,有纯内存型和持久化存储的方案,支持异步通信。它充当一个消息缓冲区,并分发异步请求。
消息队列的基本架构很简单。生产者/发布者服务,负责创建消息并将其发布到消息队列。消费者/订阅者服务,则连接到队列,并执行消息定义的操作。基本架构如图 17 所示。

图17
解耦使得消息队列成为构建可扩展且可靠的应用程序的首选架构。使用消息队列,当消费者无法处理消息时,生产者仍然可以将消息发布到队列中。即使生产者不可用了,消费者也可以从队列中读取消息。
考虑以下案例:你的应用程序支持照片自定义,包括裁剪、锐化、模糊等。这些自定义任务需要花一些时间才能完成。在图 18 中,Web 服务器将照片处理任务发布到消息队列中。照片处理消费者从消息队列中获取任务,并异步执行照片定制任务。生产者和消费者可以独立地进行扩展。当队列中的任务很多时,通过添加更多的消费者以减少处理时间。但是,如果队列大部分的时间都是空的,则可以减少消费者的数量。
图18
日志记录、指标、自动化
当维护一个运行在几台服务器上的小型网站时,日志记录、指标和自动化支持是一个很好的实践,但这不是必需的。然而,现在你的网站已经发展成为一个可以服务于庞大业务的平台,那么投资于这些工具就变得至关重要了。
日志记录:监控错误日志很重要,因为它有助于发现系统中的错误和问题。可以在每个服务器级别监控错误日志,或者使用工具将它们聚合到一个集中式的服务中,以便于进行搜索和查看。
指标:收集不同类型的指标有助于我们洞察业务情况,并了解系统的健康状况。
以下是一些有用的指标:
- 主机级别指标:CPU、内存、磁盘 I/O 等。
- 聚合级别指标:例如整个数据库层、缓存层的性能等。
- 关键业务指标:活跃用户数、留存率、收入等。
自动化:当系统变得庞大而复杂时,我们需要构建或利用自动化工具来提高生产效率。持续集成是一种很好的实践,其中每次代码提交都经过自动化验证,使得团队能够及早地发现问题。此外,自动化构建、测试、部署过程等可以显著提高开发人员的工作效率。
图 19 展示了增加消息队列和不同的工具之后的设计。由于篇幅限制,图中仅展示了一个数据中心。
- 该设计包含一个消息队列,有助于使系统更松散耦合,提升容错能力。
- 包含了日志记录、监控、指标和自动化工具。
图19
随着每天数据量的增长,数据库的负载变得越来越重。是时候扩展数据层了。
数据库扩展
数据库扩展有两种主要方法:垂直扩展和水平扩展。
垂直扩展
垂直扩展,也称为纵向扩展,是通过提升现有机器的配置(CPU、RAM、磁盘等)来进行性能的扩展。有一些配置很高的数据库服务器,功能非常强大。根据 Amazon Relational Database Service (RDS) [12]的描述,你可以获得具有 24 TB 内存的数据库服务器,这种强大的数据库服务器可以存储和处理大量的数据。例如,2013 年 stackoverflow.com 每月的独立访客超过了 1000 万,但是它只有 1 台主数据库 [13]。然而,垂直扩展也存在一些严重的缺点:
- 可以向数据库服务器添加更多的 CPU、内存等资源,但是存在硬件级别的上限。如果你拥有大量的用户,那么单台服务器是不够的。
- 单点故障的风险更大。
- 垂直扩展的总体成本很高。而且强大的服务器通常要贵得多。
水平扩展
水平扩展,也称为分片,采用了增加更多服务器的实现方法。图 20 比较了垂直扩展和水平扩展。
图20
分片将大型数据库分割成了更小、更容易管理的多个部分,称为分片。每个分片都共享相同的架构(schema),但每个分片上所存储的实际数据对于该分片来说是唯一的。
图 21 展示了一个分片数据库的示例。根据用户ID,将用户的数据分配到了对应的分片数据库服务器。每当访问数据时,都会使用哈希函数来查找相应的分片。在我们的示例中,使用user_id % 4作为哈希函数。 如果哈希计算结果等于0,则使用分片0来存储和查询数据。如果结果等于 1,则使用分片 1。同样的逻辑也适用于其他的分片。

图21
图 22 展示了分片数据库中的用户表。

图22
实施分片策略时,需要考虑的最重要的因素是对分片键的选择。分片键(也称为分区键)由一个或多个列组成,用于确定数据的分布方式。如图22所示, "user_id" 是分片键。通过将数据库查询路由到正确的数据库,分片键允许你高效地检索和修改数据。在选择分片键时,最重要的标准之一是选择一个能够均匀分布数据的键。
分片是一种很好的扩展数据库的技术,但它远非完美的解决方案。它给系统带来了复杂性和新的挑战:
重新分片数据:1)当单个分片由于快速增长而无法再容纳更多的数据时,需要重新分片数据。 2)由于数据分布不均匀,某些分片可能比其他分片更快地耗尽。当分片耗尽时,需要更新分片函数并迁移数据。一致性哈希是解决这个问题的常用技术。
名人问题:这也称为热点键问题。对特定分片的过多访问可能会导致服务器过载。想象一下,凯蒂·佩里 (Katy Perry)、贾斯汀·比伯 (Justin Bieber) 和 Lady Gaga 的数据最终都落在同一个分片上。对于一个社交应用,该分片将会因为大量的读取操作而不堪重负。为了解决这个问题,我们可能需要为每个名人分配一个分片。每个分片甚至可能还需要进一步的分区。
连接和反范式:一旦数据库被分片到多个服务器上,跨数据库分片执行连接操作就变得很困难。一个常见的解决方法是对数据库进行反范式设计,以便可以在单个表中执行查询。
在图 23 中,我们对数据库进行分片以支持快速增长的数据流量。同时,将一些非关系型的功能转移到NoSQL数据存储中,以减少数据库的负载。这里有一篇文章介绍了NoSQL的许多用例 [14]。
图23
数百万及以上的用户规模
扩展一个系统是一个迭代的过程。根据我们在本文中学到的知识进行迭代,可以让我们走得更远。为了扩展到支持数百万以上的用户规模,需要进行更多的配置微调和引入新的策略。例如,你可能需要对系统进行优化,并将系统解耦为更小的服务。本文学到的所有技术知识,为应对新的挑战提供了良好的基础。
作为本文的总结,下面简要的说明了如何扩展系统以支持数百万用户:
- 保持 Web 层无状态
- 为每一层建立冗余
- 尽可能多地缓存数据
- 支持多个数据中心
- 在 CDN 中托管静态资源
- 通过分片来扩展数据层
- 将每一层拆分为单独的服务
- 监控系统,并使用自动化工具
祝贺你走到了这一步!现在请给自己一点鼓励吧!干得好!
参考资料
[1] Hypertext Transfer Protocol: en.wikipedia.org/wiki/Hypert...
[2] Should you go Beyond Relational Databases?:
blog.teamtreehouse.com/should-you-...
[3] Replication: en.wikipedia.org/wiki/Replic...
[4] Multi-master replication:
en.wikipedia.org/wiki/Multi-...
[5] NDB Cluster Replication: Multi-Master and Circular Replication:
dev.mysql.com/doc/refman/...
[6] Caching Strategies and How to Choose the Right One:
codeahoy.com/2017/08/11/...
[7] R. Nishtala, "Facebook, Scaling Memcache at," 10th USENIX Symposium on Networked Systems Design and Implementation (NSDI '13).
[8] Single point of failure: en.wikipedia.org/wiki/Single...
[9] Amazon CloudFront Dynamic Content Delivery:
aws.amazon.com/cloudfront/...
[10] Configure Sticky Sessions for Your Classic Load Balancer:
docs.aws.amazon.com/elasticload...
[11] Active-Active for Multi-Regional Resiliency:
netflixtechblog.com/active-acti...
[12] Amazon EC2 High Memory Instances:
aws.amazon.com/ec2/instanc...
[13] What it takes to run Stack Overflow:
nickcraver.com/blog/2013/1...
[14] What The Heck Are You Actually Using NoSQL For:
highscalability.com/blog/2010/1...
公众号:二进制之路