网站经常有这样的需求:统计日活用户数,有哪些实现方式呢?

第一种做法:用redis的set集合。
用户登录以后,把用户id添加到redis的set中,set会自动进行去重,类似于这样:

127.0.0.1:6379> sadd users_2019_06_17 user1
(integer) 1
127.0.0.1:6379> sadd users_2019_06_17 user2
(integer) 1
127.0.0.1:6379> sadd users_2019_06_17 user3
(integer) 1

很显然,只需要一条scard命令:

127.0.0.1:6379> scard users_2019_06_17
(integer) 3

可以看出来,2019年6月17号的用户数是3个。
很简单,但是集合只适用于用户数比较少的场合,假如用户有100万,set存储100万个id号,如果一个id号占32个字节,总共就是差不多32M,一个月就是960M 差不多一个G了!

第二种做法:用Bitmap。
我们存放100万个id号需要100万个bit位,也就是100万/8 = 125K字节,直接用以id号和100万取余,余数作为bit的索引:

127.0.0.1:6379> setbit login_2019_06_17  10000 1
(integer) 0
127.0.0.1:6379> setbit login_2019_06_17  1024 1
(integer) 0
127.0.0.1:6379> setbit login_2019_06_17  238 1
(integer) 0
127.0.0.1:6379> setbit login_2019_06_17  3434 1
(integer) 0

这时候同样,只要一条bitcount就能查出来用户数:

127.0.0.1:6379> bitcount login_2019_06_17
(integer) 4

此时存储100万个用户,只需要125K个字节,一个月才4M。
还有没有占存储空间更少的办法?

第三种办法:用redis的HyperLogLog

Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。
在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以 HyperLogLog 不能像集合那样,返回输入的各个元素。
原理很复杂就不说了,只说下用法:

127.0.0.1:6379> pfadd login.2019_06_17 user1
(integer) 1
127.0.0.1:6379>  pfadd login.2019_06_17 user2
(integer) 1
127.0.0.1:6379>  pfadd login.2019_06_17 user3
(integer) 1
127.0.0.1:6379>  pfadd login.2019_06_17 user4
(integer) 1
127.0.0.1:6379> pfcount login.2019_06_17
(integer) 4

此时存储100万个独立用户只需要15K左右,一个月才480K左右!
需要注意的是HyperLogLog的统计结果并不是一个精确的值,误差在0.81%左右,但是对于统计用户数这种场景来说足够了。