随想:人人网之新鲜事存储实现

时间:2010-6-19     作者:smarteng     分类: 天下杂扯


现在的SNS太火了,大家都在搞SNS,

我也很喜欢用这个,呵呵,从07年开始就一直在用校内网,现在的人人网,

每天总想登陆看看好友的新鲜事,

最近被问到这么个问题:

如何设计实现好友的新鲜事存储,设计一下数据结构,以及实现的方法,

说实话对这个用户数量巨大的系统,存储还真是个问题,

正好看了一篇文章  《又拍网架构中的分库设计》[break]

从中摘录一段:

 

垂直拆分:是指按功能模块拆分,比如可以将群组相关表和照片相关表存放在不同的数据库中,这种方式多个数据库之间的表结构不同。

水平拆分:而水平拆分是将同一个表的数据进行分块保存到不同的数据库中,这些数据库中的表结构完全相同。

拆分方式

一般都会先进行垂直拆分,因为这种方式拆分方式实现起来比较简单,根据表名访问不同的数据库就可以了。但是垂直拆分方式并不能彻底解决所有压力问题,另外,也要看应用类型是否合适这种拆分方式。如果合适的话,也能很好的起到分散数据库压力的作用。比如对于豆瓣我觉得比较适合采用垂直拆分, 因为豆瓣的各核心业务/模块(书籍、电影、音乐)相对独立,数据的增加速度也比较平稳。不同的是,又拍网的核心业务对象是用户上传的照片,而照片数据的增加速度随着用户量的增加越来越快。压力基本上都在照片表上,显然垂直拆分并不能从根本上解决我们的问题,所以,我们采用水平拆分的方式。

拆分规则

水平拆分实现起来相对复杂,我们要先确定一个拆分规则,也就是按什么条件将数据进行切分。 一般2.0网站都以用户为中心,数据基本都跟随用户,比如用户的照片、朋友和评论等等。因此一个比较自然的选择是根据用户来切分。每个用户都对应一个数据库,访问某个用户的数据时, 我们要先确定他/她所对应的数据库,然后连接到该数据库进行实际的数据读写。

那么,怎么样对应用户和数据库呢?我们有这些选择:

按算法对应

最简单的算法是按用户ID的奇偶性来对应,将奇数ID的用户对应到数据库A,而偶数ID的用户则对应到数据库B。这个方法的最大问题是,只能分成两个库。另一个算法是按用户ID所在区间对应,比如ID在0-10000之间的用户对应到数据库A, ID在10000-20000这个范围的对应到数据库B,以此类推。按算法分实现起来比较方便,也比较高效,但是不能满足后续的伸缩性要求,如果需要增加数据库节点,必需调整算法或移动很大的数据集, 比较难做到在不停止服务的前提下进行扩充数据库节点。

按索引/映射表对应

这种方法是指建立一个索引表,保存每个用户的ID和数据库ID的对应关系,每次读写用户数据时先从这个表获取对应数据库。新用户注册后,在所有可用的数据库中随机挑选一个为其建立索引。这种方法比较灵活,有很好的伸缩性。一个缺点是增加了一次数据库访问,所以性能上没有按算法对应好。

比较之后,我们采用的是索引表的方式,我们愿意为其灵活性损失一些性能,更何况我们还有memcached, 因为索引数据基本不会改变的缘故,缓存命中率非常高。所以能很大程度上减少了性能损失。

索引表的方式能够比较方便地添加数据库节点,在增加节点时,只要将其添加到可用数据库列表里即可。 当然如果需要平衡各个节点的压力的话,还是需要进行数据的迁移,但是这个时候的迁移是少量的,可以逐步进行。要迁移用户A的数据,首先要将其状态置为迁移数据中,这个状态的用户不能进行写操作,并在页面上进行提示。 然后将用户A的数据全部复制到新增加的节点上后,更新映射表,然后将用户A的状态置为正常,最后将原来对应的数据库上的数据删除。这个过程通常会在临晨进行,所以,所以很少会有用户碰到迁移数据中的情况。

从中得到了启发,

大体猜想了一下人人网新鲜事的存储:

首先,人人网的用户数已经相当多了,

用户的存储也会按照规则分配到不同的服务器群上,

之后按照人人网的功能模块来垂直拆分,不同的业务分到不同的服务器上,

这是所有的业务存储,(这只是我的猜测),

关键是这个好友新鲜事的问题了,

我猜,是一个好友更新了一个业务(日志,图片,分享),之后在自己所在的服务器上记录一下,

之后通过“消息系统” , 来通知其他的好友所在的服务器,执行一个新鲜事的插入操作,这其中肯定会有Memcache缓存等的处理,这个咱们就不谈了。  

在多个服务器上写一下好友的新鲜事记录表,这样就用冗余换效率了, 用户一登陆,只要查一下自己的好友新鲜事表就可以得到人人网的登陆之后首页的好友新鲜事了,就取一次,效率肯定会高些,

接下来,就是这个“消息系统”了,如何来通知呢,关键就是这个消息系统,

如果没有消息中间件完成信息交换,应用开发者为了传输数据,必须要学会如何用网络和操作系统软件的功能,编写相应的应用程序来发送和接收信息,且交换信息没有标准方法,每个应用必须进行特定的编程从而和多平台、不同环境下的一个或多个应用通信。例如,为了实现网络上不同主机系统间的通信,将要求具备在网络上如何交换信息的知识(比如用TCP/IP的socket程序设计);为了实现同一主机内不同进程之间的通讯,将要求具备操作系统的消息队列或命名管道(Pipes)等知识。

幸亏Java中有像JMS这种消息系统(不是Java佬,这个也是听yhustc说的^_^),开源,而且有广泛的应用,所以用它比较合适,当然人人网用的什么不得而知,反正就是通过一个中间件,这个“消息系统”中间件  把插入,删除,更新操作  分发到不同的服务器上面,服务器上面的消息系统客户端  收到消息后 ,来操作数据, 实现好友新鲜事的存储。

这样一个个通知到,并返回一个状态。成功与否,消息系统再做一些处理,更新所有的好友新鲜事表完成。

SNS也是一个区域化的,也就是说,可能他一共有 1000个好友, 其中 500个好友 在同一台服务器上存储,另有一台存 300个好友,再就是其他的零散的了好友分散在不同的服务器上了,

这样所需要通知的服务器就不会很多(肯定小于等于202个了^_^),

所有开销相对就较小了,

说说这个每个用户的好友新鲜事表 ,

这个也是好友的更新记录,不是用户实际存储的数据,(用户实际存储的数据是存到”不同的业务分到不同的服务器上“),可以定期清除一下,以保持效率。

大体分析就是这样的,以上都是猜测,请不要拍砖。

感谢yhustc的指导 ^_^

————————————————————————

今天下午看了一些伟大的uchome,

大体看了一下,貌似uchome 没有好友新鲜事呀(今天下午才测试,没看仔细,要是有也别骂我),

但是有个全站动态,里面存的所有人的动态,等用户登录的时候,会取出100条来显示。

这些内容都放在 feed表中。