计数器

概览

为什么要引入计数器

计数器的作用如下:

1、简单的好处是:可以加速读取的时间。

2、对于大量(80%)帖子,其实都没有任何数(评论,点赞)。对于计数器,内部为0的数据,可以减少很多timeline的读取操作。

一、需求背景。

现在计数场景越来越多,而我们当前读取的方式主要是两种。

  1. ​ 数目(转发数,收藏数等)直接写库。更新方式: update set count = count + 1 where id = ?
  2. ​ 数目(点赞数)直接读取timeline : 读取方式为 select count() from ** ;为什么不使用zcard读取list size,这是因为timeline是会过期的,并且很容易过期。为了计数加载全量缓存的方式很不明智。

两种方式分别有什么问题?

​ 第一种方案,在高并发场景下,存在锁等待超时的问题。大概可以参考:https://blog.csdn.net/miyatang/article/details/82454999

​ 第二种方案,随着表数据越来越多。查询时间会越来越长,效率越来越低,并且,如果伴随着数据增多的情况下,一次query的耗时很容易20ms+;

二、需求场景

​ 回头来看,我们需要这些数据都有哪些场景。

  1. 读多写少或者说更新少。

  2. 更新频繁。如:阅读数、本场景的音频播放数。

  3. 希望的请求结果是:以获取一个feed(帖子)来举例。当db缓存失效的时候。我期待的最优结果应该是:

    1. 我读取一次db,拿到全部的值(点赞数,阅读数等)

    2. 我期待的次优结果应该是:我读一次db拿到大部分的值,剩下的值,通过读redis来获取。

      现状是:我读一次db,只能拿到部分值(评论数,转发数)。剩下的数(点赞数),需要通过count(*)的方式来获取。耗时较长。

三、计数器的方案预想

​ 从上面的分析而言,我们需要的方案有哪一些。

  1. 最好的方案:我期待读库能拿到全部的值。但是dau上涨之后,通过 update set count = count + 1 where id = ? 这种方案可能会有锁等待超时的问题。
  2. 次优的方案,我能通过读一次db拿到大部分的结果,剩下的都读cache拿到剩下的数。
  3. 强一致性的方案:如评论数,我评论完了,肯定希望立刻看到评论数+1.
  4. 弱一致性的方案:如音频播放数:我隔一会看到变化都没关系。

四、方案设计

​ 针对上面的四种场景,对应的设计方案主要是两种。

1、单开计数器

​ 像点赞数:使用redis.incr 操作,查询时间复杂度o(1)。 可以满足现在的次优方案、且强一致性的方案。

​ 缺点:读取相对比较麻烦。数据同步比较麻烦(数据统计也有一定问题)。

2、优化处理写库的问题。

​ demo:基于计数器和时间片的方案:优化 update count = count + 1 → 转化为: update count = count + N;

​ 一方面可以削峰,处理高并发带来的锁等待超时的问题。另一个方面可以满足最终一致性的问题。

​ eg: 以N = 10 为例。当播放数count 等数据,每次count % 10 ==0 的时候 或者 时间片耗尽的时候,我们才去更新库。这个时候也可以保证最终一致性。

五、方案补充

  1. ​ 离线数据怎么存储?kafka → hdfs
  2. ​ 热数据怎么来