feed-timeline系统架构分析

几个大型网站的Feeds(Timeline)设计简单对比
http://blog.hongtium.com/feeds-timeline-design/

 

 

参考:http://blog.hongtium.com/feeds-timeline-design/

http://www.csdn.net/article/2010-07-26/277273?bsh_bid=17053953

 

新浪微博架构与平台安全演讲稿
http://timyang.net/architecture/weibo/

如何构建一个微博型广播
http://codecampo.com/topics/61
http://codecampo.com/topics/196

How to Build a Fast News Feed in Redis
http://blog.waxman.me/how-to-build-a-fast-news-feed-in-redis#

 

How efficient would Redis sorted sets be for a news feed architecture?

http://www.quora.com/Redis/How-efficient-would-Redis-sorted-sets-be-for-a-news-feed-architecture


新浪微博开放平台Redis实践
http://blog.nosqlfan.com/html/3295.html


pull模式的问题:
1. 数据量的控制问题,如果用id in (1,2,3,4)这种方式有可能一个查询花好几秒。
2. 如果实现数据合并?
3. 访问权限的问题。


push模式的问题:
1. 数据量的问题?百万粉丝。
2. 新关注,如何重新push?

 

小云关注列表:[老轮子,ZJ]

产生的动态数据:

|ID |MESSAGE_ID| CREATE_TIME  | USER    |
|1  | 1        | 20:00        | 老轮子  |
|2  | 2        | 20:00        | 老轮子  |
|3  | 3        | 20:30        | ZJ      |
|4  | 4        | 20:30        | ZJ      |
|5  | 5        | 21:00        | 老轮子  |
|6  | 6        | 21:00        | 老轮子  |

filter(user=[]).order(create_time).limit(5)

 

 

 

当一个人关注另外一个人的时候,那么被关注的人发表的图片是能被另外一个人看到的,这和微博的feed流是一样的。
这里有两种方式实现,push模式(推模式),pull模式(拉模式)。push模式:当一个用户发表图片的时候,检索这个用户的
粉丝表,为每个粉丝插入一条feed。pull模式:当一个用户发表图片的时候,只是存一份数据,当一个用户查看他的关注的用户的feed的时候,需要先检索关注表,然后检索每一个关注用户有没有新发的feed。

push和pull模式各有利弊,当使用push模式的时候,如果一个用户(名人)的粉丝过多的话,那么每个粉丝插入一条feed对存储的压力就太大了(100000粉丝话,插入100000),存储空间浪费也非常的严重。
pull模式的话,如果一个用户关注过多的时候,查询该用户的关注列表也是有很大的代价的

项 目用的是pull模式,注意到求购项目的特殊性,一般只是看最新的求购的消息,假设某个用户当前有1000条新feed,这个用户也不能完全的看完,所以 认为只要取得最新的消息里面的若干条,其余的消息的话可以在历史feed列表里面看到或者下一次看到,这里就有个timeline的概念。当次取得最新消 息后,需要把这个时间点保存,下次查询的时候,从这个时间点进行查询。

项目具体的做法:
1.查询用户的关注列表
2.从cache中取出timeline
3.timeline为空的话,直接取出旧的feed 30条,更新timeline,其中最新的一条的创建时间为timeline
4.timeline不为空的话,取得timeline之后的feed,最大值为30条,查过30条,取得的是最旧的30条,更新timeline,其中最新的一条的创建时间为timeline。


第4步其实和微博的做法是不同的,假设有1000条feed,微博是一次性刷出来,而这个是一次刷出30条,这里有个博弈在里面:
一次刷新30条,刷新的次数多了,查询多了,但是一次1000条的单次查询压力大了,还有多少人看了30条之后继续刷新看的? 所以我们采取降低单次刷新的代价,这样对服务器峰值的压力也就下来了

 

以下是人人网技术经理张铁安的演讲内容:



下面我想说一下我们这个系统面临一些挑战。对于人人网这样一个网站来说,活跃用户是非常多的,一天可能有几千万用户。我们计算一下,当然这个数据可能不是 一个真实的数据,我们认为每秒会产生一千条Feed、一千个客户会产生一些内容,到系统里面我们要处理原始数据可能是几十亿的规模。再说一下Feed的特 点,当我改一个状态我好友所有收到这些信息就是一个扩散问题,我们需要把这个数据给这些所有想要收到数据的人看到,所以这个Feed扩散范围很大。如果我 有100个好友我要扩散到100人,如果我是一个明星就更多人会看到。

新鲜事物有这么一个特点,我发了一篇日志就两个朋友看了觉得很有意思就把这个日志分享了,如果另外一个人是那两个人的朋友,他的页面上有两个一样内容分 享,这样可能会有问题。我们会采取一种策略,把两个相关的新鲜事合并,或者做一些替换,排序,合并这些是比较复杂。另外就是用户请求量对人人网最大的请求 量就是登陆的请求量。最后一点我刚才已经讲过各个业务需求要求对新鲜事做不同的筛选。

然后讲一下关于系统设计当中的两个问题,推的模式和拉的模式。两个模式区别在于什么地方?推的模式意思就是说当一个事件产生的时候,我把这个事件产生时间 点做N次拷贝发给他想要的人。拉是另外一种方法,当一个用户登陆页面的时候,首页要显示所有好友关注人的新鲜事。这个时候用拉的模式实现。就是说我登陆 了,我查我的所有跟我有关系的列表,拿到这些列表根据这些人对应新鲜事列表里面取所有的新鲜事再做排序,归并的策略。推可能是非常快的操作,推过去以后, 那边立马有了。我们登陆列表是现成,取的时候会非常快。但是有一个问题,比如说我有几个亿用户,但是活跃用户只有几千万,剩下几个亿的用户他们可能是半年 来一次,或者说一个月两周过来一次。这些数据给他以后他可能根本没有机会看到,这样就浪费了很多资源。拉模式不会有这个问题,但是会有另外一个问题。你请 求量很大,当用户登陆必须很快返回数据的时候,运算量是非常大的。综合所有考虑,因为我们要做的是一个要求实时度很高的系统,我们还是选择推的模式,但是 在用推的时候有些地方是可以做一些权衡的,把不必要系统开销可以去掉。

这是我们现在Feed这个系统的各个层面。第一是新鲜事分发,就是说我发了一个东西以后,要把这个事情告诉所有跟我有关系的人,这个事就是页 dispatch完成的。后面有newsFeed索引的服务,跟我们新鲜事有关的东西,包括用户的反馈,还有我们一些排序方法,跟好友关系,整个在SNS 当中的朋友圈子有关系的一些东西,比如说哪些好友跟你关系很亲密,你跟你老婆关系可能很亲密跟他悄悄话我们都知道,还有一些你经常一起玩的朋友,你们这样 一些人的关系可能会相对比较紧密一些。我们在考虑新鲜事排序权重时我们会考虑把你老婆心情放在排序最上面,要第一时间响应领导的指示。

这个是跟我们新鲜事排序相关,包括Feed排序一些算法,还有跟社会化网络相关的。我们正在做的基于新鲜事内容的一些兴趣把内容分类,有点像百度百科,我 们知道哪些用户对音乐感兴趣,哪些用户对科技或者对政治感兴趣等等。这些我们会通过一些系统计算,最后反映在新鲜事排序里面。下面是MIINFeed就是 自己发的新鲜事的列表,另外还有一个是新鲜事本身内容,我发了一个日志新鲜事,能够看到就是这个摘要几十个字简短的摘要。下面说的是我们新鲜事对于索引数 据量是非常大的,我们会讲一下,索引数据对我们来说有什么意义。当我们用户取新鲜事需要查他的索引,以后再去取内容,这个东西内存CACHE丢失这个用户 页面上什么都没有了。所以我们要做持久化。INdexdb数据会有一个列表,写到硬盘里面,最后是我们渲染引擎,我们有很多的输入和很多输出,不同输出要 求不一样,比如说我们给手机输出格式和客户端格式是完全不一样。所以这两个东西都是由一个系统完成。

这个是我们看到新鲜事的简单结构图。里面内容不是我们现在线上系统的整个东西,可能只是其中一部分,我是把最重要的东西拿出来。一个笑脸就是一个人在上面 很开心,他发了一个日志通过我们网站日志相关的负责日志模块系统把这个日志内容发新鲜事系统里面,首先拿到就是Dis 把这个数据进行一些处理,把这个内容最终分发到三个不同的地方,第一就是newsFeed,比如说我发miniFeed有需要,第三是要把这个新鲜事产生 本身内容要cache起来,会发给我们一个集群。下面会了解我们持久化这一块,MINIFeed量很小,我们做一个数据表就可以存下来。我们闪存100 份,ID结尾为1放一起,这样一种散表的策略分散在机器上分担压力。我们再说一下当一个用户登陆人人网要取新鲜事的逻辑。如果是一个网站用户登陆以后设备 要访问一个服务器,会并节一些新鲜事的内容,我们并没有用传统意义上的服务器,特点就是说能够支持很高的用户的并发量,同时速度会非常快。我们整个网站新 鲜事的WEB服务器只有四台,会提供一个对外的PUSH的东西,也会提供一个拉模式,网站取数据就是拉的模式。这个地方做的工作其实就是用来对新鲜事的数 据和模板进行匹配,然后合并产生成TML,把数据和模板匹配在一起形成一个模块。

后面是一些技术细节的东西。第一是分发系统;第二是cache;第三是持久化存储有渲染等等。

整个系统我们现在设计到Opnesource相关的,第一是ICE我们整个人人团队里面引擎这一块使用量最大的一个通讯框架,为我们提供了一个很好的 cache集群,为我们很好的进行数据交互网络通信方面的一些东西。其实我们很多系统是基于这个开发的,第三是memcache,所有SNS的公司如果没 用这个就不算2.0,我们也用用。我们在下层应用层之间有一层代理,有点像代理服务器的感觉,实现了一些负载均衡策略等等。下面 googleprotobuf对象的序列化及反序列化,这个东西其实可以说是非常好的,包括谷歌内部都是使用这样一个东西,我觉得非常好。下面二进制数据 压缩,还有多索引结构,海量存储引擎大体就是这些东西。

下面是Feed的分发系统。用户发送一个新鲜事的时候传给我们系统数据包括新鲜事数据内容,还有一些合并策略一些权重数据。一个对象是很大的,加起来可能 有几K或者几百个字节大小不等。首先要做的事情是要把这个数据拆一下,拆成公共用于数据再展示使用的一些文本数据,另外还有一个就是用来做排序。怎么定位 Feed这样一个索引结构?索引结构我们系统内部INDEX架构大概一个尺寸只有32个字节,CONTENT就很大了,这两个数据会分别发到不同的地方, 索引数据一个跟NEWSFeed另外一个给MINIFeed。我们发一条新鲜事,比如说有一个100个好友发一个日志用推模式,发一条新鲜事,我要索引结 构告诉我在我好友列表里要追加这样一个新鲜事的索引,要知道我好友有哪些该把这个内容发给谁,这个操作是什么量级?一秒钟要有一千次。我查列表有一千次, 有100个好友就是100。我是一个名人,有上百万粉丝这个是吃不消的。我们第一次查列表可以到数据库取,第二次就要到内存,不能到数据库上面查。最早的 系统,大概一年多以前没有内存cache,说你这个东西搞得我们天天数据库是红的,我们做了以后就很好了,机器基本上没有什么负载。第三异步线程池,有的 时候会有一些脉冲爆发,我们要做一些控制。当我一秒钟有一万次请求,有一个脉冲一下来了一万个请求,一次给我,我可以做一个细水长流慢慢消化掉。对用户来 说朋友看到你的改的状态是在几秒钟以后,不算特别迟。但是你俩又没有在一台电脑面前,所以他感觉不到,稍微把脉冲数据做一个平滑的曲线。对于系统的负载能 力有一个很好的提升,在分发里面对线程池数量是一个很重要的东西。

做讲一个Feedcache的内存优化,讲设计模式的时候,叫Flygeight。当时在书里面一个例子,说我们在WPS做文本编辑,每一个文字有各种属 性,字体大小等等,但是一篇文章同一个字出现N次。我们把大的数据描述数据对象在全局只有一份,我们需要使用这个字的时候,对应那个位置存一个字根,对象 在一个系统里面重复出现概率很大。这样的操作对我们的帮助是很大的。我们曾经试图用这个做,但是发现在性能方面有一些问题。

我们把新鲜事内容分成两种,一种是数据内容,另外一种是索引数据。索引数据相对来说比较小,我们在另外一个cache存储这样一个索引,其实从宏观上也满 足flyweight理念。我们索引要发给100或者500人,他们拿到只是一个索引对象,真正指向内容都是同一件事。对于每一个索引cache内部我们 也利用了同样的思路,因为比如说我们散服务,我们把前站用户放在十台机器上面,也就是说我如果有100个好友,每台机器上面平均算每一个服务上面有十个人 的对于同一个新鲜事索引可能在每个服务里面出现十次,做这样一个东西我们认为一个索引结构32字节,用最小东西指向32字节又可以节约一些内存开销。

然后我们INDEX五要支持不同业务对不同的选择条件。有同学问内存怎么建一个索引。类似一个人存数据库数据库支持什么,叫一个多索引,一个数据库表里面 可以建N个不同的索引,甚至有联合索引,但是我们很少在内存里面能够实现这样一个结构。如果我们自己实现可能很复杂,对于新鲜事我要按照不同纬度建立索引 怎么办?其实提供了一个数据结构,我们可以对不同的纬度做同一个索引,对对象里面同一个内容做更新,字节也会自动跟着做变动。看到下面云里面放了四个对 象,形状不一样,第一是按照形状对四个对象做一个排序,第三是大小,对同一个是四个不同对象,这样类似对象能够支持不同的索引,我们使用它可以很方便实现 多索引的结构。

关于内存的压缩存储,可以很明显节约内存。右边图是quicklz对比图,这个压缩和解压缩速度都是非常好,使用过程中我们就使用了一种方式就是把对象进行序列化,再做压缩,在我们系统能够节约30%-35%的内存。

然后讲一下我们为什么要用memcache。第一我们要支持高并发,一个用户页面显示30条新鲜事,我要进行30次,把30次我想要的对象取出来再发给前 端做显示。对于人人网这么大一个应用可能每秒PV就好几万,我们需要这个东西搞定内存的cache。还有一个就是我们数据量大,大家也知道现在服务器的内 存也是越来越大,原先刚到公司我们用的是16G的内存觉得已经比我们PC机大很多,再过一年,变成32、36,现在服务器搞到72G。我们要做内存的 cache,对数据查询要求,随着内存里面cache内容不断增加,我们要保证查询性能不断增强。我们保证相对在我们数据量不断增加时查询性能有些下降但 是不能特别大。另外一个,当我们cache不可能放在一台机器上面,当一些服务器被重启,我们需要cache量更大,要加一些机器进来。我们要保证整个 cache能够有扩容性,同时可以很方便摘掉一些机器, 我们需要所有cache互相之间有一些冗余。最后我希望我们cache策略、机器足够多,我们现在有十几,二十几cache服务器,当我们做到上百台,几 百台机器的cache时,我们需要保证对于所有的cache服务器管理更加的方便,不是说要重新部署一次。我们是跟FACEBOOK学的,想做这样一个东 西,我讲一下自己开发的东西只是MEMcache的PROXY,做这个开源项目有两个,但是这两个项目我们调研了一下不是特别理想,另外有一个动力让我们 自己做这个东西的原因是因为我之前是做客户端服务器,对这种通讯等等东西还是比较有信心,另外一个就是说mbmcache协议是很简单的,所以我觉得这个 东西我们有把握做好,我们就做了一下,结果做成了。

基本的一个功能是什么,就是说在这个层面上我们把所有的cache的管理都放在上面,包括策略也放在上面,我们有一个cacheLOADER。我们新鲜事 操作都是PV6,到数据库里面查也是ID等于什么,这样的话我做一个cacheloader可以很好跟memcache做配合,比如说我不做新鲜事,我要 加东西的时候只需要在cacheloaler做一个配制。这样的话避免了开发人员重复开发一些用于加载的服务。另外一个就是为什么要有关 cacheporxy,因为如果没有这些,我们跟所有散服务必须放在客户端上面,这个事情会给开发使用这个集群的人带来很多不方便,随着我们客户端不断增 加,如果其他的业务不断增加,使用这个集群的人越来越多,会带来相同困扰,有这么一层我们就可以保证这个问题。

下面一个进索引持久化系统。为什么要做这个东西,是因为我们在一年以前,还没有这个东西的时候,当时经常会有一些问题。新鲜事有一些大改动,要把我们索引 cache重启,但是我这些cache在数据库里面是没有办法存的,因为这个量很大,我们刚才说每天如果我们产生的总的新鲜事量是千级万级以上的量,平均 每个人有100个好友,其实总的一天产生新鲜事索引在几十亿规模。我们想把这些索引都存下来大概需要多少台机器?可能需要上百台机器。所以如果一秒钟处理 十几万,或者几十万至少也要100台以上的机器,我们必须解决这个问题。另外我们不解决这个问题怎么做,内存索引cache没有的情况之下,我们需要把原 先所有用户产生的这些新鲜事的过程从头到尾再放一次。

刚才说传说中的解决方案,MYSPL是不欣,APENSOURCE还是不够快,第三就是说GFS可能解决这个问题。但是我们这个系统买不到。我们做这个的 时候,我们做了一些调研,这个里面包括新浪支持,还有百度支持的。大致上我们需要在每秒钟解决十万次。第三就是我们所有的每天产生的索引数据总量每天 100G以上,解决的思路是什么?第一就是普通机器我们随即读写访问就是IOPS也就能道800+的量。既然硬盘只能这样,我们怎么解决?据盘读取数据是 一块一块读,我们索引很小,一个索引大小改动,我们会浪费很多写的资源,我们必须要把随即大量随即写变成一种顺序写文件,我们就要把这种所有的随机的东西 变成一种顺序的问题,如果能够变成顺序的东西,我们用普通的机器可以搞定这个问题。

另外一个就是说如果我们要做这个事情的话,要做一些比如说IO五方面的东西,在使用的时候用了异步IO马行,直接操作硬盘,使用的时候我们也跟英特尔做调研,选择他们SSD提高硬盘写的性能和读的性能。

我们必须要把所有写合并,把大量随机变成顺序写操作,既然需要做合并,肯定我们需要先把所有的随机索引的写操作放在内存当中做一些滞后,整合以后再做写 读。我们会把所有的写读操作通过LOG文件,把LOG记录下来,机器宕机我们可以通过回放把这些数据读出来,我们使用TT保存索引,为什么很快?因为他所 有数据跑一遍都在内存里面,所以跟内存操作是一样。我们使用TT做了一个东西,TT支持存储方式比较简单,作数据节点上面IO模型我们选择异步IO。为什 么用direct为IO屏蔽OS五的cache策略,最后使用SSD解决大量的并发读取。

这个是整个系统节点,nidexNode责任存储userid到最信一块data,block的位置信息,我们把5亿用户的用户ID到索引块信息都放在内 存,也用不到10G,用TT保证系统里面所有文件至少全部在内存里面放下。我们用32G机器放这个文件。另外TT实现的时候用的是共享内存实现方式。只要 机器不死,节点服务被我杀掉,操作系统还在,内存还在,系统会把数据刷回硬盘。下面是DATAFILE,这就是DATAFILE的结构,左边是 FILE1,右边是FILE2。
最后讲一下模板渲染。说到数据格式的一致性,我们现在新鲜事数据格式是用Feed的输入很多来自各个不同业务,必须保证数据格式的一字形,输出时,通过渲 染引擎将数据变化为不同的VIEW,提供给各业务。技术方案Ctemplate提供高效的模板选择能力,还有谷歌的方式。

 

提问: 你说光良有几百万粉丝,我们选择推模式,我们要把几百万粉丝里面每一个粉丝把信息推送到他们上面去,快速获得这些粉丝信息,粉丝信息是放在内存里面。所以我想了解,如果是我这几百个放在内存是一个方式,但是几百个粉丝是怎么组织的?

张铁安: 就是一个列表,我们发送会有一个表达式,我们放的时候不是说把所有放在里面,我们其实只做了一个几万的队 列。为什么这么做?有这么几个目的,我们只对好友新鲜事,对于粉丝有几百万,这个列表实时有人加进来,这种情况下没有办法做一个像好友准确策略。我们做一 个队列,不要太长,我做一个几万,用户登陆行为是这样,上人人网以后在这个网站玩就是几十分钟就会关掉,关掉以后cache就没有多大意义,所以逐出数据 比较多。

提问: 你说现在我把用户放到内存里面,查询索引是通过ID查。我们数据库里面是通过ID把这个ID和索引放到cache里面。

张铁安: 我们两个内存cache是用ICE做的。

提问: 就是说这里面有一个像硬查询,很多里面都是ID,把这个硬查询也拆借出来。

张铁安: 我们memcache机器再多也不可能把所有新鲜事放到里面,我们现在要取一千个列表,会有不到一千个列表MIS有一个长尾效应,大部分热点数据要进行cache,对时间特别长的以前数据是不需要cache的。

你可能感兴趣的:(feed)