位图
在我们平时的开发过程中,会有-些bool型数据需要存取,比如用户年的签到记录,签了是l ,没签是0 ,要记录365 天。如果使用普通的key/value ,每个用户要记录365 个,当用户数上亿的时候,需要的存储空间是惊人的。 Redis提供了位图数据结构,这样每天的签到记录只占据一个位,365天就是365 个位,46 个字节(一个稍长一点的字符串)就可以完全容纳下,这就大大节约了存储空间。位图的最小单位是比特( bit ),每个bit 的取值只能是0 或1。在Java中也可以利用位图的特性进行&&与||运算,用较少的变量及存储空间来获取相应的数据。
Redis提供了位图统计指令bitcount和位图查找指令bitpos。bitcount 用来统计指定位置范围内l的个数,bitpos 用来查找指定范围内出现的第一个0或1。如果要一次操作多个位,就必须使用管道来处理。bitfield 有三个子指令,分别是get 、set 、incrby, 它们都可以对指定位片段进行读写,但是最多只能处理64个连续的位,如果超过64位,就得使用多个子指令,bitfield可以一次执行多个子指令。
如果统计PV,那非常好办,给每个网页配一个独立的Redis 计数器就可以了,把这个计数器的key 后缀加上当天的日期。这样来一个请求,执行incrby 指令一次,最终就可以统计出所有的PV 数据。但是UV 不一样,它要去重,同一个用户一天之内的多次访问请求只能计数一次。这就要求每一个网页请求都需要带上用户的ID ,无论是登录用户还是未登录用户都需要一个唯一ID来标识。你也许已经想到了一个简单的方案,那就是为每一个页面设置一个独立的set 集合来存储所有当天访问过此页面的用户囚。当一个请求过来时,我们使用sadd 将用户ID 塞进去就可以了。通过scard 可以取出这个集合的大小,这个数字就是这个页面的UV 数据。没锚,这是一个非常简单的可行方案。但是,如果你的页面访问量非常大,比如一个爆款页面可能有几千万个UV ,你就需要一个很大的set 集合来统计,这就非常浪费空间。如果这样的页面很多,那所需要的存储空间是惊人的。为这样一个去重功能就耗费这样多的存储空间,值得吗?
Redis提供的HyperLogLog 数据结构就是用来解决这种统计问题的。HyperLogLog 提供不精确的去重计数方案,虽然不精确,但是也不是非常离谱,标准误差是0.81%,HyperLogLog 提供了两个指令pfadd 和pfcount。