如何设计Twitter、微博

[TOC]

这篇文章你需要掌握,分析QPS->定义要做功能-》设计SERVICE-》设计data SCHEMA-》TRADE OFF DB存储。 -》一个可用的SOLUTION
优化部分,你需要掌握, PULL vs PUSH, 2个的优缺点,如何改进。 3种不同SHARDING方式的优缺点。 缓存如何应用。 三种容错模式(备份,监控,负载均衡)

需求和目标

第一步我们应该询问,需要设计哪些功能,并且这些功能需要支持多少的QPS。
也就是初步判定我们系统的重要功能和QPS。
首先我们先讲一下几个指标的意义。

在發現用戶習慣上,作者首先強調了DAU/MAU這個概念。當然,DAU/MAU這個概念很早以前就被提出了,DAU/MAU一般可叫做當前用戶留存率,兩者相比主要比較來看用戶每月訪問遊戲的平均天數是多少。舉個例子,假設一款遊戲擁有50萬DAU,100萬MAU,其DAU/MAU就是50%,也就是說玩家每月平均體驗遊戲的時間是15天。這一數值越高,說明用戶黏著度越高。通常意義上「20%」被認為是一款產品的最低極限,是保證遊戲能夠達到臨界規模的病毒式傳播和用戶黏性。。如果低於20%那就基本可以不用投入大量精力經營了。接下來我們來看看主流SNS的DAU/MAU對比。

功能要求

用户应该能够发布新推文。

用户应该能够关注/取消其他用户。

用户应该能够标记最喜欢的推文。

该服务应该能够创建和显示用户的时间线,该时间线包括来自用户遵循的所有人的热门推文。

推文可以包含照片和视频。

非功能性要求

我们的服务需要高度可用。

对于时间线生成,系统的可接受延迟为200ms。

一致性可能会受到影响(为了可用性),如果用户暂时没有看到推文是可容忍的

扩展要求

搜索推文。

回复推文。

热门话题 - 当前热门话题/搜索。

标记其他用户。

推文通知。

关注谁’的建议

存储和性能

这一步我们需要估计DAU,QPS,以及需要多少的存储空间。
有了QPS,我还要分开来思考,要把读和写分开。
我们假设推特有5亿用户,平均每人10天上线一次。那么DAU就是5000W。
平均每人每天读10条推特,每5天写一条推特。
那么读的QPS大概在,5000W * 10/86400 = 50k/s
写的QPS大概在, 1000W/86400 = 1k/s
然后为了应对峰值,我们需要*3处理。
所以读的QPS大概在150K/S
写的峰值QPS在 3K/S

那么有了qps,我们大概知道需要多大性能的设备或者要用多大的集群。
QPS = 100,个人电脑足够了。
1000的话,需要买个好点的服务器
10k, 需要100+的集群

随后一台WEB 服务器大概可以HANDLE 1000的QPS
SQL 数据库也是
NOSQL 可以到10K
内存数据库可以到1M

下面计算存储。每天有1000W的写,假设都是文字的推特的话。大概一条推特需要500字节的存储。
那么每天需要的磁盘容量是0.5G
未来三年600G的大小。
如果要算上照片,假设10条里面会有一条带照片的推特。那么照片是500KB的话大概还需要60T的磁盘大小。

如果还能上传VEDIO,就更大会。
最后们大概可以计算下带宽。
我们就拿文字举例子,我们每天最高有1.5B的读,每个读500字节。除以86400,大概要75M/S
如果图片算进去就更大了。

设计服务

img

image.png

设计架构

img

image.png

存储

数据库需要如何选择?
图片应该是放在文件系统里。TWEET可以放在NOSQL里。用户信息存在SQL里。
这里需要分析一波,SQL VS NOSQL的优缺点。
我这里尝试分析一下为啥用户信息用SQL
1.首先用户的SCHEMA比较固定,是具备SECHMA的。

  1. 用户数据的QPS不会高,因为登陆注册是个使用频率比较低的行为。

  2. 使用SQL数据库,可能可以对用户的一些潜在需要TRANSACTION的行为进行直接HANDLE,如购买付费服务。
    那为啥TWEET需要存在NOSQL 呢?

  3. TWEET的数据是海量的,使用频率极高,用户来都会先读TWEET。NOSQL 响应快

  4. TWEET的信息是半SCHEMA的,也就是后面的列可能会动态变化。

  5. 大量的数据必然需要分布式,NOSQL 水平扩展性好。
    还有一个好友关系,SQL 都 NOSQL都可以。

    img

    image.png

新鲜事如何存取?

PULL模型

img

image.png

这里要注意的一件事是我们生成了一次feed并将其存储在缓存中。 Jane关注的人们新来的帖子怎么样?如果Jane在线,我们应该有一个机制来排名并将这些新帖子添加到她的Feed中。我们可以定期(比如说每五分钟)执行上述步骤,将较新的帖子排序并添加到她的Feed中。然后,Jane可以通知她可以获取的Feed中有更新的项目。

PULL的优点,写的时候,只要写自己的就好。
查的时候,要查所有,会比较慢。
我们可以拥有专用服务器,这些服务器不断生成用户的新闻源并将其存储在内存中。因此,只要用户请求他们的Feed的新帖子,我们就可以从预先生成的存储位​​置提供服务。使用此方案,用户的新闻源不会在加载时编译,而是定期编译,并在用户请求时返回给用户。

每当这些服务器需要为用户生成订阅源时,他们将首先查询以查看为该用户生成订阅源的最后时间。然后,从那时起将生成新的数据。

1
2
3
4
Struct {
LinkedHashMap<FeedItemID> feedItems;
DateTime lastGenerated;
}

其他问题
a)在发出拉取请求之前,可能不会向用户显示新数据,b)很难找到正确的拉动节奏,因为大多数时间拉取请求将导致空响应没有新数据,造成资源浪费。

PUSH模型
每个用户都有一个NEW FEEDS LIST。 当一个用户发了一条推文时,就往所有关注他的人的LIST里加上一条。
我们可以看到这种方案,读可以直接读。 写是O N

img

image.png

这种方案的问题是那些明星用户,拥有百万粉丝。要写的工作量超级多。

混合模型
处理馈送数据的另一种方法可以是使用混合方法,即进行拉和推的组合。具体来说,我们可以停止推送具有大量粉丝(名人用户)的用户的帖子,并且只为那些拥有数百(或数千)粉丝的用户推送数据。对于名人用户,我们可以让关注者拉更新。由于推送操作对于拥有大量朋友或关注者的用户而言成本极高,因此,通过禁用推,我们可以节省大量资源。
另一种替代方法可能是用户发布帖子后;我们可以将推限制为只有她的在线朋友。此外,为了从这两种方法中获益,将推送通知和拉服务最终用户组合起来是一个很好的方法。纯粹的推拉模型不太通用。

扩展

如何抉择每次PULL的数量?

这个可以有个默认值,随后我们可以根据用户习惯去动态增大或缩小。比如用户习惯一次看40条,那么我们可以把这个值加大。当然这样增加了编码的复杂性。我们可以让用户自己设置这个SIZE,对一些有抱怨的用户。

我们是不是应该一有新消息就通知用户。

这样做是比较好的,缺点就是会费带宽。如果用户在用自己的流量,那么让用户主动刷新也是不错的选择。

如果我们当前已经用了PULL模型,如何可以改善PULL缺陷?

增加CACHE,访问CACHE的速度会比DB快100倍,同时使得可以接受的QPS提高100倍。

img

image.png

如果我们当前已经用了PUSH模型,如何可以改善PUSH缺陷?

PUSH最大的问题是明星用户问题。因为明星用户没发一条推,就会通知海量的粉丝。
有2个思路,思路1,对那些不活跃的用户之后更新,也就是设置一个优先级。活跃按照最近上线时间来判断。
思路2,就是设置一个阈值来决定是不是明星用户,如果是明星用户就不推了。让那些用户自己拉。
但是有阈值之后,就会有摇摆问题。比如一个明星的粉丝正好在阈值附近。
这个问题可以不要立刻更新,把明星用户做一个标签,统计分析最近一个月的平均粉丝,然后每周更新。可以避免这个问题。

什么情况下适合PULL,什么情况下适合PUSH?

因为PULL需要很多内存,资源充足很关键。
实时性要求高的也要用PULL。
用户发帖多,和单向好友关系。因为单向好友关系,就不需要对方同意,明星很容易有很多关注者。这时候用PULL比较好。

资源少,实时性要求不高,发帖少,双向好友关系,不容易有明星问题。可以用PUSH。

如何扩展?

首先可以想到根据USER ID 来SHARDING。 这样做的好处是查找自己的TIMELINE很方便。因为就在一台服务器上。但是坏处就是当一个用户非常热,这样会造成那个服务器负载过大。
其次,我们看下用TWEET ID 来SHARDING。 这样在整合TIMELINE 时,会不得不向好多服务器发起QUERY。这样会造成较大的延迟。
如果我们用TWEET 创建时间来SHARDING。 这样最近的TWEET都会在一个服务器上。NEWS FEED比较好的能够容易获取到。
这样会引申出一种比较巧妙的做法。就是TWEET ID 就是时间撮+递增ID。前32为放上秒数,后32位(应该不用)算下每秒最多可能有多少个TWEET。放ID。

缓存策略如何应用

首先使用LRU,可以缓存到最热的推文。
其次可以根据20 80的原则去缓存,20%最常看的推文。
这里很大可能是3天内的推文。我们可以为其建缓存。
我们的缓存就像一个哈希表,其中’key’将是’OwnerID’,’value’将是一个双向链表,其中包含过去三天来该用户的所有推文。由于我们想首先检索最新的数据,我们总是可以在链表的头部插入新的推文,这意味着所有较旧的推文都将在链表的尾部附近。因此,我们可以从尾部删除推文,为更新的推文腾出空间。

服务器挂了怎么办?

我们可以使用主从备份,读写分离的思想。我们有多个从库负责提供读。然后主库负责写。
主库挂了,从库顶上。
在服务器端,我们可以使用负载均衡器。

有能力监控我们的系统至关重要。我们应该不断收集数据,以便即时了解我们的系统如何运作。我们可以收集以下指标/计数器,以了解我们的服务表现:

每天/秒的新推文,每日峰值是多少?
时间线投放统计数据,我们的服务每天/秒发送多少推文。
用户看到刷新时间线的平均延迟。
通过监视这些计数器,我们将意识到是否需要更多复制或负载平衡或缓存等。

作者:西部小笼包
链接:https://www.jianshu.com/p/ad7a88bbd08c
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。