• 【系统设计】如何设计 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.系统设计

    img

    我们必须进行必要的服务拆分

    • 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:用户查看主页时间线

    • ClientWeb 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:用户查看用户自己的时间线

    • ClientWeb Server发布用户时间线请求
    • Web Server将请求转发到Read API Server
    • Read API Server SQL 数据库中检索用户时间线

    类似于用例2的查看主页时间线,除了所有推文都来自用户自己而不是用户关注的人。

    用例4:用户搜索关键字

    • ClientWeb Server发送搜索请求
    • Web Server将请求转发到Search API Server
    • Search API Server 联系Search Service,它执行以下操作 :
      • 解析/标记输入查询,确定需要搜索的内容
        • 删除标记
        • 将文本分解为术语
        • 修正错别字
        • 规范大写
        • 将查询转换为使用布尔运算
      • 查询搜索集群(即Lucene)以获取结果:
        • Scatter 收集集群中的每个服务器以确定是否有任何查询结果
        • 合并、排名、排序并返回结果
    $ curl https://twitter.com/api/v1/search?query=hello+world
    

    除了与给定查询匹配的推文外,响应将类似于主时间线的响应。

    4.系统优化

    img1

    优化要点:

    • DNS
    • CDN
    • Load Balancer:负载均衡
    • SQL Read Relicas :读多副本
    • SQL Write Master-Slave :写主从模式

    关于扇出服务的性能瓶颈:一个几百万的用户A发推文,可能需要几分钟,才能通知到关注了A的用户,A发送了新的推文:

    当用户A关注人数到达一定阈值的时候,可以让Client主动搜我关注的A有没有新发推文

    其他优化:

    • 在内存缓存中只保留每个家庭时间线的数百条推文
    • 仅在内存缓存中保留活动用户的主页时间线信息
      • 如果用户在过去 30 天内未处于活动状态,我们可以从SQL 数据库重建时间线
        • 查询User Graph以确定用户正在关注谁
        • 从SQL 数据库中获取推文并将它们添加到内存缓存中
    • Tweet Info Service中仅存储一个月的推文
    • 仅在User Info Service中存储活动用户
    • 搜索集群可能需要将推文保存在内存中以保持低延迟

    参考:https://github.com/donnemartin/system-design-primer/blob/master/solutions/system_design/twitter/README.md

  • 相关阅读:
    htmlUnil-2.33 jar包
    HtmlUnil 不兼容问题
    Java 网页抓取 工具类
    浏览器不兼容
    Chrome常用调试技巧1
    关于社交网络的思考
    google浏览器历史旧版
    EJB是什么Java使用EJB容器的详细概述
    何必言精通——十年杂感(转载)
    搜索优化—如何在Google搜索引擎上排名靠前Google左侧排名
  • 原文地址:https://www.cnblogs.com/yinbiao/p/16164975.html
Copyright © 2020-2023  润新知