系统设计面试题-实现Twitter时间线和搜索

设计Facebook feed和设计Facebook搜索都是类似的问题。

步骤1:概述用例和约束

收集需求并确定问题的范围。提出问题以澄清用例和约束。讨论假设。

 如果没有面试官来回答明确的问题,我们将定义一些用例和约束。

用例

我们将把问题的范围限定为只处理以下用例

  • 用户发布微博
  • 该服务向关注者推送推送消息,发送推送通知和电子邮件
  • 查看个人主页(来自用户的活动)
  • 用户查看关注时间线(用户关注的人的活动)
  • 用户搜索关键字
  • 服务具有高可用性

超出范围

  • 该服务将推特推送到Twitter Firehose和其他流
  • 该服务根据用户的可见性设置删除推文
  • 如果用户没有跟踪被回复的人,则隐藏@reply
  • 尊重“隐藏转发”设置
  • 分析

约束和假设

状态假设


总则

  • 流量分布不均匀
  • 发布tweet应该很快
  • 向你所有的追随者扩散一条tweet应该很快,除非你有数百万的追随者
  • 1亿活跃用户
  • 每天5亿条推文或每月150亿条推文
  • 每条tweet平均有10次扩散
  • 每天扩散的推文总数为50亿条
  • 每月扩散1500亿条tweet
  • 每月2500亿次读取请求
  • 每月100亿次搜索

时间线

  • 查看时间线应该很快
  • Twitter的读多写少
  • 读性能要求高
  • 接收tweet是写复杂操作

搜索

  • 搜索应该很快
  • 搜索是读操作

 

计算用量

向面试官说明你是否应该计算资源的使用量。

  • 每条推文的大小:
    • tweet id-8字节
    • 用户id-32字节
    • 文本-140字节
    • 媒体-平均10 KB
    • 总计:~10 KB
  • 每月150 TB新内容
    • 每条推文10 KB*每天5亿条推文*每月30天
    • 3年内新增微博内容5.4 PB
  • 每秒10万个读取请求
    • 每月2500亿个读取请求*(每秒400个请求/每月10亿个请求)
  • 每秒6000条推文
    • 每月150亿条推文*(每秒400条请求/每月10亿条请求)
  • 每秒扩散6万条推文
    • 每月扩散1500亿条推文*(每秒400条请求/10亿条请求/月)
  • 每秒4000个搜索请求
    • 每月100亿次搜索*(每秒400次请求/每月10亿次请求)

方便的转换指南:

  • 每月250万秒
  • 每秒1个请求=每月250万个请求
  • 每秒40个请求=每月1亿个请求
  • 每秒400个请求=每月10亿个请求

 

步骤2:创建宏观设计

概述所有重要组件的高级设计。

 

步骤3:设计核心组件

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

用例:用户发布tweet 

我们可以在关系数据库中存储用户自己的tweet来填充用户时间线(来自用户的活动)。我们应该讨论选择SQL还是NoSQL的用例和权衡。


发布tweet和建立home时间线(用户关注的人的活动)更为棘手。将tweet扩散给所有关注者(每秒扩散6万条)将使传统的关系数据库过载。我们可能希望选择一个具有快速写入的数据存储,例如NoSQL数据库或内存缓存。从内存中顺序读取1 MB大约需要250微秒,而从SSD读取需要4倍的时间,从磁盘读取需要80倍的时间。


我们可以将照片或视频等媒体存储在对象存储上。

  • 客户端将tweet发布到Web服务器,作为反向代理运行
  • Web服务器将请求转发到Write API服务器
  • writeapi将tweet存储在SQL数据库中用户的时间线中
  • Write API调用扩散服务,该服务执行以下操作:
    • 查询user graph服务以查找存储在内存缓存中的用户关注者
    • 将tweet存储在缓存中用户关注者的时间线中
    • O(n)操作:1000 followers=1000个查找和插入
    • 将tweet存储在搜索索引服务中以启用快速搜索
    • 在对象存储中存储媒体
    • 使用通知服务向关注者发送推送通知:
      • 使用队列(未显示)异步发送通知

向你的面试官说明你需要写多少代码。

如果我们的缓存是Redis,我们可以使用具有以下结构的Redis列表:

           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   |

新的tweet将被放置在缓存中,缓存填充用户的关注时间线(用户跟踪的人的活动)。

我们将使用REST API:

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

Response:

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

对于内部通信,我们可以使用远程过程调用。


用例:用户查看关注时间线

  • 客户机将主时间线请求发布到Web服务器
  • Web服务器将请求转发到readapi服务器
  • Read API服务器联系Timeline服务,该服务执行以下操作:
    • 获取存储在缓存中的时间线数据,其中包含tweet id和用户id  O(1)
    • 使用multiget查询Tweet Info服务以获取关于Tweet id  O(n)的附加信息
    • 使用multiget查询用户信息服务,以获取有关用户id  O(n)的附加信息

REST API: 

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

Response:

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

 

用例:用户查看个人主页

  • 客户端将用户时间线请求发布到Web服务器
  • Web服务器将请求转发到readapi服务器
  • readapi从SQL数据库检索用户时间线

restapi与关注 timeline类似,只是所有tweet都来自用户,而不是用户关注的人。

 

用例:用户搜索关键词

  • 客户端向Web服务器发送搜索请求
  • Web服务器将请求转发到搜索API服务器
  • 搜索服务执行以下操作:
    • 解析/标记输入查询,确定需要搜索的内容
      • 删除标记
      • 分词
      • 修复拼写错误
      • 规范化大小写
      • 使用操作符连接query
    • 在搜索集群(即Lucene)中查询结果:
      • 检索集群中的每个分片,以确定是否有任何查询结果
      • 合并、排列、排序并返回结果

REST API:

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

 除了与给定查询匹配的tweet外,响应与 timeline类似。

 

步骤4:演进设计

确定并解决瓶颈,给出限制条件。 

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

说明您将1)基准测试/负载测试,2)瓶颈概况,3)在评估备选方案和权衡时解决瓶颈,4)重复。


讨论在初始设计中可能遇到的瓶颈以及如何解决每个瓶颈是很重要的。例如,通过添加带有多个Web服务器的负载均衡可以解决哪些问题?CDN?主从复制?每种方法有哪些选择和权衡?


扩散服务是一个潜在的瓶颈。拥有数百万粉丝的Twitter用户可能需要几分钟的时间来完成他们的tweet扩散过程。(译者:后面还有一句话,我没理解意思):This could lead to race conditions with @replies to the tweet, which we could mitigate by re-ordering the tweets at serve time.


我们还可以避免从关注度很高的用户那里散播tweet。在拉取用户时间线时,通过搜索那些大v的推文list,将它们merge到用户的时间线中,再返回list (译者:其实就是写扩散的时候,大v不扩散,然后读写扩散结合去做feed流)


其他优化包括:

  • 在内存缓存中只为每个关注时间线保留几百条tweet
  • 在内存缓存中只保留活跃用户的时间线信息
    • 如果用户在过去的30天内没有活动,我们可以从SQL数据库重建时间线
      • 查询user graph服务以确定用户在关注谁
      • 从SQL数据库获取tweet并将它们添加到内存缓存中
  • 在Tweet Info服务中只存储一个月的Tweet
  • 仅在用户信息服务中存储活跃用户
  • 搜索集群可能需要将tweet保存在内存中以保持低延迟

我们还希望通过SQL数据库解决瓶颈问题。


尽管内存缓存可以减少数据库的负载,但仅SQL读取副本不太可能足以处理cache miss。我们可能需要采用额外的SQL扩展模式。


大量的写操作将使单个SQL写操作主从设备无法承受,这也表明需要额外的扩展技术。

  • 分片
  • 反范式
  • SQL调优

我们还应该考虑将一些数据移动到NoSQL数据库。

 

其他谈话要点

根据问题范围和剩余时间,需要深入研究的其他主题。

NoSQL

Caching

Asynchronism and microservices

Communications

  • rpc
  • HTTP APIs following REST

Security

Latency numbers

Ongoing

  • 继续基准测试和监控,以解决出现的瓶颈问题
  • 持续迭代

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值