Designing a system that supports millions of users is challenging, and
it is a journey that requires continuous refinement and endless
improvement. In this chapter, we build a system that supports a
single user and gradually scale it up to serve millions of users.
After reading this chapter, you will master a handful of techniques
that will help you to crack the system design interview questions.
设计一个支持数百万用户的系统是具有挑战性的,
这是一个需要不断的提炼和无尽的改进。在本章中,我们构建了一个系统
它支持单个用户,并逐渐扩展到服务数百万用户。在阅读
在本章中,你将掌握一些可以帮助你破解这个系统的技巧
设计面试问题。
千里之行始于足下,建立复杂的系统需要从简单到复杂。从简单的系统开始,所有东西都在一台服务器上运行,Figure1-1显示了单个服务设置的示例,其中所有的内容都在一台服务器上运行,web,app,数据库,缓存等
要理解这种设置,研究请求流和流量源是有帮助的。让我们首先看看请求流(Figure1-2)。
- 用户通过域名访问网站,如api.mysite.com。通常,域名系统(DNS)是由第三方提供的付费服务,而不是由我们的服务器。
- 返回给浏览器或移动应用的IP (Internet Protocol)地址。返回IP地址为15.125.23.214。
- 获取IP地址后,发送HTTP (Hypertext Transfer Protocol)[1]请求直接发送到您的web服务器。
- web服务器返回HTML页面或JSON响应进行呈现。接下来,让我们检查一下流量源。您的web服务器的流量来自两个来源:web应用和移动应用。
Web应用程序:它使用服务器端语言(Java,Python等)的组合来处理业务逻辑、存储等,以及客户端语言(HTML和JavaScript)演示。
移动应用:HTTP协议是移动设备之间的通信协议应用程序和web服务器。JavaScript对象表示法(JSON)是常用的API响应格式传输数据,因为它简单。中的API响应示例
> JSON格式如下:
> GET /users/12 – Retrieve user object for id = 12
随着用户群的增长,一台服务器是不够的,我们需要多台服务器:一台
一个用于web/移动流量,另一个用于数据库流量(figure1-3)。分离网络/移动流量
您可以在传统的关系数据库和非关系数据库之间进行选择。让我们来看看他们之间的差异。
关系数据库也称为关系数据库管理系统(RDBMS)或SQL数据库。最流行的是MySQL, Oracle数据库,PostgreSQL等。
关系数据库在表和行中表示和存储数据。您可以执行join跨不同数据库表使用SQL进行操作。
非关系数据库也称为NoSQL数据库。流行的是CouchDB,Neo4j, Cassandra, HBase, Amazon DynamoDB等 [2].。
这些数据库分为四类:键值存储、图存储、列存储和文档存储。加入非关系数据库通常不支持操作。对于大多数开发人员来说,关系数据库是最好的选择,因为它们一直存在40多年来,它们一直运转良好。但是如果是关系数据库是否适合您的特定用例,关键是要探索超越关系数据库。非关系数据库可能是正确的选择,如果:
垂直扩展,被称为“向上扩展”,意味着增加更多功率(CPU,RAM等)到您的服务器。
水平扩展,称为“向外扩展”,允许您扩展通过向资源池中添加更多服务器。
当流量较低时,垂直扩展是一个很好的选择,垂直扩展的简单性它的主要优势。不幸的是,它有严重的局限性。
水平扩展更适合大规模应用,这是由于垂直扩展,在之前的设计中,用户直接连接到web服务器。如果web服务器离线用户将无法直接访问web服务器。
在另一个场景中,如果许多用户访问Web服务器同时运行,达到Web服务器的负载限制,用户普遍使用响应较慢或连接到服务器失败。负载平衡器是最好的解决这些问题的技术。
负载均衡器将传入的流量均匀地分配给web服务器。负载均衡器工作原理如图figure1-4所示。
如figure1-4所示,用户直接访问负载均衡器的公网IP地址。用这个设置,客户端无法直接访问web服务器。为了更好的安全,私人ip用于服务器之间的通信。私网IP是指可访问的IP地址
在同一网络中的服务器之间;然而,它无法通过互联网访问。负载balancer通过私有ip与web服务器通信。在ifgure1-4中,添加负载均衡器和第二个web服务器后,我们成功了没有解决故障转移问题,提高了web层的可用性。详细说明下图:
•如果服务器1离线,所有流量将被路由到服务器2。这可以防止网站从下线。我们还将向服务器池中添加一个新的健康web服务器平衡负载。
•如果网站流量增长迅速,两台服务器不足以处理流量,
负载平衡器可以很好地处理这个问题。您只需要添加更多的服务器到web服务器池,负载均衡器自动开始向它们发送请求。web层看起来不错,那么数据层呢?目前的设计只有一个数据库,因此它不支持故障转移和冗余。数据库复制是一种常用技术来解决这些问题。让我们看一看。
引用自Wikipedia:“数据库复制可用于许多数据库管理系统,通常在原始(主)和副本之间具有主/从关系(奴隶)”[3]。主数据库通常只支持写操作。从主数据库获得数据的副数据,只支持读操作。所有的数据修改插入、删除或更新等命令必须发送到主数据库。**大多数应用程序需要更高的读与写比率;因此,奴隶的数量系统中的数据库数量通常大于主数据库的数量。**figure1-5所示一个带有多个从数据库的主数据库。
数据库复制的优点:
在前一节中,我们讨论了负载平衡器如何帮助改进系统可用性。我们在这里问同样的问题:如果其中一个数据库离线了怎么办?figure 1-5中讨论的架构设计可以处理这种情况:
尽管其他一些复制方法,如多主机和循环复制会有帮助,那些设置更复杂;他们的讨论是超出了本书的范围。有兴趣的读者可参考所列的参考资料材料[4][5]。
添加负载均衡器和数据库复制后的系统设计如figure1-6所示。
让我们来看看设计:
现在,您已经对web和数据层有了坚实的了解,是时候改进了加载/响应时间。这可以通过添加缓存层和移动静态内容来实现(JavaScript/CSS/图片/视频文件)到CDN (content delivery network)。
缓存是一个临时存储区域,用于存储昂贵响应或频繁响应的结果,内存中已访问的数据,以便更快地处理后续请求。作为说明在figure1-6中,每次加载一个新的网页时,都会执行一个或多个数据库调用获取数据。反复调用数据库对应用程序性能影响很大。缓存可以缓解这个问题。
缓存层是一个临时数据存储层,比数据库快得多。的好处拥有独立的缓存层包括更好的系统性能,减少数据库的能力工作负载,以及独立扩展缓存层的能力。如figure1-7所示设置缓存服务器:
在收到请求后,web服务器首先检查缓存是否有可用。如果有它把数据发送回客户端。如果没有,则查询数据库,将响应存储在缓存,并将其发送回客户端。这种缓存策略称为透读缓存。根据数据类型、大小和访问模式,可以使用其他缓存策略。一个先前的研究解释了不同的缓存策略是如何工作的[6]。
与缓存服务器交互很简单,因为大多数缓存服务器为以下对象提供api通用编程语言。下面的代码片段显示了典型的Memcached api:
以下是使用缓存系统的一些注意事项:
CDN是由地理位置分散的服务器组成的网络,用于传递静态内容。CDN服务器缓存静态内容,如图像,视频,CSS, JavaScript文件等。动态内容缓存是一个相对较新的概念,超出了本书的范围。它启用基于请求路径、查询字符串、cookie的HTML页面缓存。以及请求头。有关的更多信息,请参阅参考资料[9]中提到的文章这一点。这本书的重点是如何使用CDN缓存静态内容。以下是CDN在高层的工作原理:当用户访问网站时,CDN服务器最接近向用户传递静态内容。直观地看,大量的用户来自CDN服务器,使得网站加载速度变慢。例如,如果CDN服务器在旧金山,那么在洛杉矶的用户洛杉矶将比欧洲用户更快地获得内容。figure1-9是一个很好的例子CDN如何改善加载时间。
CDN流程如figure1-10所示。
成本:cdn由第三方提供商运行,您需要为数据传输付费并离开CDN。缓存不经常使用的资产没有显著的好处你应该考虑将它们移出CDN。
现在是时候考虑横向扩展web层了。为此,我们需要移动状态(例如用户会话数据)。一个好的做法是将会话数据存储在持久存储,如关系数据库或NoSQL。集群中的每个web服务器可以从数据库访问状态数据。这被称为无状态web层。
有状态服务器和无状态服务器有一些关键的区别。有状态服务器进行记忆从一个请求到下一个请求的客户端数据(状态)。无状态服务器不保留任何状态信息。figure1-12是一个有状态架构的示例。
如figure1-12所示,用户A的会话数据和配置文件镜像存储在服务器1上。进行身份验证用户A的HTTP请求必须路由到服务器1。如果请求被发送到其他服务器,如服务器2,身份验证将失败,因为服务器2不包含用户A的会话数据。类似地,来自用户B的所有HTTP请求必须路由到服务器2;所有来自用户的请求C必须发送到服务器3。
问题是来自同一个客户端的每个请求都必须路由到同一个服务器。这可以在大多数负载平衡器[10]中使用粘性会话;然而,这增加了开销。使用这种方法添加或删除服务器要困难得多。这也是处理服务器故障具有挑战性。
无状态架构如figure1-13所示。
在这种无状态体系结构中,来自用户的HTTP请求可以发送到任何web服务器
从共享数据存储中获取状态数据。状态数据存储在共享数据存储中并保存退出web服务器。无状态系统更简单、更健壮、可扩展。figure1-14展示了无状态web层的更新设计。
在figure1-14中,我们将会话数据移出web层并将其存储在持久化中数据存储。共享的数据存储可以是关系数据库、Memcached/Redis、NoSQL、等。选择NoSQL数据存储是因为它易于扩展。自动缩放意味着添加或根据流量负载自动移除web服务器。删除状态数据后在web服务器之外,web层的自动扩展很容易通过添加或删除来实现基于流量负载的服务器。你的网站发展迅速,吸引了大量的国际用户
来提高可用性,在更广泛的地理区域提供更好的用户体验,支持多个数据中心至关重要。
figure1-15展示了一个包含两个数据中心的示例设置。正常情况下,用户为geodns路由,也称为地理路由,到最近的数据中心,分割流量为美国东部占x%,美国西部占(100 - x)%。geoDNS是一种允许域的DNS服务根据用户所在位置将名称解析为IP地址。
在任何重大数据中心中断的情况下,我们将所有流量引导到正常的数据中心。
在figure1-16中,数据中心2 (US-West)离线,100%的流量被路由到data
中心1(美国东部)
要实现多数据中心的设置,必须解决以下几个技术挑战:
消息队列是存储在内存中的持久组件,支持异步沟通。它充当缓冲区并分发异步请求。的基本
消息队列的体系结构很简单。输入服务,称为生产者/发布者,负责创建消息,并将它们发布到消息队列。其他服务或服务器,称为消费者/订阅者连接到队列,并执行消息定义的操作。
型号如figure1-17所示。
解耦使得消息队列成为构建可伸缩的和可靠的应用程序。通过消息队列,生产者可以向队列发布消息当使用者无法处理它时。类中读取消息即使生产者不可用也要排队。考虑以下用例:您的应用程序支持照片自定义,包括裁剪,锐化,模糊等。这些定制任务需要时间来完成。在
figure1-18,web服务器将照片处理作业发布到消息队列。照片处理工作者从消息队列中取出作业并异步执行photo定制任务。生产者和消费者可以独立扩展。当队列的大小变大,增加更多的工人来减少处理时间。但是,如果队列大部分时间是空的,则可以减少工人的数量。
当处理一个运行在几个服务器上的小网站时,日志记录、指标和自动化支持是很好的实践,但不是必需的。然而,现在你的网站已经为了服务于大型企业,投资这些工具是必不可少的。
日志记录:监视错误日志很重要,因为它有助于识别错误和问题在系统中。您可以在每个服务器级别监视错误日志,或者使用工具将它们聚合到一个集中的服务,方便搜索和查看。
度量标准:收集不同类型的度量标准有助于我们获得业务洞察力和理解系统的健康状态。以下是一些有用的指标:
更新后的设计如figure1-19所示。由于空间限制,只有一个数据中心如图所示。
有两种广泛的数据库扩展方法:垂直扩展和水平扩展。
垂直扩展,也称为向上扩展,是通过增加更多的功率(CPU, RAM,磁盘等)到现有的机器。有一些功能强大的数据库服务器。根据Amazon Relational Database Service (RDS)[12],您可以获得一个24tb的数据库服务器的RAM。这种功能强大的数据库服务器可以存储和处理大量的数据。
例如,stackoverflow.com在2013年拥有超过1000万的月独立访问量只有有1个主数据库[13]。然而,垂直扩展也有一些严重的缺点:
水平扩展,也称为分片,是添加更多服务器的实践。figure1 -20比较了垂直缩放和水平缩放
分片将大型数据库分成更小、更容易管理的部分,称为分片。每个shard共享相同的模式,尽管每个shard上的实际数据是惟一的碎片。
如figure1-21所示。用户数据被分配到数据库中基于用户id的服务器。每次访问数据时,都会使用散列函数来查找对应的碎片。在我们的示例中,使用user_id % 4作为散列函数。如果结果是= 0时,表示分片0用于存储和获取数据。如果结果等于1,则使用分片1。同样的逻辑也适用于其他分片。
分片数据库中用户表如figure1-22所示。
在实现分片策略时要考虑的最重要的因素是分片键。分片键(称为分区键)由一个或多个列组成
这决定了数据是如何分布的。如figure1-22所示,“user_id”为分片关键。分片键允许您通过路由数据库有效地检索和修改数据查询正确的数据库。在选择分片密钥时,最重要的一点是标准是选择一个能均匀分布数据的键。分片是一种很好的扩展数据库的技术,但它远非完美的解决方案。它给系统带来复杂性和新的挑战:
重新分片数据:1)当单个分片不能再容纳数据时,需要重新分片数据快速增长带来更多数据。2)某些碎片可能会更快地经历碎片耗尽由于数据分布不均匀。当分片耗尽时,它需要
更新分片功能并移动数据。一致哈希,也就是解决这个问题的常用技术。
名人问题:这也被称为热点问题。对某一特定事物的过度访问碎片可能导致服务器过载。想象一下凯蒂·佩里、贾斯汀·比伯和Lady的数据Gaga最后都在同一个分片上。对于社交应用程序,带有读操作,这个分片将不堪重负。为了解决这个问题,我们可能需要为每个人分配一个分片名人。每个分片甚至可能需要进一步的分区。
连接和反规范化:一旦数据库在多个服务器上被分片,它就会被分片难以跨数据库分片执行连接操作。一种常见的解决方法是对数据库进行规范化,以便可以在单个表中执行查询。
在figure1-23中,我们对数据库进行了分片,以支持快速增长的数据流量。同时时间,一些非关系功能转移到NoSQL数据存储中以减少数据库负载。这篇文章涵盖了NoSQL[14]的许多用例。
数百万用户甚至更多 Millions of users and beyond
扩展系统是一个迭代过程。重复我们在本章所学的内容能让我们走远。需要更多的微调和新的策略来扩展到数百万以上用户。例如,您可能需要优化系统并将系统解耦到偶数更小的服务。本章学习的所有技术都应该提供一个良好的基础应对新挑战。在本章的结尾,我们提供了一个如何扩展的总结我们的系统支持数百万用户:
恭喜你走了这么远!现在给自己点鼓励吧。找个好工作!
参考
[1] Hypertext Transfer Protocol: https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol
[2] Should you go Beyond Relational Databases?:
https://blog.teamtreehouse.com/should-you-go-beyond-relational-databases
[3] Replication: https://en.wikipedia.org/wiki/Replication_(computing)
[4] Multi-master replication:
https://en.wikipedia.org/wiki/Multi-master_replication
[5] NDB Cluster Replication: Multi-Master and Circular Replication:
https://dev.mysql.com/doc/refman/5.7/en/mysql-cluster-replication-multi-master.html
[6] Caching Strategies and How to Choose the Right One:
https://codeahoy.com/2017/08/11/caching-strategies-and-how-to-choose-the-right-one/
[7] R. Nishtala, “Facebook, Scaling Memcache at,” 10th USENIX Symposium on Networked
Systems Design and Implementation (NSDI ’13).
[8] Single point of failure: https://en.wikipedia.org/wiki/Single_point_of_failure
[9] Amazon CloudFront Dynamic Content Delivery:
https://aws.amazon.com/cloudfront/dynamic-content/
[10] Configure Sticky Sessions for Your Classic Load Balancer:
https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-sticky-sessions.html
[11] Active-Active for Multi-Regional Resiliency:
https://netflixtechblog.com/active-active-for-multi-regional-resiliency-c47719f6685b
[12] Amazon EC2 High Memory Instances:
https://aws.amazon.com/ec2/instance-types/high-memory/
[13] What it takes to run Stack Overflow:
http://nickcraver.com/blog/2013/11/22/what-it-takes-to-run-stack-overflow
[14] What The Heck Are You Actually Using NoSQL For:
http://highscalability.com/blog/2010/12/6/what-the-heck-are-you-actually-using-nosql-for.html