← Courses Catalogue

#设计推特时间轴与搜索功能

注意:这个文档中的链接会直接指向系统设计主题索引中的有关部分,以避免重复的内容。你可以参考链接的相关内容,来了解其总的要点、方案的权衡取舍以及可选的替代方案。

设计 Facebook 的 feed设计 Facebook 搜索与此为同一类型问题。

#第一步:简述用例与约束条件

搜集需求与问题的范围。 提出问题来明确用例与约束条件。 讨论假设。

我们将在没有面试官明确说明问题的情况下,自己定义一些用例以及限制条件。

#用例

#我们将把问题限定在仅处理以下用例的范围中

#不在用例范围内的有

#限制条件与假设

#提出假设

普遍情况

时间轴功能

搜索功能

#计算用量

如果你需要进行粗略的用量计算,请向你的面试官说明。

便利换算指南:

#第二步:概要设计

列出所有重要组件以规划概要设计。

Imgur

#第三步:设计核心组件

深入每个核心组件的细节。

#用例:用户发表了一篇推特

我们可以将用户自己发表的推特存储在关系数据库中。我们也可以讨论一下究竟是用 SQL 还是用 NoSQL

构建用户主页时间轴(查看关注用户的活动)以及推送推特是件麻烦事。将特推传播给所有关注者(每秒约递送 6 万条推特)这一操作有可能会使传统的关系数据库超负载。因此,我们可以使用 NoSQL 数据库内存数据库之类的更快的数据存储方式。从内存读取 1 MB 连续数据大约要花 250 微秒,而从 SSD 读取同样大小的数据要花费 4 倍的时间,从机械硬盘读取需要花费 80 倍以上的时间。1

我们可以将照片、视频之类的媒体存储于对象存储中。

向你的面试官告知你准备写多少代码

如果我们用 Redis 作为内存缓存,那可以用 Redis 原生的 list 作为其数据结构。结构如下:

           tweet n+2                   tweet n+1                   tweet n
| 8 bytes   8 bytes  1 byte | 8 bytes   8 bytes  1 byte | 8 bytes   8 bytes  1 byte |
| tweet_id  user_id  meta   | tweet_id  user_id  meta   | tweet_id  user_id  meta   |

新发布的推特将被存储在对应用户(关注且活跃的用户)的主页时间轴的内存缓存中。

我们可以调用一个公共的 REST API

$ curl -X POST --data '{ "user_id": "123", "auth_token": "ABC123", \
    "status": "hello world!", "media_ids": "ABC987" }' \
    https://twitter.com/api/v1/tweet

返回:

{
    "created_at": "Wed Sep 05 00:37:15 +0000 2012",
    "status": "hello world!",
    "tweet_id": "987",
    "user_id": "123",
    ...
}

而对于服务器内部的通信,我们可以使用 RPC

#用例:用户浏览主页时间轴

REST API:

$ curl https://twitter.com/api/v1/home_timeline?user_id=123

返回:

{
    "user_id": "456",
    "tweet_id": "123",
    "status": "foo"
},
{
    "user_id": "789",
    "tweet_id": "456",
    "status": "bar"
},
{
    "user_id": "789",
    "tweet_id": "579",
    "status": "baz"
},

#用例:用户浏览用户时间轴

REST API 与前面的主页时间轴类似,区别只在于取出的推特是由用户自己发送而不是关注人发送。

#用例:用户搜索关键词

REST API:

$ curl https://twitter.com/api/v1/search?query=hello+world

返回结果与前面的主页时间轴类似,只不过返回的是符合查询条件的推特。

#第四步:架构扩展

根据限制条件,找到并解决瓶颈。

Imgur

重要提示:不要从最初设计直接跳到最终设计中!

现在你要 1) 基准测试、负载测试。2) 分析、描述性能瓶颈。3) 在解决瓶颈问题的同时,评估替代方案、权衡利弊。4) 重复以上步骤。请阅读「设计一个系统,并将其扩大到为数以百万计的 AWS 用户服务」 来了解如何逐步扩大初始设计。

讨论初始设计可能遇到的瓶颈及相关解决方案是很重要的。例如加上一个配置多台 Web 服务器负载均衡器是否能够解决问题?CDN呢?主从复制呢?它们各自的替代方案和需要权衡的利弊又有什么呢?

我们将会介绍一些组件来完成设计,并解决架构扩张问题。内置的负载均衡器将不做讨论以节省篇幅。

为了避免重复讨论,请参考系统设计主题索引相关部分来了解其要点、方案的权衡取舍以及可选的替代方案。

消息输出服务有可能成为性能瓶颈。那些有着百万数量关注着的用户可能发一条推特就需要好几分钟才能完成消息输出进程。这有可能使 @回复 这种推特时出现竞争条件,因此需要根据服务时间对此推特进行重排序来降低影响。

我们还可以避免从高关注量的用户输出推特。相反,我们可以通过搜索来找到高关注量用户的推特,并将搜索结果与用户的主页时间轴合并,再根据时间对其进行排序。

此外,还可以通过以下内容进行优化:

我们还可以考虑优化 SQL 数据库 来解决一些瓶颈问题。

内存缓存能减小一些数据库的负载,靠 SQL Read 副本已经足够处理缓存未命中情况。我们还可以考虑使用一些额外的 SQL 性能拓展技术。

高容量的写入将淹没单个的 SQL 写主从模式,因此需要更多的拓展技术。

我们也可以考虑将一些数据移至 NoSQL 数据库

#其它要点

是否深入这些额外的主题,取决于你的问题范围和剩下的时间。

#NoSQL

#缓存

#异步与微服务

#通信

#安全性

请参阅「安全」一章。

#延迟数值

请参阅「每个程序员都应该知道的延迟数」

#持续探讨