【翻译: System Design Interview: An Insider’s Guide】
Chapter 1:从零扩展至百万用户(二)
CDN (Content delivery network)
CDN是一个地理位置分散的服务器网络,用于交付静态内容。CDN服务器可以缓存静态内容,如图像、视频、CSS、JavaScript文件等。
动态缓是个很新的概念,不在此次讨论范围,它可以根据请求路径、查询字符串、cookie和request header来缓存的HTML页面,可以参考[9]来获取更多关于动态缓存的内容。这里更加关注如何使用CDN 缓存静态内容。
以下在架构层展示CDN是如何工作的:当用户访问网站时,离用户最近的CDN服务器将提供静态内容。直观地说,用户离CDN服务器的越远,网站加载速度就越慢。例如,如果CDN服务器在旧金山,洛杉矶的用户将获得比欧洲用户更快的访问速度。图1-9展示了CDN如何提高加载时间:
图1-10展示了CDN的工作流程:
-
用户尝试通过图片URL访问 image.png,URL域名是由CDN 供应商提供,下面两个是图片URL分别来自Amazon和Akamai CDN供应商。
-
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缓存image.png并将其返回给用户A, image.png将一直缓存在CDN中,直到TTL过期。
-
用户B发送请求以获取相同的图片。
-
只要TTL未过期,图片就会从缓存中返回。
使用CDN需要考虑的点
-
成本:CDN由第三方提供商运营,向用户收取进出CDN数据的传输费用。缓存哪些不常用的内容不会带来很显著的提升,应该考虑将其移出CDN。
-
设置适当的缓存到期时间(TTL):对于时间敏感的内容,设置缓存到期时间很重要。缓存到期时间既不能太长也不能太短。时间太长,内容长时间无法刷新;时间太短,可能会导致内容从源服务器重复加载到CDN
-
CDN回退:应该考虑网站/应用程序如何应对CDN故障。如果CDN出现临时中断,client应该能够检测到问题,并从数据源请求资源
-
文件失效:通过执行以下操作,在CDN过期之前将文件从CDN中移除:
1)使用CDN供应商提供的API使CDN对象无效。
2)使用object版本控制区分不同版本的对象。可以向URL添加版本号。例如,在查询字符串中添加版本号2: image.png?v=2
图1-11展示了添加cache和CDN的系统设计
无状态网络层(Stateless web tier)
现在考虑水平扩展Web层,为此,我们需要将state 数据移出web层(例如用户session数据)。一个好的做法是将session数据存储在持久性存储中,如关系数据库或NoSQL。集群中的每个web服务器都可以访问数据库中的state数据。这被称为无状态web层。
有状态架构(stateful)
有状态server和无状态server有以下区别: 在多个请求中,有状态server会记住client数据(状态),无状态server不做任何记录。
图1-12展示了有状态(stateful)架构:
在图1-12中,UserA的session数据和个人资料存储在server1中。要对用户A进行身份验证,必须将HTTP请求路由到server1。如果请求被发送到server2等其他服务器,身份验证将失败,因为server2不包含UserA的会话数据。同样,来自user B的所有HTTP请求都必须路由到server2;来自UserC的所有请求都必须发送到server3。
这样会产生问题:来自同一client的每个请求都必须路由到同一server。虽然这可以通过大多数负载平衡器中的粘性 session来实现[10];但这会这增加开销。使用这种方法,添加或删除服务器要困难得多,处理服务器故障也很困难。
无状态架构(stateless)
在这种无状态架构中,用户的HTTP请求可以发送到任何web服务器。web服务器从共享数据库中获取状态数据。状态数据存储在共享数据存储中,不在web服务器内,使得系统更简单、更健壮且可扩展。
图1-14展示了无状态web层的架构设计:
在图1-14中,我们将session数据移出web层,并将其存储在持久数据存储中。共享数据存储可以是关系数据库、Memcached/Redis、NoSQL等。推荐使用NoSQL数据存,因为其易于扩展。自动缩放(Autoscaling)意味着可以根据流量负载自动添加或删除web服务器。从web服务器中删除状态数据后,可以根据流量负载添加或删除服务器,从而轻松实现web层的自动扩展。
当网站发展迅速,在国际上吸引了大量用户。为了在世界范围内提升可用性和更好的用户体验,支持多数据中心至关重要。
数据中心(Data centers)
图1-15显示了带有两个数据中心的架构设计。常规操作中,用户被路由到最近的数据中心,分配到美国东部和美国西部的流量分别为x%和(100–x%)。geoDNS可以提供这样的域名解析服务,它基于用户的地理位置将域名解析为不同IP地址。
如果数据中心出现任何重大故障,我们会将所有流量转移到另外健康的数据中心。在图1-16中,ata center 2 (US-West)处于离线状态,100%的流量路由到data center 1 (US-East).
要实现多数据中心配置,必须解决几个技术难题:
-
流量重定向:需要有效的工具将流量引导到正确的数据中心。根据用户所在的位置,GeoDNS可用于将流量引导至最近的数据中心
-
数据同步:来自不同地区的用户可以使用不同的本地数据库或缓存。在故障转移情况下,流量可能会被路由到无用户数据的数据中心。一种常见的策略是跨多个数据中心复制数据。之前的一项研究显示了Netflix如何实现异步多数据中心备份[11]。
-
测试和部署:使用多数据中心设置,在不同位置测试网站/应用程序非常重要。自动化部署工具对于保证所有数据中心的服务一致性至关重要[11]。
为了进一步扩展我们的系统,我们需要解耦系统的不同组件,以便它们可以独立地进行扩展。消息队列被很多实际分布式系用来解决解耦问题。
消息队列(Message Queue)
消息队列是存储在内存中的持久组件,支持异步通信。它充当缓冲区并分发异步请求。消息队列的基本架构很简单。输入服务(称为生产者/发布者)创建消息,并将其发布到消息队列。其他服务或服务器(称为使用者/订阅者)连接到队列,并执行消息定义的操作。模型如图1-17所示
解耦使得消息队列在构建可扩展、可靠的应用时更受到青睐。通过使用消息队列,当消费者无法处理消息时,生产者依然可以向队列发布消息;生产者不可用时,消费者也可以从队列中读取消息。
考虑下面情况:应用程序支持照片定制,包括裁剪、锐化、模糊等。这些定制任务需要一定时间来完成。在图1-18中,web服务器将照片处理任务发布到消息队列。照片处理worker从消息队列中提取任务,并异步执行照片自定义任务。生产者和消费者可以独立扩展。当队列变大时,会添加更多的er以减少处理时间。但是,如果队列大部分时间都是空的,则可以减少worker的数量。
logging、指标(metric)、自动化
当在少量的服务器上运行的小型网站时,采用log日志、metric和自动化支持是规范的做法,但不是必要的。然而,现在你的网站已经成长为一家大型企业,对这些工具的使用是必不可少的。
logging:监控错误日志很重要,因为它有助于识别系统中的错误和问题。您可以在每个服务器级别监视错误日志,或者使用工具将它们聚合到一个集中的服务中,以便于搜索和查看。 指标:收集不同类型的指标有助于我们获得业务洞察力,并了解系统的健康状态。以下一些指标很有用:
-
主机级指标:CPU、内存、磁盘I/O等。
-
聚合级指标:例如,整个数据库层、缓存层等的性能。
-
关键业务指标:日常活跃用户、保留量、收入等
自动化:当一个系统变得庞大复杂时,我们需要构建或利用自动化工具来提高生产力。持续集成是一种很好的做法,在这种做法中,每个代码签入都通过自动化进行验证,从而允许团队尽早发现问题。此外,自动化构建、测试、部署程序等,可以显著提高开发人员的生产率。
添加了消息队列和不同的工具的架构设计
图1-19显示了更新后的设计。由于空间限制,图中只显示了一个数据中心。1.该设计包括一个消息队列,有助于使系统解耦和故障恢复。 2.包括日志记录、监控、metric和自动化工具
随着数据规模不断增长,数据层也会过载,所以需要扩展数据层
数据库扩展
两种方式扩展:垂直扩展和水平扩展
垂直扩展
垂直扩展也称为向上扩展,是指通过对现有机器的性能(进行扩展。CPU、RAM、磁盘等)。有一些性能很强大的数据库服务器。根据Amazon关系数据库服务(RDS)[12],可支持具有24 TB RAM的数据库服务器。这种强大的数据库服务器可以存储和处理大量数据。例如,stackoverflow.com 在2013年每月有1000多万独立访客,但它只有一个主数据库[13]。然而,垂直扩展也有一些严重的缺点:
-
您可以向数据库服务器添加更多CPU、RAM等,但存在硬件限制。如果用户群很大,单台服务器是不够的
-
单点故障风险更大
-
垂直扩展的总体成本很高。性能强大的服务器要昂贵得多。水平缩放水平缩放也称为分片,是添加更多服务器的实践。图1-20比较了垂直缩放和水平缩放。
分片(sharding)将大型数据库分成更小、更易于管理的部分,称为分片。每个分片共享相同的模式,尽管每个分片上的实际数据对该分片是唯一的。
图1-21显示了分片数据库的示例。根据用户ID将数据分配给不同数据库服务器。访问数据时,使用哈希函数来查找相应的分片。在我们的示例中,使用user_id%4作为哈希函数。如果取余结果等于0,则使用分片0存储和获取数据。如果结果等于1,则使用分片1。同样的逻辑也适用于其他分片。
图1-22显示了分片数据库中的用户表。
在执行sharding策略时最重要因素是选择sharding key。sharding key, 又称为partition key)由一个或多个列组成,这些列决定数据的分布方式。如图1-22所示,“用户id”是sharding key。通过sharding key,路由到正确的数据库,对数据进行修改和检索数据。在选择sharding key时,最重要的标准之一是选择一个可以均匀分布数据的列。
sharding是一种很好的扩展数据库的技术,但它远不是一个完美的解决方案。它给系统带来了新的挑战:
resharding 数据: 1)单个碎片由于快速增长而无法保存更多数据时,需要重新sharding数据。2) 由于数据分布不均,某些shard可能会比其他shard空间被更快使用完。当某个shard空间耗尽时,需要resharding 功能并移动数据。第5章讨论的一致性哈希可以解决这个问题。
名人问题: 这也被称为热点key问题。对特定shard的过度访问可能会导致服务器过载。想象一下,凯蒂·佩里(Katy Perry)、贾斯汀·比伯(Justin Bieber)和Lady Gaga的数据在同一块碎片上。对于社交应用程序,该碎片将被读取操作淹没。为了解决这个问题,需要为每个名人分配一个碎片。每个shard甚至可能需要进一步的分区。
join和反范式: 一旦数据库在多个服务器上被切分,就很难跨数据库执行join操作。常见的解决方法是对数据库进行反范式化,以便在单个表中执行查询。
在图 1-23 中,我们对数据库进行分片操作,以支持快速增长的数据流量。 同时,非关系功能的数据被存入 NoSQL ,以减少数据库负载。 这是一篇涵盖许多 NoSQL 用例的文章[14]。
百万及百万以上的用户
系统的扩展是一个迭代的过程。重复我们在本章学到的知识,让我们走得更远。当用户数达到百万以上时,需要更多细微的调整和新的策略,例如,将系统解耦到更小的服务。本章所学的将为后面的挑战打好基础,
本章主要讲,如何扩展系统以支持数百万用户:
-
保持web层无状态
-
在每一层构建冗余
-
尽可能多地缓存数据
-
支持多个数据中心
-
在CDN中托管静态文件
-
通过分片扩展数据层
-
将每层分为独立的服务
-
为系统添加监控并使用自动化工具
参考文献
[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