点赞,是很常见的一个业务场景。微博消息,头条文章,公众号文章......都会显示文章的点赞数。对于这类吞吐量超大(30万QPS)的业务,架构该怎么设计呢?
业务分析
我们先只考虑点赞计数。可以看到,这个业务的特点是:
(1)吞吐量超高。明星一出轨,点赞就爆炸。
(2)能够接受一定数据不一致(计数有微小不准确,不是大问题)

那么,容易想到:
(1)单独架一个计数服务,将计数与其他业务逻辑解耦;
(2)肯定不能用数据库抗实时读写流量;
(3)redis天然支持固化,可以用高可用redis集群来做固化存储;
(4)也可以用MySQL来做固化存储,redis做缓存,读写操作都落缓存,异步线程定期刷DB;
此时,MySQL和缓存的核心数据结构是:
(msg_id, praise_count)
架构如下:
似乎没啥难度,很容易就搞定了:
(1)服务可以水平扩展;
(2)数据量增加时,数据库可以按 msg_id 分库,水平扩展;
(3)读写量增加时,缓存也可以水平扩展;
计数系统的难点,还在于业务的扩展性,以及效率问题
以微博为例:
扩展一:
微博首页,有多条消息,每条消息都要显示点赞数。
扩展二:
同一条消息,不止有点赞数,还有转发数,阅读数,评论数等
假如用最朴素的方式实现,则微博首页,展示多条消息,多个计数的伪代码如下:
获取首页所有微博消息id;
for (每一条消息) {
获取阅读计数
获取转发计数
获取评论计数
获取赞计数
}
由于同一个msg_id多了几种计数,redis 的 key 也需要升级为:
msg_id:read
msg_id:forword
msg_id:comment
msg_id:praise
通过不同的key,存储不同的计数。
假设首页有100条消息,每条消息调用4次RPC接口,获取不同的计数。总计要有400次调用!系统的扩展性和效率非常低。
系统优化
首先看下数据库层面的扩展
容易想到的是:增加列,记录更多的计数。
这种方式的缺点是:
每次要新增一种计数时,就要修改表结构,很烦~
可以!行扩展是更好的方式。
表结构固化为:
(msg_id, count_key, count_value)
当要扩充业务计数时,增加一行就行,不需要修改表结构。
接下来看下redis的优化方案
原始方案,通过拼装key来区分同一个msg_id的不同业务计数。
可以升级为,同一个value来存储多个计数。
如上图所示,同一个msg_id的四个计数,存储在一个value里,从而避免多次redis访问。
总结
(1)服务化,计数系统与业务系统解耦;
(2)用缓存扛实时读写,数据库和缓存水平切分扩展。
(3)数据库使用行扩展;缓存可以用一个value存储多个业务计数;
关注【老张聊架构】,前迅雷大数据CTO,带你成为百万年薪架构师!