系统设计:Twitter 系统设计目录

2025-05-25

系统设计:Twitter

系统设计

目录

让我们设计一个类似Twitter的社交媒体服务,类似于FacebookInstagram等服务。

什么是 Twitter?

Twitter 是一项社交媒体服务,用户可以阅读或发布短消息(最多 280 个字符),即推文。它可在网页端以及 Android 和 iOS 等移动平台上使用。

要求

我们的系统应满足以下要求:

功能要求

  • 应该能够发布新的推文(可以是文本、图像、视频等)。
  • 应该能够关注其他用户。
  • 应该具有新闻推送功能,其中包含用户关注的人的推文。
  • 应该能够搜索推文。

非功能性需求

  • 高可用性和最小延迟。
  • 该系统应具有可扩展性和高效性。

扩展要求

  • 指标和分析。
  • 转发功能。
  • 最喜歡的響言。

估计和约束

让我们从估计和约束开始。

注意:请务必与面试官核对任何与规模或交通相关的假设。

交通

这将是一个阅读量很大的系统。假设我们拥有 10 亿用户,其中每日活跃用户 (DAU) 为 2 亿,平均每位用户每天发推文 5 条。这样,我们每天的推文量就达到了 10 亿条。

200   n × 5   e 一个 e = 1   b n / d 一个 y 2亿\space条\space消息乘以5\space条消息=10亿条/天
推文还可以包含图片或视频等媒体文件。我们可以假设 10% 的推文是用户分享的媒体文件,这意味着我们需要额外存储 1 亿个文件。
10   e r c e n × 1   b n = 100   n / d 一个 y 10 \space 百分比 \times 1 \space 十亿 = 100 \space 百万/天
我们的系统的每秒请求数(RPS)是多少?

每天 10 亿个请求相当于每秒 12000 个请求。

1   b n ( 24   h r × 3600   e c n d ) = 12   r e e / e c n d \frac{1 \space 十亿}{(24 \space 小时 \times 3600 \space 秒)} = \sim 12K \space 请求/秒
### 贮存

如果我们假设每条消息平均为 100 字节,那么我们每天将需要大约 100 GB 的数据库存储空间。

1   b n × 100   b y e = 100   B / d 一个 y 1 \space 十亿 \times 100 \space 字节 = \sim 100 \space GB/天
我们还知道,根据我们的要求,我们每天大约有 10% 的消息(1 亿条)是媒体文件。假设每个文件平均大小为 50 KB,那么我们每天将需要 5 TB 的存储空间。
100   n × 100   B = 5   T B / d 一个 y 1亿\space百万\乘以100\space KB = 5\space TB/天
10 年后,我们将需要大约 19 PB 的存储空间。
( 5   T B + 0.1   T B ) × 365   d 一个 y × 10   y e 一个 r = 19   B (5 \space TB + 0.1 \space TB) \times 365 \space 天 \times 10 \space 年 = \sim 19 \space PB
### 带宽

由于我们的系统每天要处理 5.1 TB 的入口数据,因此我们需要每秒约 60 MB 的最低带宽。

5.1   T B ( 24   h r × 3600   e c n d ) = 60   B / e c n d \frac{5.1 \space TB}{(24 \space 小时 \times 3600 \space 秒)} = \sim 60 \space MB/秒
### 高级估算

以下是我们的高级估计:

类型 估计
每日活跃用户(DAU) 1亿
每秒请求数 (RPS) 12K/秒
存储(每天) ~5.1 TB
储存(10年) ~19 PB
带宽 ~60 MB/s

数据模型设计

这是反映我们要求的通用数据模型。

Twitter 数据模型

我们有下表:

用户

该表将包含用户的信息,例如,,,name其他详细信息。emaildob

推文

顾名思义,该表将存储推文及其属性,例如type(文本,图像,视频等)content等。我们还将存储相应的userID

收藏夹

该表将推文与用户进行映射,以实现我们应用程序中的收藏推文功能。

追随者

该表将关注者和被关注者映射为用户可以互相关注(N:M 关系)。

提要

该表存储了饲料属性及其相应的属性userID

feeds_tweets

该表映射了推文和 feed(N:M 关系)。

我们应该使用什么样的数据库?

虽然我们的数据模型看起来相当相关,但我们不一定需要将所有内容存储在单个数据库中,因为这会限制我们的可扩展性并很快成为瓶颈。

我们将数据拆分到不同的服务,每个服务拥有特定表的所有权。然后,我们可以将关系数据库(例如PostgreSQL)或分布式 NoSQL 数据库(例如Apache Cassandra)用于我们的用例。

API 设计

让我们为我们的服务做一个基本的 API 设计:

发布推文

该 API 将允许用户在平台上发布推文。



postTweet(userID: UUID, content: string, mediaURL?: string): boolean


Enter fullscreen mode Exit fullscreen mode

参数

用户 ID(UUID):用户的 ID。

内容(string):推文的内容。

媒体 URL ( string):附加媒体的 URL (可选)

返回

结果(boolean):表示操作是否成功。

关注或取消关注用户

此 API 将允许用户关注或取消关注其他用户。



follow(followerID: UUID, followeeID: UUID): boolean
unfollow(followerID: UUID, followeeID: UUID): boolean


Enter fullscreen mode Exit fullscreen mode

参数

关注者ID(UUID):当前用户的ID。

关注者 ID(UUID):我们想要关注或取消关注的用户的 ID。

媒体 URL ( string):附加媒体的 URL (可选)

返回

结果(boolean):表示操作是否成功。

获取新闻源

此 API 将返回在给定新闻源中显示的所有推文。



getNewsfeed(userID: UUID): Tweet[]


Enter fullscreen mode Exit fullscreen mode

参数

用户 ID(UUID):用户的 ID。

返回

推文(Tweet[]):在给定的新闻源中显示的所有推文。

高层设计

现在让我们对我们的系统进行高层设计。

建筑学

我们将使用微服务架构,因为它可以更轻松地水平扩展和解耦我们的服务。每个服务都拥有自己的数据模型。让我们尝试将系统划分为几个核心服务。

用户服务

该服务处理与用户相关的问题,例如身份验证和用户信息。

新闻推送服务

该服务将处理用户新闻推送的生成和发布。具体细节我们将另行讨论。

推特服务

推文服务将处理与推文相关的用例,例如发布推文、收藏等。

搜索服务

该服务负责处理与搜索相关的功能。我们将另行详细讨论。

媒体服务

此服务将处理媒体(图片、视频、文件等)的上传。我们将另行详细讨论。

通知服务

该服务只会向用户发送推送通知。

分析服务

该服务将用于指标和分析用例。

服务间通信和服务发现怎么样?

由于我们的架构基于微服务,因此服务之间也会相互通信。通常,REST 或 HTTP 的性能表现良好,但我们可以使用更轻量、更高效的gRPC来进一步提升性能。

服务发现是我们需要考虑的另一件事。我们还可以使用服务网格,实现各个服务之间可管理、可观察且安全的通信。

注意:了解有关REST、GraphQL、gRPC 的更多信息以及它们之间的比较。

新闻源

说到新闻推送,实现起来似乎很容易,但有很多因素会影响这个功能的成败。所以,让我们把问题分成两部分:

一代

假设我们要为用户 A 生成 feed,我们将执行以下步骤:

  1. 检索用户 A 关注的所有用户和实体(主题标签、主题等)的 ID。
  2. 获取每个检索到的 ID 的相关推文。
  3. 使用排名算法根据相关性、时间、参与度等参数对推文进行排名。
  4. 将排名后的推文数据以分页的方式返回给客户端。

生成推文是一个繁琐的过程,可能会耗费大量时间,尤其是对于关注人数较多的用户而言。为了提升性能,可以预先生成推文并将其存储在缓存中,然后我们可以通过一种机制定期更新推文,并将我们的排名算法应用于新推文。

出版

发布是根据每个特定用户推送动态数据的步骤。这可能是一个相当繁重的操作,因为一个用户可能有数百万的好友或粉丝。为了解决这个问题,我们有三种不同的方法:

  • 拉动模型(或负载扇出)

新闻推送拉动模型

当用户创建推文,关注者刷新其新闻源时,该新闻源会被创建并存储在内存中。只有当用户请求时,才会加载最新的新闻源。这种方法减少了数据库的写入操作次数。

这种方法的缺点是,除非用户从服务器“拉”数据,否则他们将无法查看最新的信息,这将增加服务器上的读取操作次数。

  • 推送模型(或写入时扇出)

新闻推送模型

在这个模型中,用户一旦创建推文,就会立即“推送”到所有关注者的动态。这样一来,系统就无需逐一检查用户的整个关注者列表来查看更新。

然而,这种方法的缺点是它会增加数据库的写入操作次数。

  • 混合模型

第三种方法是拉动模型和推动模型之间的混合模型。它结合了上述两种模型的优点,并试图在两者之间提供一种平衡的方法。

混合模型仅允许关注者数量较少的用户使用推送模型,而对于关注者数量较多的用户(名人),将使用拉取模型。

排名算法

正如我们所讨论的,我们需要一个排名算法来根据每条推文与每个特定用户的相关性对其进行排名。

例如,Facebook 曾经使用过EdgeRank算法,其中每个 feed 项的排名由以下公式描述:

R 一个 n = 一个 f f n y × 西 e h × D e c 一个 y 等级 = 亲和力 × 权重 × 衰减
在哪里,

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系统设计。

详细设计

现在是时候详细讨论我们的设计决策了。

数据分区

为了扩展数据库,我们需要对数据进行分区。水平分区(又称分片)是一个很好的第一步。我们可以使用以下分区方案:

  • 基于哈希的分区
  • 基于列表的分区
  • 基于范围的分区
  • 复合分区

上述方法仍然会导致数据和负载分布不均匀,我们可以使用一致性哈希来解决这个问题。

有关更多详细信息,请参阅分片一致性哈希

共同的朋友

对于共同好友,我们可以为每个用户构建一个社交图谱。图中的每个节点代表一个用户,一条有向边代表关注者和被关注者。之后,我们可以遍历用户的关注者,找到并推荐共同好友。这需要使用像Neo4jArangoDB这样的图数据库。

这是一个非常简单的算法,为了提高我们的建议准确性,我们需要结合使用机器学习作为我们算法一部分的推荐模型。

指标和分析

记录分析和指标是我们的扩展需求之一。由于我们将使用Apache Kafka发布各种事件,因此我们可以使用Apache Spark(一个用于大规模数据处理的开源统一分析引擎)来处理这些事件并对数据进行分析。

缓存

在社交媒体应用中,我们必须谨慎使用缓存,因为用户期望获取最新数据。因此,为了防止资源使用量激增,我们可以缓存排名前 20% 的推文。

为了进一步提高效率,我们可以在系统 API 中添加分页功能。这项功能对于网络带宽有限的用户来说非常实用,因为他们无需在需要时才检索旧消息。

使用哪种缓存驱逐策略?

我们可以使用RedisMemcached等解决方案并缓存 20% 的每日流量,但哪种缓存驱逐策略最适合我们的需求?

对我们的系统来说,最近最少使用(LRU)策略可能是一个不错的选择。在这个策略中,我们首先丢弃最近最少使用的键。

如何处理缓存未命中?

每当出现缓存未命中时,我们的服务器可以直接访问数据库并使用新条目更新缓存。

有关详细信息,请参阅缓存

媒体访问和存储

众所周知,我们的大部分存储空间将用于存储媒体文件,例如图像、视频或其他文件。我们的媒体服务将处理用户媒体文件的访问和存储。

但是,我们可以在哪里大规模存储文件呢?嗯,对象存储就是我们想要的。对象存储将数据文件分解成称为对象的块。然后,它将这些对象存储在一个存储库中,该存储库可以分布在多个联网系统中。我们也可以使用分布式文件存储,例如HDFSGlusterFS

内容分发网络 (CDN)

内容分发网络 (CDN)可以提高内容可用性和冗余度,同时降低带宽成本。通常,静态文件(例如图像和视频)由 CDN 提供。对于这种情况,我们可以使用Amazon CloudFrontCloudflare CDN等服务。

识别并解决瓶颈

Twitter 高级设计

让我们识别并解决设计中的单点故障等瓶颈:

  • “如果我们的某项服务崩溃了怎么办?”
  • “我们将如何在组件之间分配流量?”
  • “我们如何才能减轻数据库的负载?”
  • “如何提高我们的缓存的可用性?”
  • “我们如何才能使我们的通知系统更加强大?”
  • “我们如何降低媒体存储成本”?

为了使我们的系统更具弹性,我们可以执行以下操作:

  • 运行我们每项服务的多个实例。
  • 在客户端、服务器、数据库和缓存服务器之间引入负载平衡器。
  • 为我们的数据库使用多个读取副本。
  • 我们的分布式缓存有多个实例和副本。
  • 在分布式系统中,精确一次传递和消息排序是一项挑战,我们可以使用专用消息代理(如Apache KafkaNATS)来使我们的通知系统更加健壮。
  • 我们可以在媒体服务中添加媒体处理和压缩功能来压缩大文件,这将节省大量存储空间并降低成本。

本文是我在 Github 上提供的开源系统设计课程的一部分。

文章来源:https://dev.to/karanpratapsingh/system-design-twitter-865
PREV
系统设计:WhatsApp 系统设计目录
NEXT
系统设计:Netflix 系统设计目录