用Redis bitmap统计活跃用户、留存

     Spool的开发者博客,描述了Spool利用Redis的bitmaps相关的操作,进行网站活跃用户统计工作。

      原文:http://blog.getspool.com/2011/11/29/fast-easy-realtime-metrics-using-redis-bitmaps/

  Redis支持对String类型的value进行基于二进制位的置位操作。通过将一个用户的id对应value上的一位,通过对活跃用户对应的位进行置位,就能够用一个value记录所有活跃用户的信息。如下图所未,下图中的bitmap有9个位被置为1,表示这9个位上对应的用户是今天的活跃用户。其中第15位表示uid为15的用户,第一位表示uid为0的用户。(如果你的uid不是从1开始的,比如从100000开始,实际上你也可以相应的用uid减去初始值来表示其位数,比如1000000用户对应到bitmap的第一位)

  NoSQLFan:用Redis bitmap统计活跃用户

  具体的代码类似下面这样:

redis.setbit(play:yyyy - mm - dd,  user_id 1 )

  这样一次记录的复杂度是O(1),在Redis中速度非常快。

  而我们通过每天换用一个不同的key来将每天的活跃用户状态记录分开存。并且可以通过一些与或运算计算出N天活跃用户,和连接N天活跃用户这样的统计数据。

  如下图,第一行表示星期一的活跃用户情况,第二行表示周二的,以此类推。为样我们通过对N天的活跃用户记录取并集操作,就能得出在N天内活跃过的用户列表。

  NoSQLFan:用Redis bitmap统计活跃用户

  下面表格表示对应一天,一周,一个月统计时所花费的时间。

  NoSQLFan:用Redis bitmap统计活跃用户

  下面是具体的java代码片断:

  算出一天的活跃用户数量

import  redis.clients.jedis.Jedis;
import  java.util.BitSet;
...
  Jedis redis 
=   new  Jedis( " localhost " );
...
  
public   int  uniqueCount(String action, String date) {
    String key 
=  action  +   " : "   +  date;
    BitSet users 
=  BitSet.valueOf(redis.get(key.getBytes()));
    
return  users.cardinality();
  }

  计算某几个内活跃用户的数量(某一天活跃就算,所以是取并集)

import  redis.clients.jedis.Jedis;
import  java.util.BitSet;
...
  Jedis redis 
=   new  Jedis( " localhost " );
...
  
public   int  uniqueCount(String action, String... dates) {
    BitSet all 
=   new  BitSet();
    
for  (String date : dates) {
      String key 
=  action  +   " : "   +  date;
      BitSet users 
=  BitSet.valueOf(redis.get(key.getBytes()));
      all.or(users);
    }
    
return  all.cardinality();
  }

  具体的用法还很多,比如你还可以对独特终端的用户单独记一个bitmap,这样就可以统计不同终端用户的活跃情况。有的同学会说用set也能实现同样的效果。但使用set在内存使用量上是会大很多的。

==========================================================================

      看完这篇文章后,我测试了一下:
redis> SETBIT bit 10086 1
(integer) 0
redis> GETBIT bit 10086
(integer) 1

对使用大的offset的 SETBIT 操作来说,第一次内存分配可能造成 Redis 服务器被阻塞.因为Redis需要生成很长的二进制系列。
问题:

如果活跃用户在百万级别,使用Redis BitMap很划算。

如果如果活跃用户很少,而用户id都是10位以上的int。那就很浪费内存了。那还不如使用set集合呢。然后求交集就可以了。

我们可以计算内存:offset = 999 999 999 =》需要的内存999 999 999/8/1024/1024 = 119M左右。

如果统计的数据还有很多维度,且维度组合有上千种,使用这个方式就不划算。我们可以借鉴bitmap使用另外的方式来统计活跃留存:

留存的指标:
    次日注册留存、
    2日注册留存...
    N日注册留存,
    比如昨天注册了1000名用户中,在今天有300名用户又登录了,那么对应于昨天的注册留存就是30%;
从总体上看,这些指标依赖于核心变量——用户访问时间。
那我们可以使用bitMap来记录用户访问时间:

如果我们统计时间是从2013年开始,那么2013-01-01就是bit的第一位...以此类推,
2013年的最后一天,即是bit位的第365位。

这样我们已经记录用户所有天的是否登录。
然后我们计算留存:
留存计算:
  1) 计算当天时间,对应对应的bit位,如今他是7月01日,bit位是182.
  2)次日留存:
     查看bit的(182-1)=181位是否存在,若存在,留存数+1
     N日留存: 
     查看bit的(182-n)位是否存在,若存在,n日留存数+1
我们再来估算占用空间。一年365bit位。1000万用户,占用的空间=1000万 * 365bit/8 /1024/1024 = 430M

### 使用 Redis Bitmap 实现用户连续签到功能 RedisBitmap 结构是一种高效的数据存储方式,通过将每一位(bit)作为独立单元来表示某种状态,可以用于实现用户的签到记录以及统计连续签到的功能。以下是关于如何使用 Redis Bitmap 来实现这一目标的具体说明。 #### 1. 数据结构设计 Bitmap 是基于 Redis 的 String 类型实现的,其中每个 bit 可以用来标记某一天的状态。例如,`0` 表示未签到,而 `1` 则表示已签到。假设我们为每位用户创建一个单独的 key,key 名字可以根据用户 ID 和日期范围组合而成,比如 `sign:<uid>` 或者更具体的 `sign:<uid>:<year>-<month>`[^1]。 对于某个特定用户 uid=10086,在其对应的 key 中按时间顺序设置每天的签到情况: ```bash SETBIT sign:10086 0 1 # 第一天签到 (从左至右第0位设为1) SETBIT sign:10086 1 1 # 第二天签到 (从左至右第1位设为1) SETBIT sign:10086 2 0 # 第三天未签到 (从左至右第2位保持为0) ``` #### 2. 统计总的签到次数 为了获取某一用户的历史总签到次数,可以通过 BITCOUNT 命令完成。该命令会返回指定 key 下所有被置为 `1` 的比特数量,即代表了用户累计签到的天数[^4]。 ```bash BITCOUNT sign:10086 # 返回用户UID=10086自注册以来的总签到天数 ``` #### 3. 计算最长连续签到天数 要找出一位用户的最长连续签到天数,则需借助外部逻辑配合 Redis 提供的基础操作共同达成目的。一种常见做法是从头遍历整个 Bitmap 并跟踪当前连贯序列长度与历史最高纪录对比更新直至结束为止。 伪代码如下所示: ```python def get_longest_streak(redis_key): streak, max_streak = 0, 0 for i in range(0, MAX_DAYS): # 遍历可能的最大天数 is_signed_in = int(redis_conn.getbit(redis_key, i)) if is_signed_in: streak += 1 if streak > max_streak: max_streak = streak else: streak = 0 return max_streak ``` 这里需要注意的是实际应用中应考虑性能优化措施,如分批读取数据而非逐个查询单个位置上的值等策略[^2]。 #### 4. 当前连续签到天数 如果想实时知道最近一次中断之前的持续签到数目,可以从最新的一端向前扫描直到遇到第一个零停止并记下经过了多少个一即可得到结果[^3]。 同样给出一段简单的 Python 脚本来演示此过程: ```python def current_signin_days(redis_key, today_index): days = 0 while True: signed_today = redis_conn.getbit(redis_key, today_index - days) if not signed_today or today_index-days < 0: break days += 1 return days ``` 以上方法均依赖于合理规划 keyspace 设计原则以便后续扩展维护方便的同时也兼顾效率问题。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hguisu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值