Redis作为计数器,统计PV、UV、IP的几种方案(集合、位图、HyperLogLog)

参考 https://www.kxler.com/2022/09/01/redis-counter-pv-uv-ip-by-sets-bitmap-hyperloglog/
内容纲要

最近在工作中有一个统计图片浏览量和保存次数的需求,由于性能问题,我们不能频繁的操作MySQL数据库,在这里就可以使用redis作为计数器,再定期更新到数据库。下面我总结了几种常见的PV、UV、IP统计方案。

  • PV(Page View)页面浏览量
  • UV(Unique Visitor)独立访客数量
  • IP(Internet Protocol)独立IP数

Redis INCR

简单的key-value计数器,key包含要统计对象的标识,value为访问量。

命令介绍:

  • INCR:将key值递增1,返回最新的值。

INCR是原子增量,即使多个客户端针对同一key发出 INCR,也永远不会进入争用状态。例如,客户端 1 读取“10”,客户端 2 同时读取“10”,两者都递增为 11,并将新值设置为 11,这种情况永远不会发生。最终值将始终为 12。

使用示例:

# 新增文章123的访问量
>incr article:123:view
1
>incr article:123:view
2
# 获取文章123的访问量
>get article:123:view
2

适用于:

PV、无需去重的统计

Redis Sets

redis集合元素具有唯一性,可以轻松实现去重统计。

命令介绍:

  • SADD:将新成员添加到集合中。
  • SREM:从集合中删除指定的成员。
  • SISMEMBER:判断元素是否是集合的成员。
  • SINTER:返回两个或多个集合的交集。
  • SCARD:返回集合的大小(又名基数)。

使用示例:

# 文章123被用户2访问
>sadd article:123:user 2
1
# 文章123被用户6访问
>sadd article:123:user 6
1
# 检查用户2是否浏览了文章123
>sismember article:123:user 2
1
# 文章123的总用户访问量
>scard article:123:user
2

假设每个文章需要存储100w个用户id(id依次递增1-1000000),那么内存占用量为:
6543216 bytes = 6543216/1024/1024 MB = 6.24 MB。
单个对象需要的内存不多,但是如果有1000个对象(文章),那么存储量将是 6.24 * 1000 = 6240 MB,这就很离谱了。

适用于:

少量数据、精确的统计、独立IP数

性能:

大多数集合操作(包括添加、删除和检查项目是否为集合成员)都是 O(1)。这意味着它们非常高效。但是,对于具有数十万或更多成员的大型集合,在运行SMEMBERS命令时应格外小心。此命令为 O(n),并在单个响应中返回整个集合。作为替代方法,请考虑SSCAN,它允许您以迭代方式检索集合的所有成员。

对大型数据集的集合成员资格检查可能会占用大量内存。如果您担心内存使用情况并且不需要完美的精度,请考虑使用Bloom过滤器或Cuckoo过滤器作为集合的替代方案。

Redis Bitmap

位图,用一个bit位来表示某个元素对应的状态(0或1)。

位图的最大优点之一是,在存储信息时,它们通常可以节省极大的空间。仅使用512MB 内存即可记住40亿用户的布尔信息(例如,知道用户是否想要接收新闻稿)。

命令介绍:

  • SETBIT:对key所储存的字符串值,设置或清除指定偏移量上的位(bit)。返回值为偏移位上的原始值。

具体语法:setbit key offset value,其中offset是偏移位,通常可以是对象标识ID;value表示对偏移位清除或设置,即0或1。

  • BITCOUNT:返回key所储存的字符串值设置为 1 的位数。

  • GETBIT:获取指定偏移量上的位值(0或1)。

  • BITOP operation destkey key [key ...]:在多个字符串key之间执行按位运算(AND、OR、XOR 和 NOT),并将结果存储在目标键中。

  • BITPOS key bit [start [end [BYTE | BIT]]]:返回字符串中设置为 1 或 0 的第一个位的位置。

使用示例:

# 文章123被用户6访问
>setbit article:123:user 6 1
0
# 文章123被用户9访问
>setbit article:123:user 9 1
0
>setbit article:123:user 9 1
1
# 检查用户9是否浏览了文章123
>getbit article:123:user 9
1
# 文章123的总用户访问量
>bitcount article:123:user
2

假设每个文章需要存储100w个用户id,占用的内存是 1000000/8/1024/1024=0.11920929 MB。
如果有1000个文章,那么存储量将是 0.11920929 * 1000 = 119.20929 MB。
由此可见,当统计较多对象时还是比较消耗内存的。

适用于:

集合成员对应整数0-n、精确统计、 统计对象key单一或较少、用户月签到记录等

Redis HyperLogLog

HyperLogLog是用来做基数统计的算法。它的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定的、并且是很小的。

Redis HyperLogLog 牺牲数据的精准性来节省内存的占用空间,只需要12K内存就能统计2^64个数据, 0.81% 的标准误差。

命令介绍:

  • PFADD:将项目添加到 HyperLogLog 中。
  • PFCOUNT:返回集合中项目数的估计值。
  • PFMERGE:将两个或多个HyperLogsLogs合并为一个。

使用示例:

# 文章123被用户3访问
>pfadd article:123:view 3
1
# 文章123被用户95访问
>pfadd article:123:view 95
1
# 被添加元素可以是字符串
>pfadd article:123:view abc
1
# 获取访问量
>pfcount article:123:view
3
# 批量添加元素
>pfadd hll a b c d e f g
1
>pfcount hll
7

同步策略

  • 在业务低频的时候同步到数据库。

  • 对于实时性要求高的,后台可以直接读取redis的数据。

  • 在key加上日期,每次持久化只处理过去时间的、key的值不会再变化的,如此一来,可以保证临界点数据(读出redis数据,然后写入数据库,最后重置或删除key期间产生的新数据)不丢失。

想想埋点,会不会是更好地方案呢~

posted @ 2024-04-24 16:33  狂客  阅读(327)  评论(0编辑  收藏  举报