点赞、计数系统的设计,以及图数据的存储选型

需求

功能性需求:设计微博的点赞和计数功能


image.png

非功能性需求

image.png

分析
空间:
千亿微博,假设未来预期5倍,每条微博占0.5KB,空间消耗5千亿 * 0.5KB=250TB
对于计数服务,算每条占100B,5千亿 * 100B=50TB。
(只是估算,更准确的评估见微博工程师的blog
吞吐:
一秒百万,按一天4万秒算,每天400亿请求,假设1亿用户,平均每个用户400个请求(这有点多啊)

Service

计数和点赞关系拆分成两个服务(模块)

Storage

1. 计数服务

个人理解,问题的本质是“这是一个聚合计算功能,那么在什么时候计算?是在写时还是读时?写时计算是同步计算还是异步计算,又或是写后定时聚合计算?"
其实很多功能的设计都可以归到此类问题,比如IM系统中的”未读消息“功能(也是个计数功能),比如日历系统中的根据规则定时提醒(系统设计需要考虑是写规则时提前计算好哪天几点要提醒啥,还是每次client发起读请求则计算此刻要提醒啥)。比如设计微博,可以选择写时计算(push模型,写时fan out)也可以选择读时计算(pull模型)。又比如OLAP报表的设计。
在写请求相对较少、读请求有极高吞吐的情况下,读时计算太影响性能,因此选择写时计算,计算结果存到内存存储里。
瓶颈是一秒百万的读请求和超大的空间占用。前者通过Cache提供批量读API来优化(下文讨论),而对于空间占用问题,要用内存存储但是不能作为存储存全量数据,要做淘汰策略。

A. Cache aside,写redis+写日志流式统计存DB,按访问时间LRU
比如访问一条5年前的微博,这条微博的点赞数要查库、查出来放cache里,如果cache不够大则把访问时间最早的一条数据删掉。

B.Cache aside,写redis+写日志流式统计存DB,按发帖时间LRU;对于挖坟贴单独设置一个Cache
相当于热数据Cache as Sor,冷数据Cache aside

C.Cache aside,写redis+写日志流式统计存DB,按最后修改时间LRU

D.Cache as Sor,自己魔改存储(比如Redis)实现LRU存磁盘,不写DB。
LRU淘汰策略在上面所述的方案中选择。
这种方案的好处有:

  1. Cache as Sor可以不存空数据(但是Cache aside就得存),适合稀疏数据结构的场景。
    注:微博用的LRU策略是按发帖时间LRU,近半年的在内存,这样内存就不用存0点赞0点踩的数据了。这种策略存在个问题:如果老帖突然成为热点,会导致每次请求都查磁盘!解决这个问题,可以在近半年的内存区外,单独划分一个cache区(微博似乎管他叫cold cache区),这个估计就得存0数据了。

  2. 能定制化优化、压缩存储
    微博优化到了400亿 * 12B = 480GB

  3. 省去了在app层存取DB、处理一致性问题。减少业务层复杂度,统一管控出问题的概率也小,架构更合理。

D2. 魔改其他存储的LRU策略

E.异步统计。点赞时不会直接写cache,写日志流式统计存DB、存Cache。
这种方案可以减少写吞吐,但是写吞吐不是瓶颈,瓶颈是空间大小。

评论:微博选的D,如果没有魔改数据库能力的话,可以B

存储模型:
msgId:praise
msgId:forward
....
查询时mget

问题:能扛住写吞吐么?
假设每10次请求有1次写请求,10万TPS用Redis集群可以了;(微博每秒写吞吐只有数万)
如果再高就考虑异步归并写(写日志流式统计),不直接写Redis

follow up:怎么保证cache和db数据一致性?
A.上文 Cache as Sor方案:不存DB,Cache做持久化(定时/异步dump)、高可用,万一真掉电丢数据就丢一部分数据,用上次快照恢复。点赞可以落一条日志,存档、做离线计算。

B.上文Cache aside方案,异步统计,不一致就不一致了

follow up:怎么解热点写问题?
A.发现热点,然后降级为异步统计。说起来简单,实现起来麻烦
B.异步统计
C.几万每秒的写,单机能搞定,不需要优化

问题:能扛住读吞吐么?
百万用redis集群砸机器,热点用sdk统计发现热点、存本地缓存定时刷新
优化思路:
A. 批量查询

问题:扩展加字段?
A. 新字段用新key
key占用空间大。
B. 新字段存value里
如key=1,value=1:0:2:1
C. 魔改Redis,提供同key加字段能力

2.点赞关系

本质上是图数据怎么存储的问题。一个难点是全量存内存的话量太大,把内存当缓存又很容易Miss,而只存hbase这种存储百万级QPS访问量又太大。

A. 热数据(如半年内)全量存储在内存,冷数据存磁盘(如hbase)+cache
存储模型
a1方案,用kv
key=userId:weiboId value=1
a2方案,用set
key=userId value=weiboId1,id2,id3...
a3方案,用set
key=weiboId1 value=userId1,userId2...
缺点是可能会有热点问题

评论:内存和hbase都用a2,或都用a1

B. 还是sharding+冷热分离的思路,Mysql做足够多的分库分表,比如拆1000张表,历史数据定期归档到另一个库。

评论:基础设施完善的话,B简单一点,配一配分表、数据归档,不用开发冷热逻辑/cache逻辑。A还是B看实际情况。另外A和B都要考虑历史数据DOS攻击

C. Bloom filter
//TODO 需要多大空间?

D.分布式图数据库
//TODO 待调研

3.如何实现“赞过的人”

image.png

还是图数据的边怎么存储的问题。同上面不同的是查询需求,上面要的是Exist查询,这个是order by 时间limit xxx

A. 在Redis等存储给每条微博维护一个有界队列;DB里存每个边(点赞关系)的明细数据。
内存的容量问题用上文的冷热分离方案解决;DB选型针对查询需求有几个选择:
a1.MySQL做sharding
a2.NewSQL
a3.支持后缀排序、前缀匹配的KV存储,key=weibo:userId:timestamp。hbase好像不支持后缀排序
a4.MongoDB这种查询能力丰富的

B.异步定时计算

REF

[WeiDesign]微博计数器的设计(上)
[WeiDesign]微博计数器的设计(下)
每秒30W次的点赞业务,怎么优化?
[WeiDesign]微博计数器的设计 velocity
新浪微博「点赞功能」数据库如何设计的?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容