去年年底和今年上半年,做了一个类似微博的sns的项目—求购项目,有求购消息,评论消息和好友关系,也和项目组的同学一起讨论了项目的设计,不敢说架构,因为好多底层的存储,分库分表的功能我厂的其它团队已经完成。
表设计
1.求购消息主要是图片和文字的说明,这个其实可以看成是个微博每个用户发表feed的表,但有些不同,这个下文再说,这里我们叫photo表,下面所说的图片就是求购消息。
考虑到求购消息比较多,所以需要进行分表,假设分8个表。
由于业务的需求是:可以查看单张图片,可以看一个用户的图片列表,问题来了,我们按什么进行分表?看下表结构:
id是图片的id,要查询单张图片的话,需要能快速的定位到哪一张表,而查询一个用户的图片列表的话,需要快速找到这个用户所有的图片,那么这个用户的图片应该是放在
一个表里面的,即通过user_id能路由到一张表,从而从这张表里面取得这个用户所有的图片。
通过上面分析:如果选择user_id对表数量取模的值为选择存储的第几个表,
那么一个user_id所有的图片都在一张表里面了,这里对id就有要求,id对表数量取模的值要和user_id对表数量取模的值一致 。
这样做的结果是,id的值不是连续的,有些许的浪费,如果user_id分布不均的话,对于每个表的数据量也会分布不均
2.评论表
对图片进行评论,对于单个图片来说有评论列表,对于单个用户来说,他能看到所有对他图片进行评论的评论列表,所以采用了两套表的方案,对于用photo_id分表的
的comment表,以及针对user_id分表的comment_u表
3.好友关系表
这个类似于微薄里面的关注或者粉丝表,和评论表一样,这里需要两套表,一个是关注表,一个是粉丝表
Feed流
当一个人关注另外一个人的时候,那么被关注的人发表的图片是能被另外一个人看到的,这和微博的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条之后继续刷新看的? 所以我们采取降低单次刷新的代价,这样对服务器峰值的压力也就下来了
其它
还有很多优化的点没有说出来,比如路由表取模的时候,是用位与操作的(18%16==18&15)
还有各个能用到cache的地方尽量使用cache,对于读写的话,可以增加读写队列.
对于扩展,性能,后续考虑的:
当单个用户关注数超过一定数目,这个用户就需要特别的对待,比如用户关注单独的变成一张表,减少关注表的压力
对用户活跃度进行分析,活跃用户的feed使用pull和push模式相结合的模式
对于粉丝数很多的用户,可以单独成表,发的feed可以先push给一部分用户,让一部分用户先看到feed.