Redis作为计数器,统计PV、UV、IP的几种方案(集合、位图、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期间产生的新数据)不丢失。
想想埋点,会不会是更好地方案呢~