如何设计Twitter是个经典的面试问题,我们要想一想Twitter的本质是什么,它其实是feed流。那什么是feed流呢?有很多list,这些List的集合就是你最终展现的结果,把一些list集合到一块去就是feed流。这个概念跟Kafka其实是非常相似的,Kafka是按照时间排序把很多log流聚合到一块。除了Twitter,还有很多现在火爆的应用主要功能都涉及到了feed流,比如Facebook、微博、微信朋友圈、Google Reader、今日头条。
比如说在Twitter中,我们有三个feed list,这里需要强调,因为在不同公司不同项目叫法不太一致,我们讲的feed是指单独的list,而list的集合叫timeline,所以timeline是feed的聚合,而每个单独的feed就是一个list。比如说上图中第一列是关于Nelson自己的list,第二列是关于Tim自己的list,第三列是QCon的list,每个人自己的feed可以放一起。
如果聚到一块就叫timeline。这里这个概念非常重要,大家要记住,每个单独的列表是自己的list,例如朋友圈有自己发的内容,你妈妈发的内容,你爸爸发的内容,那三个聚集到一块,成为你观看所有好友list的朋友圈就是timeline。
在Twitter里除了每个人自己的list外,第二重要的概念是每个人都有自己的好友关系,为什么说重要呢?只有有了这个好友关系和关注关系,才能够把你关注的所有人的信息聚集到一起,得到最终的结果social graph。这个关系图可以是单向或双向的,但为了简单起见,我们会把它看成单向,因为单向也可以表示双向。
根据我们的系统设计SNAKE(Scenario, Necessary, Application, Kilobit, Evolve)原则,首先需要了解场景,根据上面的介绍,我们对场景已经有了基本的认识。
第二个是限制条件,对于Twitter有什么限制性条件呢?大家可以猜猜Twitter实时的限制是多少?在Twitter里我们往往最关注读和写。Twitter的读,得到自己Timeline的请求是QPS=300k,这是很大的值,意味着每秒30万个人在读自己的list;那它的写是多少呢?只有5k,也就是说它的写远远少于读。这很正常,大多数情况读的人比较多,而写的人比较少。
在Twitter里另外一个重要功能是通知功能,比如Lady Gaga有几千万的关注者,她发了一条通知,会希望在之后的几十秒内能让所有关注者都收到这条通知,这时候就需要通知每个关注者。
Twitter希望做到什么能力呢?做到每秒一百万条通知,这是一个非常厉害的数字。那我们根据这个数字可以计算在Twitter里并发用户有多少个,答案是1500万个。那每天可以推送多少消息呢?用每秒发的数量乘以1天时间就行,得到答案大概每天4亿tweets,这是很大的数量,有这个数量以后我们可以计算每天的硬盘需求量,内存需求量,这些在下一篇Twitter基本设计原理里面会提及到。
在Twitter设计时,如果想把消息通知所有人,一共有两种模式:一种是自己拿广播通知所有人,这叫“推”模式;一种是有人主动来找你要,这叫“拉”模式。
首先讲最基本的“推”模式。
在任何一个模式里面,我们仍最关心两件事,读和写。首先什么是写呢?如果一个用户写了一条推文“Hello,I’m Fannec”,首先会调用Twitter自己对外写的API,这样才能写到后面执行所有的流程,写API要做的事情是通知大家推送出去消息,需要广播给所有关注的好友把消息发送他们。具体发送给谁呢?就需要Fanout广播系统读出我的好友关系,接着用O(n)的复杂度。因为每个人都需要写,比如一万个人关注我,就需要发一万份消息给大家,所以需要在每个人 的List后面加一个消息,“Fannec发送一条新消息”。这就是写。
写完以后怎么读呢?如果写是O(n),读就简单了,为什么呢?只需要把自己的Timeline读出来就可以展现最终结果。所以当你写的时候是O(n),读的时候是O(1)。比如另外一个人上线以后,直接从Timeline Service里得到他的Timeline,最终结果就出来了。
这个模式有什么缺点吗?
最大缺点就是Lady Gaga模式,什么意思呢?Lady Gaga有将近5000万的关注用户,当Lady Gaga发一条消息时,需要读出5000万个关注者,然后写出5000万份,那在这一瞬间就只能写Lady Gaga的,别人的没法写。所以当关注者太多时根本来不及写,或者说当写这个的时候别人都没办法写,难道每次Lady Gaga发消息别人都要延迟一分钟吗?肯定不能。那最好的方法怎么做呢?其实最好的方法就是只通知在线用户,因为在线用户需要立刻观看,不在线用户可以慢一些,所以可以用这种方法来优化。在线的关注者可能只有500万,这样5秒钟就通知完了。这是一个很好的方法,但广播模式最大的问题仍然是当一个人有很多关注者就会变得很慢。
换成“拉”模式怎么样?
依然从读和写两方面看,如何写决定了如何读。那如何写呢?首先写条消息,然后发到writeAPI,这回变聪明了,这条消息只写到自己的list中,如果是Fannnec就写到Fannec List里面,如果是Lady Gaga就写到Lady Gaga的List里。所以写的时候只写一条,因为只写自己,不写其他人,是O(1)的复杂度
写的时候容易,但读的时候呢?比如另外一个人需要读,他一定要去看自己关注的所有人,然后才能从Feed List里找到所有人的消息,把所有人的消息聚集到一起,所以读的时候会变得很慢,写是O(1)但读的时候是O(n)。
这就是上文所说的balance,需要做一个选择。当读取时如果关注了很多人,跟“推”模式相反,Lady Gaga是如果一个人被关注太多会成为瓶颈,“拉”模式是如果一个人关注很多人会成为问题,比如黑客帝国架构师的问题,黑客帝国架构师关注了世界上所有人,当他想知道世界发生什么事情的时候,难道要把十亿人的行为全部放到一起去吗?根本来不及看,架构师就会把系统弄崩溃。
“推、拉”模式都有缺点怎么办?
第一个想法肯定是“推拉结合”,这很简单,对于那些关注者比较少的就推过去,对于那些关注者比较多的就拉过来。所以最终结果是当有一个人想要得到消息时, 他的一部分关注者,那些关注者少的人会通过“推”模式发出来,而关注者比较多的人,如Lady Gaga,贝克汉姆之类还要“拉”一下,这两种方式可以混合得到最终结果,所以“推拉结合”的方式往往能够比较好的解决问题。
“推拉结合”难道没有问题吗?
问题是关注者的阈值怎么设。设置10万还是20万更合适?即使设置10万的定值还会有一个问题,叫做摇摆者问题,如果一个人的关注者在10万左右来回摇摆,是不是会出问题呢?有人可能会说Twitter怎么可能会把自己的关注数减少呢?不可能的。但有一种情况很可能会出现剧烈的摇摆,就是清理僵尸粉,比如新浪微博会有一段时间看到僵尸粉太多了,清理一下吧,结果发现系统中一半以上的ID都有僵尸粉,一清理,很多人的关注者从10万直接变成了几千,很多人会在推拉间从“拉”模式变成了“推”模式,这种情况就会产生巨大的摇摆,系统会在短时间出现问题。那怎么办呢?是否有别的办法?其实任何一个系统,针对一个绝对阈值设置不同的行为往往会出现问题,因为会发生摇摆,所以我们需要设置一个范围阈值。什么意思呢?如果关注数少于10万的话就开始推,如果关注数大于9万就开始拉。也就是设立一个阈值,当用户从10万往下降的时候并不会影响到自己的摇摆,它摇摆的过程被限制住了。所以通过设置一个范围的情况,能够极大的减少抖动,当然也不是绝对解决问题。但可以想一想设立一个缓冲区也是同样意义,比如两个国家打仗,无法完全分出胜负怎么办?就会在中间设置几个缓冲区,谁也别占,这样能够取得一定的平衡。
面试时,选择推还是选择拉呢?
我们有一些选择标准可以帮助判断。
第一:理解场景。比如像微信的后台架构,对于微信而言,群聊是“拉”模式好还是“推”模式好呢?必然是“推”模式好,为什么呢?因为群聊里最多是500人,所以说推的成本比较低,而“拉”模式会很复杂。而对于Twitter可以根据具体情况分析选择。
第二:延迟、计算、存储量之间做平衡。根据自己服务器的情况及当前的场景和未来的预估去做决定。
第三:O(n)不可行。在分布式系统中如果做算法,O(n)已经很低,但很多时候做分布式O(n)往往是不可行的。为什么呢?可能O(n)里面的每一个1都已经是很复杂的操作,比如每个1需要10毫秒,n是1万,那就是好几秒时间。所以当在分布式系统里,就算看到O(n)也不能掉以轻心,应该想O(n)前面的那个常量是多少,才能评估出最准确的结果。
最后:一条路走到黑。大家会问大多数系统是“推拉结合”的吗?其实没有,大多数系统选择了只一条路走到天黑,为什么呢?因为实现两个系统的复杂度反而更高,还不如针对某一个做优化。所以大家可以看到在Facebook里面,其实对所有人用的都是“拉”模式,而Instagram对所有人用的是“推”模式,也运行很好。大家不用很纠结,两种模式都可以做的很好,只要针对它做很好的优化。这是一个经验,大家不要执着于方法的混合,往往一种单独方法就很好。
·feed流中的概念
·Feed:Feed流中的每一条状态或者消息都是Feed,比如朋友圈中的一个状态就是一个Feed,微博中的一条微博就是一个Feed。
·Feed流:持续更新并呈现给用户内容的信息流。每个人的朋友圈,微博关注页等等都是一个Feed流。
·Timeline:Timeline其实是一种Feed流的类型,微博,朋友圈都是Timeline类型的Feed流,但是由于Timeline类型出现最早,使用最广泛,最为人熟知,有时候也用Timeline来表示Feed流。
顾名思义,Feed是喂养的意思,你想吃什么,就喂给你什么; 典型的例子就是微博、知乎的首页,以及各个聚合类资讯app的订阅号。这些信息的共同点就是给你喂你想看的,而不是将所有的东西全部给你;
a) 兴趣订阅类产品.此类产品都是针对兴趣的产品,因为用户的兴趣不同,所以针对不同的喜好用户推送不同的内容是必要的,而不是简单地将所有的东西不分青红皂白全额推送;
b) 针对用户画像差别推荐的产品。用户画像和兴趣的区别是,兴趣是自发地选择,而用户画像则是通过机器大数据的判定,例子就是天猫通过你的购买行为确定你的用户画像,从而推荐给你购买力相当的产品,而不是错位推荐,徒劳而无果;
c) 特定推送的产品。这种主要是付费类产品,普遍推送的是廉价的知识,而对于付费用户,则是区分地推送附加价值更高的高知识密度知识产品,这里的例子是得到app,通过订阅来推送普通用户无权限查看的精华文章,而免费的推送是普发。
a) 拉模式(读扩散)
推模式就是,用户A关注了用户B,用户B每发送一个动态,后台遍历用户B的粉丝,往他们粉丝的feed里面推送一条动态。
b) 推模式(写扩散)
与推模式相反,拉模式则是,用户每次刷新feed第一页,都去遍历关注的人,把最新的动态拉取回来。
c) 推拉结合
这是一种折中的解决方案,就是在线推,离线拉。
在线推:异步遍历在线的粉丝,将动态ID,添加到粉丝的Feed中。
离线拉:离线用户打开APP后,我们是会请求一个公共的入口接口,主做统计以及其他初始化操作,在这里,我们也开了一个异步线程,对用户进行Feed更新操作。
简单的来说,feed 是消息源,而rss是feed的一种格式。
feed:消息来源(英文:web feed、news feed、syndicatedfeed)是一种资料格式,网站透过它将最新资讯传播给用户。用户能够订阅网站的先决条件是,网站提供了消息来源。消息来源受到网志及新闻网站的广泛采用,这类型的网站经常更新内容。消息来源又译为源料、馈送、资讯提供、供稿、摘要、源、新闻订阅、网源。如前所述,feed译名很多,莫衷一是,至2008年底为止,还没有一个十分通用而备受认可的中文译名;所以此文当中我们用英文feed来称呼。将feed汇流于一处称为聚合①(aggregation),而用于聚合的软体称为聚合器(aggregator)。对最终用户而言,聚合器是专门用来订阅网站的软件,一般亦称为RSS阅读器、feed阅读器、新闻阅读器等。
rss(简易信息聚合):是一种消息来源格式规范,用以发布经常更新数据的网站,例如博客文章、新闻、音频或视频的网摘。RSS文件(或称做摘要、网络摘要、或频更新,提供到频道)包含了全文或是节录的文字,再加上发用者所订阅之网摘布数据和授权的元数据。网络摘要能够使发行者自动地发布他们的数据,同时也使读者能更够定期更新他们喜欢的网站或是聚合不同网站的网摘。RSS摘要可以借由RSS阅读器、feedreader或是aggregator等网页或以桌面为架构的软件来阅读。标准的XML档式可允许信息在一次发布后通过不同的程序阅览。用户借由将网摘输入RSS阅读器或是用鼠标点取浏览器上指向订阅程序的RSS小图标之URI(非通常称为URL)来订阅网摘。RSS阅读器定期检阅是否有更新,然后下载给监看用户界面。
瀑布流:就像瀑布一样,一直源源不断地给你东西,才不管你是不是需要,才不管你是不是饱了。典型的例子是简书app的首页,传统类新闻app等。他们的共同点就是只要源头有水就不会断流,当然你的兴趣也得不到照顾。
瀑布流的应用场景:
a) 传统资讯产品。比如四大门户新浪、搜狐、腾讯、网易新闻首页,不论你登陆还是不登陆号码,都将会相同的页面,这是对于每一个用户的无差别展现,这种展现方式保证了每一个用户看到的首页信息的一致性,也保证了需要宣传的头条的一致性。
b) 专业类产品。比如36kr,虎嗅网,钛媒体,雷锋网等科技创业媒体,基于用户的确定性和稳定性,也基于文章数量的轻便性,推送的内容一致,但是也同时声明,自己的每篇文章都是高质量、有意义的。
c) 图片素材展示产品。比如花瓣网,可以说是典型的瀑布流了,通过瀑布流的展示,将各个图片素材展示给设计师用户以便甄选,从中挑出自己的理想素材,但如果选用feed流,则会使很多可能的灵感流失。
a) 永恒的Timeline
Timeline是Feed流设计中最原始、最基本也是最直觉的展示形式。timeline,所谓的“时间线”,内容的分发完全按照时间进行排序和展示的。Timeline有简单粗暴的优点:利于用户对呈现的内容进行理解,时间的先后顺序嘛,另外由于是按照时间顺序,每次更新都能最大化的保证用户能够看到更新的内容。当时timeline也有致命的弱点:内容呈现的效率极为底下,甚至可能会出现大量的垃圾内容。
b) 重力排序法——兼顾热度和更新时间
其实一个平台大了之后,每天将会产生大量的内容,既有大量的feed流,这些feed流中大部分内容其实对用户是没有太大的价值的。
为重力排序法,对于一个feed流中的内容而言,有两种力量:重力和拉力。重力就是让内容持续往下路的力,即时间,时间越久,掉的越快;拉力就是让内容往前排的力,比如知乎的点赞、门户新闻的阅读数等。重力和拉力,两者相斥,共同决定内容的排序机制。
这里有一个重力算法的排序公式,来自于Reddit的核心排序算法:
score(H,T)=logH + (T-t)/A
c) 智能排序法——在唾骂中前行
首先,系统需要知道什么是一个内容被展示的目标值。比如微博,一个内容被展示的目标值是转发、评论、点赞的次数。那么通过大量的样本机器学习,系统对于什么是好的内容会有一个预测。这对于一个内容的预测,则是智能排序的基础。
其次,系统会屏蔽一些违规的内容,比如涉及到政治、敏感事件。
再次,为了用户内容质量,系统会在用户的feed中增加一些热门的内容。
最后,考虑内容和用户的亲密度,系统认为内容受到欢迎的程度,内容事件衰减等因素后,系统进行综合排序。
首先,什么是 Feed ?a web feed (or news feed) is a data format used for providing users with frequently updated content. Content distributors syndicate a web feed, thereby allowing users to subscribe to it.从 Feed 的定义来看,有两点值得注意:1. Feed 是一种数据格式,用于给(订阅的)用户提供持续更新的内容;2. 看似是 Push 内容给用户的形式,实质是用户自己主动选择多个订阅源,展示内容汇总的聚合器(典型代表是RSS)主动向服务器请求内容,再以时间顺序呈现到聚合器,是一种典型的 Pull Technology(定义如下):Pull coding or client pull is a style of network communication where the initial request for data originates from the client, and then is responded to by the server.所以,这样来看,先前回答中的很多例子(搜索结果、智能排序、论坛、单个新闻类网站)中的那部分被我们认为是 Feed 的部分严格来说都不能算是 Feed。
作者:胡点Vivian
链接:https://www.zhihu.com/question/20825185/answer/107671816
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
然而,虽然早期的 Feed 典型是 RSS,现在使用的人相对已经比较少了(大名鼎鼎的 Google Reader 都关了)。而大多数90后(比如我)第一次见到类似 Feed 的东西大概是从 Path、人人、微博这类社交产品上。2006年 Facebook 第一次提出了 News Feed 这个新东西,可以说是从此重新定义了 Feed 这个词的含义。今天听 @JJ Ying 他们的播客中也提到了对 RSS 的热情逐渐淡去因为归根结底他们更加在乎的是产生内容的人而不是内容本身,只要关注对了人,经过筛选的内容总会源源不断的涌现。Facebook 首页的 News Feed 可以看做一个新型聚合器,订阅源的是你的好友或 Follow 的公众人物,内容是他们公开发布的动态。当好友数量较多且活跃时,就可以收到不断更新的内容,这是我们最常见的 Feed 形式。微博、知乎也类似。姑且称为 Feed 2.0 吧。Feed 2.0 的特点在我看来是,不再纯粹遵循上面的定义,具体表现为:1. 内容中掺杂着非订阅源的内容,如 Facebook 中的广告,微博中的推广内容;2. 内容不再严格按照时间顺序排列,如智能排序、知乎实验室版本的首页动态等;3. 聚合器不再基于万维网,内容以 HTML 这种朴素而开放的形式传输,而是相对封闭,因订阅源来自平台中的真实用户,所以用户都必须登录才能查看订阅内容,而内容的产生也都基于平台的账号系统和规则。在这样的基础下,我们再来讨论内容展示的形式。时间是 Feed 所遵循的终极维度,因为内容的更新是不断向服务器发出请求的结果。在Facebook中,个人页面除了 Timeline ,还有 About 以及 Photo 等页面内容,但只有 Timeline 页面真正算得上是 Feed。可以说,Timeline 是 Feed 最原始最直觉也最基本的展示形式,如果说有更好的,那也是在 Timeline 的基础上做设计。比如,除了一溜烟的无限下拉外,可以将内容归类并添加导航。如 Facebook Timeline 边上有按照年份的归类,点击一下就可以直接跳回 2010 年
另外还有知乎、Quora 之类的智能排序,大方向上仍旧是基于时间先后,但是不是严格遵循时间顺序,在一定的范围内会根据推荐机制强势插入时间上并不是「最近」的内容。至于在 Timeline 的规则下,是以卡片形式、瀑布形式、对话形式还是杂志形式来展示需根据产品定位设计,不再赘述。总的来说,Feed 是一种用户主动订阅和索取信息的方式,但是对于将要看到的内容并不会有特别明确的预期(不同于搜索),尤其在现在人们无时无刻在用手机「杀时间」的情况下,一般来说效率和准确性并不是内容排序中优先级最高的,而感兴趣才是。
先说说feed的元信息吧feed的核心是内容,其次会有可能有:内容产生的时间内容产生的地点内容的发布者分类(tag)其中内容是核心,依附于内容的还有更小的元信息例如互联网上常见的设计,被顶的次数、被转发的次数、被评论的次数等都是内容下的更小的元信息讨论feed的形式其实就是讨论内容和所有的元信息组合组合之后的如何组织并展示请允许我这么定义(实在找不到更准确的表达方式了):feed就是在若干条件限制下的内容展示百度搜索结果就是一种feed,条件是关键词以及后台算法(网易花田把征友搜索条件做成了feed)人人网上的好友的新鲜事儿就是一种feed,条件是你好友动态的时间排序google reader中的内容流就是一种feed,条件是订阅的文章的时间排序以上的feed是一维的打开你的团购客户端,搜索附近的团购,你会看到两种展示,一种是按照距离排序的一维的列表一种就是在地图上显示的团购信息,可以生动的看到内容所在的位置这种地图上显示的feed就是二维的当你跳出虚拟地图,站到了真正的土地上,看到了活生生的世界,你眼前的feed是立体的(比三维更多),有地理信息,有商家店面信息,有顾客人流信息,有停车位信息,等等,有数不尽的元信息(这些都是限制条件)回到上面的问题,除了timeline有没有更好的内容展示形式,取决与用户如何快速准确得到想要的信息搜索如果是时间排序,微博如果是按照转发数量排序,会发生什么情况呢?很显然用户就无法得到自己想要的信息,如果是一维的排序,要不是依据一个元信息进行排序,例如时间、距离、价格,要不就是两种以上的元信息进行排序(无限种组合,所谓的智能排序),用户对一维的排序是有个心里预期的,当你打破这种预期,就会对用户造成困扰,比较恶心的例子如:搜索的结果排序(在结果前把广告植入进去)新浪微博的智能排序(打乱时间,用户的心理预期收到干扰)淘宝商品价格排序(很多淘宝店主都定一个很低价格,比如某某数码相机799,但进去后发现799的没货,其实是999)类似的商业手段屡试不爽
Facebook起源的NewsFeed,以及Twitter起源的Timeline,核心问题都是如何处理巨大的消息(活动,activity)分发。“推Push”和“拉Pull”或者“推拉结合”,是主要的处理方式。
以前各大网站陆续透露的文档,以及这次QCon2012 London和深圳的架构师会议,较大程度的公开了各自的实现方式。本文从 消息分发模式;内部通信工具、协议;存储方式 3方面总结。
各大网站都大量使用的Nginx, memcached, MySQL等开源产品,都标配了,文中不再提。实现技术上,异步消息队列的引入,来模块解耦和尖峰削平;Cache的精良设计等,也都是各家大量使用的技能,可看参看文档,不再详述。
推 Push, fan-out, Write-fanout 写时消息推送给粉丝。空间换时间
拉 Pull, fan-in, Read-fanout 读时拉取所有好友的消息,再聚合。时间换空间
混合 Hybrid 基于推,混入拉;基于拉,加速推。时空平衡
1
Facebook
参考《Facebook news-feed QCon12.pdf》。典型的Pull方式,读时fanout,获得所有好友的活动,再进行聚合,rank,排序等操作(这几步后续动作,是feed和timeline的最大不同特点)。Facebook把这种模式叫做“Multifeed – Multi-fetch and aggregate stories at read time”。
FB的众多产品、模块,通讯协议自然用自家的Thrift,还用到SMC和其他的底层平台。
存储模块,有自家的“排序”存储文件(feed要按时间倒排,还有rank影响排序…内存的B树排序结构,可以预测性的合并到文件。可能开源)。还大量使用了 Redis 和Google开发的开源持久化KV存储: LevelDB。
Feeds相对于Timeline,最大特点是有rank影响排序,需要按类型合并,有推荐算法的插入,有更复杂的数据结构…这些都是影响架构设计的重要因素,但这些都没有文档详细描述。拉模式下,最重要的是高效稳定、分布式的Aggregator的设计,也没有详细文档说明。
(Facebook可以说是技术文档最不透明的网站了,特别是相较于他拥有最大的UGC而言。)
2
Twitter
参考《TimelinesTwitter-QCon12.pdf》等众多文档。主要是推模式。Twitter的Timeline这种应用,和FB的Feed最大的区别,就是要解决fan-out的效率和全文搜索的效率。整体模块划分图:
主要特点是对fanout的处理:队列化(有自己用Scala语言实现的Kestrel队列),并发处理推送等大消耗业务,各级缓存(包括In-Proc)…
通讯协议上, Kestrel 复用了MemCached协议;而Timeline API模块使用了FB的Thrift。通信框架是大量使用的自己开发的(已开源)RPC框架 Finagle (A fault tolerant, protocol-agnostic RPC system)。
搜索引擎使用了Lucene。存储也大量使用了Redis。
3
人人网
参考《人人网Feed系统结构浅析.pdf》和《人人网网站架构–服务化的演进》。作为中国的大型SNS网站,设计上也有很多自己的特色。
从查询的效率考虑, 人人网采用了推模式(近似twitter模式)。但是,人人网的Feeds,又比twitter类的timeline,有更复杂的结构和功能需求,所以在设计上,会有FB和Twitter双方融合的特点。
在Cache上,人人有自己实现的Server来支持。特别是在IndexCache上,基本数据结构和FB一样,使用了C++ Boost multi-index container;序列化和压缩采用Protobuf和QuickLZ。特别是有专门实现的解决feed索引持久化难题的Feed Index DB。
最后用模板渲染引擎(也是C++实现)来显示复杂的Feed。
Renren在网络通信上大量使用 ICE框架 ,协议上多用Protobuf,实现缓存等中间层、新鲜事儿等系统。大量自己开发的server集群,通过他们高效通信。
在高性能计算上,Renren网倾向用C/C++编写定制性Server,保证数据中心存储,大规模数据尽量在进程内访问。像IndexCache Server(海量的Feed数据装载在单一Server内,实现“数据尽可能靠近CPU”的原则),实现高速排序等计算需求;此外还有文档里提及的渲染Server…都是用C写的专用Server。好处自然是本地内存的纳秒级访问速度,远远高于网络IO,可实现极高的性能。
现在,人人网的架构也在向Service化方向发展,并封装成了XOA,基础总线使用了Thrift,消息队列用了ZeroMQ …
4
新浪微博
参考TimYang的《 构建可扩展的微博架构 》和《新浪微博cache设计谈.pdf》
虽然来源于Twitter,但不得不说,就数据量、复杂性而言,已经不弱于Twitter;稳定性更是高出了Twitter很多。新浪微博基础是拉模式,但是增加了“在线推”,对于在线用户有“Inbox Cache”加速对timeline的获取,减少aggregator的性能和时间消耗。结构如下图:
首页timeline获取步骤是:1.检查inbox cache是否可用; 2.获取关注列表; 3.聚合内容, 从 following 关系; 4.根据id list返回最终feed聚合内容。Sina的这种结合模式,符合中国的特点:明星海量粉丝(纯推送代价巨大),个人用户关注多(纯拉取代价大),且在线用户能得到极快的响应。
存储大量使用了Redis。并且有专门的讲演,详细介绍了Sina在Redis的大规模应该场景。《 Redis介绍》 《 新浪微博开放平台Redis实践 》
但是基于拉模式的aggragator没有对外介绍。此外,sina微博的消息机制、RPC框架,也未介绍。
5
腾讯微博
参考《 张松国-腾讯微博架构介绍 08.pdf》。腾讯作为最有技术底子的公司,其架构有很多独特之处,参考和直接利用国外的网站的模式最少。腾讯微博采用“拉”模式,聚合计算aggregator采用了多级模式:
同大多的timeline系统一样,使用队列来异步化和解耦,不过qq的解耦包括了系统解耦和业务解耦(和Renren网的“中转单向RPC调用的消息队列”类似),不但解耦模块,还使得各模块开发得以并行,提升开发效率。其主要架构图:
腾讯的积累,使得腾讯微博在平台化做的扎实。整个产品的“接口-服务”感觉清晰。在容灾容错方面更是比其它家(至少从文档上)高出了很多。集群建设,系统维护都沿袭了腾讯的积累,光海量日志的查询就用了Sphinx全文搜索。数据挖掘和分析(比如关系链分析、圈子挖掘、用户价值评估)也一直是腾讯的重点能力。
微博日活跃用户1.6亿+,每日访问量达百亿级,面对庞大用户群的海量访问,良好的架构且不断改进的缓存体系具有非常重要的支撑作用。本文将由新浪微博技术专家陈波老师,跟大家详细讲解那些庞大的数据都是如何呈现的。
本文大纲
1、微博在运行过程中的数据挑战
2、Feed平台系统架构
3、Cache架构及演进
4、总结与展望
数据挑战
Feed平台系统架构
Feed平台系统架构总共分为五层,最上面是端层,比如web端、客户端、大家用的IOS或安卓的一些客户端,还有一些开放平台、第三方接入的一些接口;下一层是平台接入层,不同的池子,主要是为了把好的资源集中调配给重要的核心接口,这样遇到突发流量的时候,就有更好的弹性来服务,提高服务稳定性。再下面是平台服务层,主要是Feed算法、关系等等。接下来是中间层,通过各种中间介质提供一些服务。最下面一层就是存储层。
大家日常刷微博的时候,比如在主站或客户端点一下刷新,最新获得了十到十五条微博,这是怎么构建出来的呢?
刷新之后,首先会获得用户的关注关系。比如他有一千个关注,会把这一千个ID拿到,再根据这一千个UID,拿到每个用户发表的一些微博。同时会获取这个用户的Inbox,就是他收到的特殊的一些消息,比如分组的一些微博、群的微博、下面的关注关系、关注人的微博列表。
拿到这一系列微博列表之后进行集合、排序,拿到所需要的那些ID,再对这些ID去取每一条微博ID对应的微博内容。如果这些微博是转发过来的,它还有一个原微博,会进一步取原微博内容。通过原微博取用户信息,进一步根据用户的过滤词对这些微博进行过滤,过滤掉用户不想看到的微博。
根据以上步骤留下的微博,会再进一步来看,用户对这些微博有没有收藏、点赞,做一些flag设置,还会对这些微博各种计数,转发、评论、赞数进行组装,最后才把这十几条微博返回给用户的各种端。
这样看来,用户一次请求得到的十几条记录,后端服务器大概要对几百甚至几千条数据进行实时组装,再返回给用户,整个过程对Cache体系强度依赖,所以Cache架构设计优劣会直接影响到微博体系表现的好坏。
2
Feed Cache架构
接下来我们看一下Cache架构,它主要分为六层。首先第一层是Inbox,主要是分组的一些微博,然后直接对群主的一些微博。Inbox比较少,主要是推的方式。
然后对于第二层的Outbox,每个用户都会发常规的微博,都会在它的Outbox里面去。根据存的ID数量,实际上分成多个Cache,普通的大概是200多条,如果长的大概是2000条。
第三层是一些关系,它的关注、粉丝、用户。
第四层是内容,每一条微博一些内容存在这里。
第五层就是一些存在性判断,比如某条微博我有没有赞过。之前有一些明星就说我没有点赞这条微博怎么显示我点赞了,引发了一些新闻。而这种就是记录,实际上她有在某个时候点赞过但可能忘记了。
最下面还有比较大的一层——计数,每条微博的评论、转发等计数,还有用户的关注数、粉丝数这些数据。
Cache架构及演进
1
简单KV数据类型
单层hash
main ha
最开始微博上线时,我们是把它作为一个简单的KV数据类型来存储。我们主要采取哈希分片存储在MC池子里,上线几个月之后发现一些问题:有一些节点机器宕机或是其它原因,大量的请求会穿透Cache层达到DB上去,导致整个请求变慢,甚至DB僵死。
于是我们很快进行了改造,增加了一个HA层,这样即便Main层出现某些节点宕机情况或者挂掉之后,这些请求会进一步穿透到HA层,不会穿透到DB层。这样可以保证在任何情况下,整个系统命中率不会降低,系统服务稳定性有了比较大的提升。
对于这种做法,现在业界用得比较多,然后很多人说我直接用哈希,但这里面也有一些坑。比如我有一个节点,节点3宕机了,Main把它给摘掉,节点3的一些QA分给其他几个节点,这个业务量还不是很大,穿透DB,DB还可以抗住。但如果这个节点3恢复了,它又加进来之后,节点3的访问就会回来,稍后节点3因为网络原因或者机器本身的原因,它又宕机了,一些节点3的请求又会分给其他节点。这个时候就会出现问题,之前分散给其他节点写回来的数据已经没有人更新了,如果它没有被剔除掉就会出现混插数据。
main ha l1
实际上微博是一个广场型的业务,比如突发事件,某明星找个女朋友,瞬间流量就30%了。突发事件后,大量的请求会出现在某一些节点,会导致这些节点非常热,即便是MC也没办法满足这么大的请求量。这时MC就会变成瓶颈,导致整个系统变慢。
基于这个原因,我们引入了L1层,还是一个Main关系池,每一个L1大概是Main层的N分之一,六分之一、八分之一、十分之一这样一个内存量,根据请求量我会增加4到8个L1,这样所有请求来了之后首先会访问L1。L1命中的话就会直接访问,如果没有命中再来访问Main-HA层,这样在一些突发流量的时候,可以由L1来抗住大部分热的请求。对微博本身来说,新的数据就会越热,只要增加很少一部分内存就会抗住更大的量。
简单总结一下,通过简单KV数据类型的存储,我们实际上以MC为主的,层内HASH节点不漂移,Miss穿透到下一层去读取。通过多组L1读取性能提升,能够抗住峰值、突发流量,而且成本会大大降低。对读写策略,采取多写,读的话采用逐层穿透,如果Miss的话就进行回写。对存在里面的数据,我们最初采用Json/xml,2012年之后就直接采用Protocol Buffer格式,对一些比较大的用QuickL进行压缩。
2
集合类数据
刚才讲到简单的QA数据,那对于复杂的集合类数据怎么来处理?
比如我关注了2000人,新增一个人,就涉及到部分修改。有一种方式是把2000个ID全部拿下来进行修改,但这种对带宽、机器压力会很大。还有一些分页获取,我存了2000个,只需要取其中的第几页,比如第二页,也就是第十到第二十个,能不能不要全量把所有数据取回去。还有一些资源的联动计算,会计算到我关注的某些人里面ABC也关注了用户D。这种涉及到部分数据的修改、获取,包括计算,对MC来说实际上是不太擅长的。
各种关注关系都存在Redis里面取,通过Hash分布、储存,一组多存的方式来进行读写分离。现在Redis的内存大概有30个T,每天都有2-3万亿的请求。
在使用Redis的过程中,实际上还是遇到其他一些问题。比如从关注关系,我关注了2000个UID,有一种方式是全量存储,但微博有大量的用户,有些用户登陆得比较少,有些用户特别活跃,这样全部放在内存里成本开销是比较大的。所以我们就把Redis使用改成Cache,比如只存活跃的用户,如果你最近一段时间没有活跃,会把你从Redis里踢掉,再次有访问的时候再把你加进来。
这时存在一个问题,因为Redis工作机制是单线程模式,如果它加某一个UV,关注2000个用户,可能扩展到两万个UID,两万个UID塞回去基本上Redis就卡住了,没办法提供其他服务。所以我们扩展一种新的数据结构,两万个UID直接开了端,写的时候直接依次把它写到Redis里面去,读写的整个效率就会非常高。它的实现是一个long型的开放数组,通过Double Hash进行寻址。
我们对Redis进行了一些其他的扩展,大家可能也在网上看到过我们之前的一些分享,把数据放到公共变量里面,整个升级过程,我们测试1G的话加载要10分钟,10G大概要十几分钟以上,现在是毫秒级升级。
对于AOF,我们采用滚动的AOF,每个AOF是带一个ID的,达到一定的量再滚动到下一个AOF里去。对RDB落地的时候,我们会记录构建这个RDB时,AOF文件以及它所在的位置,通过新的RDB、AOF扩展模式,实现全增量复制。
3
其他数据类型-计数
接下来还有一些其他的数据类型,比如一个计数,实际上计数在每个互联网公司都可能会遇到,对一些中小型的业务来说,实际上MC和Redis足够用的,但在微博里计数出现了一些特点:单条Key有多条计数,比如一条微博,有转发数、评论数,还有点赞;一个用户有粉丝数、关注数等各种各样的数字。因为是计数,它的Value size是比较小的,根据它的各种业务场景,大概就是2-8个字节,一般4个字节为多,然后每日新增的微博大概十亿条记录,总记录就更可观了,然后一次请求,可能几百条计数要返回去。
4
计数器-Counter Service
最初是可以采取Memcached,但它有个问题,如果计数超过它内容容量时,会导致一些计数的剔除,宕机或重启后计数就没有了。另外可能有很多计数它为零,那这个时候怎么存,要不要存,存的话就占很多内存。微博每天上十亿的计数,光存0都要占大量的内存,如果不存又会导致穿透到DB里去,对服务的可溶性会存在影响。
2010年之后我们又采用Redis访问,随着数据量越来越大之后,发现Redis内存有效负荷还是比较低的,它一条KV大概需要至少65个字节,但实际上我们一个计数需要8个字节,然后Value大概4个字节,所以有效只有12个字节,还有四十多个字节都是被浪费掉的。这还只是单个KV,如果在一条Key有多个计数的情况下,它就浪费得更多了。比如说四个计数,一个Key 8个字节,四个计数每个计数是4个字节,16个字节大概需要26个字节就行了,但是用Redis存大概需要200多个字节。
后来我们通过自己研发的Counter Service,内存降至Redis的五分之一到十五分之一以下,而且进行冷热分离,热数据存在内存里,冷数据如果重新变热,就把它放到LRU里去。落地RDB、AOF,实现全增量复制,通过这种方式,热数据单机可以存百亿级,冷数据可以存千亿级。
整个存储架构大概是上图这样,上面是内存,下面是SSD,在内存里是预先把它分成N个Table,每个Table根据ID的指针序列,划出一定范围。任何一个ID过来先找到它所在的Table,如果有直接对它增增减减,有新的计数过来,发现内存不够的时候,就会把一个小的Table Dump到SSD里去,留着新的位置放在最上面供新的ID来使用。
有些人疑问说,如果在某个范围内,我的ID本来设的计数是4个字节,但是微博特别热,超过了4个字节,变成很大的一个计数怎么处理?对于超过限制的,我们把它放在Aux dict进行存放,对于落在SSD里的Table,我们有专门的IndAux进行访问,通过RDB方式进行复制。
5
其他数据类型-存在性判断
除了计数,微博还有一些业务,一些存在性判断。比如一条微博展现的,有没有点赞、阅读、推荐,如果这个用户已经读过这个微博了,就不要再显示给他。这种有一个很大的特点,它检查是否存在,每条记录非常小,比如Value1个bit就可以了,但总数据量巨大。比如微博每天新发表微博1亿左右,读的可能有上百亿、上千亿这种总的数据需要判断。怎么来存储是个很大的问题,而且这里面很多存在性就是0。还是前面说的,0要不要存?如果存了,每天就存上千亿的记录;如果不存,那大量的请求最终会穿透Cache层到DB层,任何DB都没办法抗住那么大的流量。
我们也进行了一些选型,首先直接考虑能不能用Redis。单条KV 65个字节,一个KV可以8个字节的话,Value只有1个bit,这样算下来每日新增内存有效率是非常低的。第二种我们新开发的Counter Service,单条KV Value1个bit,我就存1个byt,总共9个byt就可以了。这样每日新增内存900G,存的话可能就只能存最新若干天的,存个三天差不多快3个T了,压力也挺大,但比Redis已经好很多。
我们最终方案是自己开发Phantom,先采用把共享内存分段分配,最终使用的内存只用120G就可以。算法很简单,对每个Key可以进行N次哈希,如果哈希的某一个位它是1,那么进行3次哈希,三个数字把它设为1。把X2也进行三次哈希,后面来判断X1是否存在的时候,从进行三次哈希来看,如果都为1就认为它是存在的,如果某一个哈希X3,它的位算出来是0,那就百分百肯定是不存在的。
它的实现架构比较简单,把共享内存预先拆分到不同Table里,在里面进行开方式计算,然后读写,落地的话采用AOF+RDB的方式进行处理。整个过程因为放在共享内存里面,进程要升级重启数据也不会丢失。对外访问的时候,建Redis协议,它直接扩展新的协议就可以访问我们这个服务了。
6
小结
小结一下,到目前为止,我们关注了Cache集群内的高可用、扩展性、组件高性能,还有一个特别重要就是存储成本,还有一些我们没有关注到的,比如运维性如何,微博现在已经有几千差不多上万台服务器等。
7
进一步优化
8
服务化
采取的方案首先就是对整个Cache进行服务化管理,对配置进行服务化管理,避免频繁重启,另外如果配置发生变更,直接用一个脚本修改一下。
服务化还引入Cluster Manager,实现对外部的管理,通过一个界面来进行管理,可以进行服务校验。服务治理方面,可以做到扩容、缩容,SLA也可以得到很好的保障。另外,对于开发来说,现在就可以屏蔽Cache资源。
总结与展望
最后简单总结一下,对于微博Cache架构来说,我们从它的数据架构、性能、储存成本、服务化等不同方面进行了优化增强。