本文主要节选和总结自沈剑大佬的 计数系统架构实践一次搞定 | 架构师之路和文章的评论,略有删改
一、问题描述
很多业务都有“计数”需求,以微博为例:
微博首页的个人中心部分,有三个重要的计数:关注了多少人的计数、粉丝的计数、发布博文的计数
微博首页的博文消息主体部分,有四个计数:转发计数、评论、点赞、浏览计数
本文主要讨论:数据量大、高并发情况下怎么高效查询?计数属性增加怎么快速扩展架构?
二、架构设计
不同的属性计数,分别用一张表来记录,比如博文转发表,新增一个转发则新增一条记录、博文评论表、博文点赞表新增一条评论或者点赞都在各自的表里新增一条记录,用户关注表、用户粉丝表、博文表、收藏表等,也是新增粉丝或者新增博文就在对应表里新增记录。
每个用户的三个计数值需要发起三次count查询,每篇博文的4个技术值,也需要4次count的查询。高并发下数据库压力较大。
缺点:每个用户需要3个count查询,首页十多篇博文,需要几十次count查询
2.1 计数外置
额外冗余设置 2 张计数表,分别用来记录用户维度和博文维度的计数,每个计数属性在计数表里是一列字段。比如上面的微博业务可以抽象出两个表(用户计数表和博文计数表),针对这两个维度来进行计数的存储:
t_user_count (uid, gz_count, fs_count, bw_count);
t_bw_count (bw_id, forword_count, comment_count, praise_count);
查询
每个用户的计数只需要查一次用户计数表即可,微博首页每条博文也仅需要查一次博文计数表即可,甚至可以用 in 语句把所有博文的计数放在同一条sql里执行,这样微博首页所有博文计数仅需一次查询即可。
更新
MQ 消息解耦,每当发生一个计数更新的事件,比如点赞事件、转发事件、都给 MQ 发送一条消息,由下游服务负责监听消息,给对应事件的表里增加条记录,更新总的计数表,如果有缓存,需要淘汰缓存。
2.2 加入缓存提高查询性能
像关注计数,粉丝计数,微博消息计数,变化的频率很低,查询的频率很高,这类读多些少的业务场景,非常适合使用缓存来进行查询优化,减少数据库的查询次数,降低数据库的压力。
缓存中的数据结构是hash,一个实体是一个hash,hash实体的 key 是{用户id拼接上:user} 或者{博文id拼接上:bowen},hash 实体每个元素的key是具体的属性,value是对应的计数值。
hset 123:user follower 10
hset 123:user fanse 20
hget 123:user // {"follower": 10, "fanse": 20}
一次 mget 网络操作即可拿到该用户或者该博文所有的属性计数值。
2.3 一致性不高的业务场景可以用缓存提高写性能
对于写多读少,一致性要求不高的计数,也可以先用缓存保存,然后定期刷到数据库中,以降低数据库的写压力。或者发生 50 个计数事件就同步一次 db。
写操作需要先 mget 查到原来的值,然后 mset 设置新值。
2.4 计数属性增加
在数据量很大,并发很高的情况下,如果想在冗余计数表中增加一个字段,比如用户计数表中增加一个收藏博文数,会很难。好的方式是以下量两种:
1、冗余的计数表设计时预留好字段
2、冗余计数表采用竖表设计而非横表设计,把列扩展变成了行扩展。即计数表的每个计数属性值不再存在同一条横向记录里,而是把每种属性的技术值单独存一条记录,这样每次增加属性,就只需要增加一条记录即可。
三、问题
问:计数外置,增加冗余计数表,怎么解决数据冗余必将引发的数据一致性问题
- 对于一致性要求比较高的业务,要有定期 check 并 fix 的机制,例如关注计数,粉丝计数,博文总数计数等。check 机制包括定时任务扫库、日志扫增量、MQ 延时消息实现定时任务
- 对于一致性要求比较低的业务,即使有数据不一致,业务可以接受,例如微博浏览数,微博转发数等
问:一致性要求高的场景能不能用 redis 来执行写操作
对于缓存维护计数定期更新到数据库的方式,数据库的数据存在一定的滞后性,如果缓存发生重启,从db恢复数据时可能会丢失部分数据。对于一致性要求比较高的业务,不太合适。
一般不建议把缓存当数据库来用,一是 redis 持久化不如 db 好,重启有可能丢数据;二是缓存持久化频繁,会降低性能。如果非要用 redis 来提高写性能,最好提高 redis 的持久化性能。
缓存计数自增的命令是:INCRBY,可以保证原子性。
问:能不能只用 redis,不用数据库
不太建议
问:如果把冗余计数表从横表换成竖表,那岂不是会导致记录数量扩大好几倍
是的,但是数据量可以很容易通过水平扩展的方式来解决。
问:博文的评论这些是不是用 mongo 来存比较好
可以使用mongodb来应对频繁修改表的需求
问:横表和竖表怎么选择
横表显然记录条数会比较少,查询相对较快,竖表记录条数会变多,查询相对较慢,如果表结构相对稳定就使用横表,如果表结构可能频繁变更,就用竖表,大部分根据经验来判断。比如配置性的数据用竖表就非常合适。