我们在开发中,经常有一些计数相关的工作。例如,抖音上的短视频,有评论数,播放数。发送微博的时候,也有转发数,评论数,点赞数,电商网站上,有着商品售卖金额,商品的出售件数等统计。那么,这些统计是怎么做到的呢?
不难想到,我们可以在用户查看这条评论的时候,去统计总共有多少个赞。举个例子,你在虎扑上发表一条评论,每当有人点赞的时候,都会在评论下面增加一条点赞记录,记录谁谁谁点赞了。当有人访问这条评论的时候,我们可以使用Mysql的SelectCount语句,对其进行统计,查询总共有多少条记录,并且返回给前段。这种方案有什么问题呢?当数据量小的时候,我们总可以非常迅速地计算到结果。但是随着数据量的增大,例如一条非常火的评论,可能已经有数十万个赞了,每次都去查询效率都比较低。评论的内容是核心数据,评论的点赞数是次要数据,如果查询点赞数量的耗时过长,反而会影响主要业务。我们要做的,是尽量地少去查询,或者让查询的速度更快。
在此之前的方案,我们每次都是全量Count一遍,那么,我们能不能把这个数据存起来呢?这样子,就不需要每次有人来查询的时候,都去统计。每次有变更的时候,就去数据库里面进行selectCount,查询后将结果保存下来。之后每次有人访问这条评论,我们就直接返回,而不需要进行SelectCount。当然,每次都去SelectCount也是一个笨方法,我们为什么不每次有人点赞就+1,有人取消点赞就减一呢。我们将点赞数据的电话分为两种事件,一种是+1,另一种是-1。当然,这种做法的潜在风险点就是并发问题,例如原来的数据是5,突然来了两个+1的请求,由于他们并发执行了,最终的结果变成6。这种情况下我们需要加锁防止并发,或者让数据库帮我们实现。
仅是这样是远远不够的,我们如何面对突发的流量呢?例如突然爆发了一个热点事件,突然火了一条评论,可能10分钟之内就已经几万个点赞了。假如我们每次都去数据库更新,那势必效率会大大降低。为了应对这种突发性的流量,业内的解决方案大家其实都非常熟悉,不就采用消息队列进行削峰嘛。但是,这种突发性的流量容易造成MQ堆积,那么有没有什么好的方法呢?其实非常简单,本来我们要进行10次+1的操作,为什么我们不合并成1次+10操作呢。这样就能大大减少数据库的压力,我们一般有两场常见的实现方式,一种是对异步队列进行任务合并,另外一种是我们可以将计数存在缓存上,定期将数据刷到数据库中。
我们更多采用的是第二种方法,并且我们还会进一步优化,就是我们会将点赞的统计存放在Redis这种的缓存中间件上,然后再定期刷到磁盘上。对于这样的统计数据,冷热是非常明显的,基本上,采用采用这种方案,已经可以适用大部分计数系统了。欢迎大家关注我,共同学习,共同进步。大家的支持是我继续唠嗑的动力。