系统设计
嘿,欢迎来到本课程。希望本课程能给您带来良好的学习体验。
这门课程也可以在我的网站上找到,也可以在leanpub上找到电子书。如果觉得有帮助,请留下⭐作为鼓励!
目录
-
入门
-
第一章
-
第二章
-
第三章
-
第四章
- …
让我们设计一个类似Twitter的社交媒体服务,类似于Facebook、Instagram等服务。
Twitter 是一项社交媒体服务,用户可以阅读或发布短消息(最多 280 个字符),即推文。它可在网页端以及 Android 和 iOS 等移动平台上使用。
我们的系统应满足以下要求:
让我们从估计和约束开始。
注意:请务必与面试官核对任何与规模或交通相关的假设。
这将是一个阅读量很大的系统。假设我们拥有 10 亿用户,其中每日活跃用户 (DAU) 为 2 亿,平均每位用户每天发推文 5 条。这样,我们每天的推文量就达到了 10 亿条。
每天 10 亿个请求相当于每秒 12000 个请求。
如果我们假设每条消息平均为 100 字节,那么我们每天将需要大约 100 GB 的数据库存储空间。
由于我们的系统每天要处理 5.1 TB 的入口数据,因此我们需要每秒约 60 MB 的最低带宽。
以下是我们的高级估计:
类型 | 估计 |
---|---|
每日活跃用户(DAU) | 1亿 |
每秒请求数 (RPS) | 12K/秒 |
存储(每天) | ~5.1 TB |
储存(10年) | ~19 PB |
带宽 | ~60 MB/s |
这是反映我们要求的通用数据模型。
我们有下表:
用户
该表将包含用户的信息,例如,,,name
和其他详细信息。email
dob
推文
顾名思义,该表将存储推文及其属性,例如type
(文本,图像,视频等)content
等。我们还将存储相应的userID
。
收藏夹
该表将推文与用户进行映射,以实现我们应用程序中的收藏推文功能。
追随者
该表将关注者和被关注者映射为用户可以互相关注(N:M 关系)。
提要
该表存储了饲料属性及其相应的属性userID
。
feeds_tweets
该表映射了推文和 feed(N:M 关系)。
虽然我们的数据模型看起来相当相关,但我们不一定需要将所有内容存储在单个数据库中,因为这会限制我们的可扩展性并很快成为瓶颈。
我们将数据拆分到不同的服务,每个服务拥有特定表的所有权。然后,我们可以将关系数据库(例如PostgreSQL)或分布式 NoSQL 数据库(例如Apache Cassandra)用于我们的用例。
让我们为我们的服务做一个基本的 API 设计:
该 API 将允许用户在平台上发布推文。
postTweet(userID: UUID, content: string, mediaURL?: string): boolean
参数
用户 ID(UUID
):用户的 ID。
内容(string
):推文的内容。
媒体 URL ( string
):附加媒体的 URL (可选)。
返回
结果(boolean
):表示操作是否成功。
此 API 将允许用户关注或取消关注其他用户。
follow(followerID: UUID, followeeID: UUID): boolean
unfollow(followerID: UUID, followeeID: UUID): boolean
参数
关注者ID(UUID
):当前用户的ID。
关注者 ID(UUID
):我们想要关注或取消关注的用户的 ID。
媒体 URL ( string
):附加媒体的 URL (可选)。
返回
结果(boolean
):表示操作是否成功。
此 API 将返回在给定新闻源中显示的所有推文。
getNewsfeed(userID: UUID): Tweet[]
参数
用户 ID(UUID
):用户的 ID。
返回
推文(Tweet[]
):在给定的新闻源中显示的所有推文。
现在让我们对我们的系统进行高层设计。
我们将使用微服务架构,因为它可以更轻松地水平扩展和解耦我们的服务。每个服务都拥有自己的数据模型。让我们尝试将系统划分为几个核心服务。
用户服务
该服务处理与用户相关的问题,例如身份验证和用户信息。
新闻推送服务
该服务将处理用户新闻推送的生成和发布。具体细节我们将另行讨论。
推特服务
推文服务将处理与推文相关的用例,例如发布推文、收藏等。
搜索服务
该服务负责处理与搜索相关的功能。我们将另行详细讨论。
媒体服务
此服务将处理媒体(图片、视频、文件等)的上传。我们将另行详细讨论。
通知服务
该服务只会向用户发送推送通知。
分析服务
该服务将用于指标和分析用例。
服务间通信和服务发现怎么样?
由于我们的架构基于微服务,因此服务之间也会相互通信。通常,REST 或 HTTP 的性能表现良好,但我们可以使用更轻量、更高效的gRPC来进一步提升性能。
服务发现是我们需要考虑的另一件事。我们还可以使用服务网格,实现各个服务之间可管理、可观察且安全的通信。
注意:了解有关REST、GraphQL、gRPC 的更多信息以及它们之间的比较。
说到新闻推送,实现起来似乎很容易,但有很多因素会影响这个功能的成败。所以,让我们把问题分成两部分:
一代
假设我们要为用户 A 生成 feed,我们将执行以下步骤:
生成推文是一个繁琐的过程,可能会耗费大量时间,尤其是对于关注人数较多的用户而言。为了提升性能,可以预先生成推文并将其存储在缓存中,然后我们可以通过一种机制定期更新推文,并将我们的排名算法应用于新推文。
出版
发布是根据每个特定用户推送动态数据的步骤。这可能是一个相当繁重的操作,因为一个用户可能有数百万的好友或粉丝。为了解决这个问题,我们有三种不同的方法:
当用户创建推文,关注者刷新其新闻源时,该新闻源会被创建并存储在内存中。只有当用户请求时,才会加载最新的新闻源。这种方法减少了数据库的写入操作次数。
这种方法的缺点是,除非用户从服务器“拉”数据,否则他们将无法查看最新的信息,这将增加服务器上的读取操作次数。
在这个模型中,用户一旦创建推文,就会立即“推送”到所有关注者的动态。这样一来,系统就无需逐一检查用户的整个关注者列表来查看更新。
然而,这种方法的缺点是它会增加数据库的写入操作次数。
第三种方法是拉动模型和推动模型之间的混合模型。它结合了上述两种模型的优点,并试图在两者之间提供一种平衡的方法。
混合模型仅允许关注者数量较少的用户使用推送模型,而对于关注者数量较多的用户(名人),将使用拉取模型。
正如我们所讨论的,我们需要一个排名算法来根据每条推文与每个特定用户的相关性对其进行排名。
例如,Facebook 曾经使用过EdgeRank算法,其中每个 feed 项的排名由以下公式描述:
Affinity
:表示用户与边线创建者的“亲密度”。如果用户经常点赞、评论或留言给边线创建者,那么亲密度值就会更高,从而导致帖子排名更高。
Weight
:是根据每条边分配的值。评论的权重可能比点赞更高,因此评论较多的帖子更有可能获得更高的排名。
Decay
:衡量边的生成时间。边越老,衰减值越小,最终的等级就越低。
如今,算法变得更加复杂,排名是使用机器学习模型来完成的,该模型可以考虑数千个因素。
转发是我们的扩展需求之一。为了实现此功能,我们只需创建一条新推文,其中包含转发原推文的用户 ID,然后修改新推文的type
枚举和content
属性,使其与原推文关联起来。
例如,type
枚举属性可以是 tweet 类型,类似于文本、视频等,并且content
可以是原始推文的 ID。这里第一行表示原始推文,而第二行表示转发推文。
ID | 用户身份 | 类型 | 内容 | 创建于 |
---|---|---|---|---|
ad34-291a-45f6-b36c | 7a2c-62c4-4dc8-b1bb | 文本 | 嘿,这是我的第一条推文…… | 1658905644054 |
f064-49ad-9aa2-84a6 | 6aa2-2bc9-4331-879f | 鸣叫 | ad34-291a-45f6-b36c | 1658906165427 |
这是一个非常基本的实现,为了改进它,我们可以创建一个单独的表来存储转发。
有时,传统的 DBMS 性能不够强,我们需要一些能够快速、近乎实时地存储、搜索和分析海量数据,并在几毫秒内给出结果的工具。Elasticsearch可以帮助我们实现这一目标。
Elasticsearch是一个分布式、免费且开放的搜索和分析引擎,适用于所有类型的数据,包括文本、数字、地理空间、结构化和非结构化数据。它构建于Apache Lucene之上。
我们如何识别热门话题?
趋势功能将基于搜索功能。我们可以缓存最近N
几秒内搜索最频繁的查询、标签和主题,并M
使用某种批处理机制每秒更新一次。我们的排名算法也可以应用于趋势主题,赋予它们更高的权重,并为用户提供个性化服务。
推送通知是任何社交媒体平台不可或缺的一部分。我们可以使用消息队列或消息代理(例如Apache Kafka)配合通知服务,将请求发送到Firebase 云消息传递 (FCM)或Apple 推送通知服务 (APNS),后者负责将推送通知发送到用户设备。
有关更多详细信息,请参阅我们在其中讨论推送通知的Whatsapp系统设计。
现在是时候详细讨论我们的设计决策了。
为了扩展数据库,我们需要对数据进行分区。水平分区(又称分片)是一个很好的第一步。我们可以使用以下分区方案:
上述方法仍然会导致数据和负载分布不均匀,我们可以使用一致性哈希来解决这个问题。
对于共同好友,我们可以为每个用户构建一个社交图谱。图中的每个节点代表一个用户,一条有向边代表关注者和被关注者。之后,我们可以遍历用户的关注者,找到并推荐共同好友。这需要使用像Neo4j和ArangoDB这样的图数据库。
这是一个非常简单的算法,为了提高我们的建议准确性,我们需要结合使用机器学习作为我们算法一部分的推荐模型。
记录分析和指标是我们的扩展需求之一。由于我们将使用Apache Kafka发布各种事件,因此我们可以使用Apache Spark(一个用于大规模数据处理的开源统一分析引擎)来处理这些事件并对数据进行分析。
在社交媒体应用中,我们必须谨慎使用缓存,因为用户期望获取最新数据。因此,为了防止资源使用量激增,我们可以缓存排名前 20% 的推文。
为了进一步提高效率,我们可以在系统 API 中添加分页功能。这项功能对于网络带宽有限的用户来说非常实用,因为他们无需在需要时才检索旧消息。
使用哪种缓存驱逐策略?
我们可以使用Redis或Memcached等解决方案并缓存 20% 的每日流量,但哪种缓存驱逐策略最适合我们的需求?
对我们的系统来说,最近最少使用(LRU)策略可能是一个不错的选择。在这个策略中,我们首先丢弃最近最少使用的键。
如何处理缓存未命中?
每当出现缓存未命中时,我们的服务器可以直接访问数据库并使用新条目更新缓存。
有关详细信息,请参阅缓存。
众所周知,我们的大部分存储空间将用于存储媒体文件,例如图像、视频或其他文件。我们的媒体服务将处理用户媒体文件的访问和存储。
但是,我们可以在哪里大规模存储文件呢?嗯,对象存储就是我们想要的。对象存储将数据文件分解成称为对象的块。然后,它将这些对象存储在一个存储库中,该存储库可以分布在多个联网系统中。我们也可以使用分布式文件存储,例如HDFS或GlusterFS。
内容分发网络 (CDN)可以提高内容可用性和冗余度,同时降低带宽成本。通常,静态文件(例如图像和视频)由 CDN 提供。对于这种情况,我们可以使用Amazon CloudFront或Cloudflare CDN等服务。
让我们识别并解决设计中的单点故障等瓶颈:
为了使我们的系统更具弹性,我们可以执行以下操作:
本文是我在 Github 上提供的开源系统设计课程的一部分。
嘿,欢迎来到本课程。希望本课程能给您带来良好的学习体验。
这门课程也可以在我的网站上找到,也可以在leanpub上找到电子书。如果觉得有帮助,请留下⭐作为鼓励!
入门
第一章
第二章
第三章
第四章