【系统设计】如何设计 Twitter 时间线和搜索?
如何设计 Twitter 时间线和搜索?
1.业务场景
业务场景如下:
- 用户发布推文
- 服务将推文推送给关注者,发送推送通知和电子邮件
- 用户查看用户时间线(来自用户的活动)
- 用户查看主页时间线(用户关注的人的活动)
- 用户搜索关键字
- 服务具有高可用性
其他场景:
- 服务将推文推送到 Twitter Firehose 和其他流
- 服务根据用户的可见性设置删除推文
- 如果用户没有关注被回复的人,则隐藏回复
- 尊重“隐藏转发”设置
- 分析
2.业务要求
假设如下:
- 流量分布不均
- 发布推文应该很快
- 向所有关注者发送推文应该很快,除非你有数百万关注者
- 1亿活跃用户
- 每天 5 亿条推文或每月 150 亿条推文
- 每条推文平均扇出 10 次交付
- 每天通过扇出发送 50 亿条推文
- 每月通过扇出发送 1500 亿条推文
- 每月 2500 亿次读取请求
- 每月 100 亿次搜索
时间线
- 查看时间线应该很快
- Twitter 阅读量大于写入量
- 优化推文的快速阅读
- 摄取推文写得很重
搜索
- 搜索应该很快
- 搜索是重读
简单的对业务要求进行计算,转换成业务指标
- 每条推文的大小:
tweet_id
- 8 个字节user_id
- 32 字节text
- 140 字节media
- 平均 10 KB- 总计:~10 KB
- 每月 150 TB 的新推文内容
- 每条推文 10 KB * 每天 5 亿条推文 * 每月 30 天
- 3 年内 5.4 PB 的新推文内容
- 每秒 10 万个读取请求
- 每月 2500 亿次读取请求 *(每秒 400 次请求 / 每月 10 亿次请求)
- 每秒 6,000 条推文
- 每月 150 亿条推文 *(每秒 400 条请求 / 每月 10 亿条请求)
- 每秒扇出 6 万条推文
- 每月通过扇出发送 1500 亿条推文 *(每秒 400 个请求 / 每月 10 亿个请求)
- 每秒 4,000 个搜索请求
- 每月 100 亿次搜索 *(每秒 400 次请求 / 每月 10 亿次请求)
方便的转换指南:
- 每月 250 万秒
- 每秒 1 个请求 = 每月 250 万个请求
- 每秒 40 个请求 = 每月 1 亿个请求
- 每秒 400 个请求 = 每月 10 亿个请求
3.系统设计
1.系统设计
我们必须进行必要的服务拆分
-
Timeline Service : 时间线服务,获取存储在Memory Cache中的时间线数据,包含用户ID和推文ID
- TWeet Info Service: 推文信息服务,获取有关推文ID的附加信息
- User Info Service : 用户信息服务,获取有关UserID的附加信息
-
Fan Out Service:扇出服务,A发布推文后,通知关注了A的所有用户,A发了新推文
-
User Graph Service : 用户关系服务,提供用户之间的关系图,比如A用户关注了哪些用户
-
Search Service : 关键字搜索服务,全文检索(搜索集群,Lucene)
-
Notification Service: 通知服务,向某用户发送推文通知(你关注的用户xx发了新推文)
-
2.用例实现
用例1:用户发布推文
我们可以将用户自己的推文存储在关系数据库中以填充用户时间线(来自用户的活动)。
我们可以将照片和视频等存储在 Object Store
- Client将推文发布到Web Server,作为反向代理运行
- Web Server将请求转发到Write API Server
- Write API Server将推文存储在SQL 数据库上的用户时间轴中
- Write API Server 联系 Fan Out 服务,该服务执行以下操作:
- 查询 User Graph 服务,查找 内存缓存中存储的用户关注者
- 将推文存储在内存缓存中用户关注者的主页时间线中
- O(n) 操作:1,000 个关注者 = 1,000 次查找和插入
- 将推文存储在Search Service中以实现快速搜索
- 在Object Store中存储媒体数据
- 使用Notification Service 服务向关注者发送推送通知:
- 使用队列(未图示)异步发送通知
内存缓存如果使用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 |
新的推文也会被放在redis中,该缓存会填充用户的主页时间线(来自用户关注人的活动)
$ 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",
...
}
内部通信,可以用grpc
用例2:用户查看主页时间线
- Client向Web Server发布主时间线请求
- Web Server将请求转发到Read API Server
- Read API Server 与 Timeline Service联系,后者执行以下操作:
- 获取存储在内存缓存中的时间线数据,包含推文 ID 和用户 ID - O(1)
- 使用multiget查询Tweet Info Service以获取有关推文 ID 的附加信息 - O(n)
- 使用 multiget查询User Info Service以获取有关用户 ID 的附加信息 - O(n)
$ 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"
},
用例3:用户查看用户自己的时间线
- Client向Web Server发布用户时间线请求
- Web Server将请求转发到Read API Server
- Read API Server 从SQL 数据库中检索用户时间线
类似于用例2的查看主页时间线,除了所有推文都来自用户自己而不是用户关注的人。
用例4:用户搜索关键字
- Client向Web Server发送搜索请求
- Web Server将请求转发到Search API Server
- Search API Server 联系Search Service,它执行以下操作 :
- 解析/标记输入查询,确定需要搜索的内容
- 删除标记
- 将文本分解为术语
- 修正错别字
- 规范大写
- 将查询转换为使用布尔运算
- 查询搜索集群(即Lucene)以获取结果:
- Scatter 收集集群中的每个服务器以确定是否有任何查询结果
- 合并、排名、排序并返回结果
- 解析/标记输入查询,确定需要搜索的内容
$ curl https://twitter.com/api/v1/search?query=hello+world
除了与给定查询匹配的推文外,响应将类似于主时间线的响应。
4.系统优化
优化要点:
- DNS
- CDN
- Load Balancer:负载均衡
- SQL Read Relicas :读多副本
- SQL Write Master-Slave :写主从模式
关于扇出服务的性能瓶颈:一个几百万的用户A发推文,可能需要几分钟,才能通知到关注了A的用户,A发送了新的推文:
当用户A关注人数到达一定阈值的时候,可以让Client主动搜我关注的A有没有新发推文
其他优化:
- 在内存缓存中只保留每个家庭时间线的数百条推文
- 仅在内存缓存中保留活动用户的主页时间线信息
- 如果用户在过去 30 天内未处于活动状态,我们可以从SQL 数据库重建时间线
- 查询User Graph以确定用户正在关注谁
- 从SQL 数据库中获取推文并将它们添加到内存缓存中
- 如果用户在过去 30 天内未处于活动状态,我们可以从SQL 数据库重建时间线
- Tweet Info Service中仅存储一个月的推文
- 仅在User Info Service中存储活动用户
- 搜索集群可能需要将推文保存在内存中以保持低延迟
心之所向,素履以往
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 上周热点回顾(2.17-2.23)