REDIS实践之在线人数统计几种方案分析

本文探讨了四种在线人数统计方案:有序集合、集合、HyperLogLog及Bitmap,对比它们的内存消耗与统计能力。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在线人数统计业务是我们开发web肯定要设计的业务逻辑,本文就会给出几种设计方案,来分析下各个方案的优缺点:

  • 使用有序集合
这种方案能够同时储存在线的用户 和 用户上线时间,能够执行非常多的聚合计算,但是所消耗的内存也是非常可观的。
  • 使用集合
这种方案能储存在线的用户,也能够执行一定的聚合计算,相对有序集合,所消耗的内存要小些,但是随着用户量的增多,消耗内存空间也处于增加状态
  • 使用hyperloglog
这种方案无论统计多少在线用户, 消耗的内存都是12k,但是只能给出在线用户的统计信息,无法获取准确的在线用户名单
  • 使用bitmap
这种方案还是比较好的,在尽可能节省内存空间情况下,记录在线用户的情况,而且能做一定的聚合运算

下面我们就用实际例子来说明:

我们先以每天会有10w~30w的小量用户, 100w的用户群来说明下面的几种方案

方案一:使用有序集合

先生成用户在线记录数据:

$start_time = mktime(0, 0, 0, 9, 5);    //monday
for ($i=0; $i < 6; $i++) {
    $day_start_time  = $start_time + 86400 * $i;    //every day begin time
    $day_end_time =  $day_start_time + 86400;       //every day end time
    $online_user_num = mt_rand(100000, 300000);     //online user between 100000 and 300000 

    for ($j=1; $j < $online_user_num; $j++) { 
        $user_id = mt_rand(1, 1000000);
        $redis->zadd('000|online_users_day_'.$i, mt_rand($day_start_time, $day_end_time), $user_id);
    }
}

好了记下来我们就来看看都能统计出哪些信息来吧

//note: 统计每天的在线总人数
for ($i=0; $i < 6; $i++) { 
    print_r($redis->zsize('000|online_users_day_'.$i). "\n");
}

//note: 统计最近6天都在线的人数
var_dump($redis->zInter('000|online_users_day_both_6', 
    [
    '000|online_users_day_0', 
    '000|online_users_day_1', 
    '000|online_users_day_2', 
    '000|online_users_day_3', 
    '000|online_users_day_4', 
    '000|online_users_day_5'
    ]
    ));

//note: 统计出近6天中共有多少上线
$redis->zunion('000|online_users_day_total_6', ['000|online_users_day_0', '000|online_users_day_1', '000|online_users_day_2', '000|online_users_day_3', '000|online_users_day_4', '000|online_users_day_5']);

//note: 统计某个时间段总共在线用户
print_r($redis->zcount('000|online_users_day_5', mktime(13, 0, 0, 9, 10), mktime(14, 0, 0, 9, 10)));

//note: 统计某个时间段在线用户名单
print_r($redis->zrangebyscore('000|online_users_day_5', mktime(13, 0, 0, 9, 10), mktime(14, 0, 0, 9, 10), 
    array('withscores' => TRUE)));

不单单只有这些, 我们还能统计出早, 中, 午, 晚 等等时间段的用户在线情况,还有很多其他的,这就让我们发挥想象吧,是不是挺多的? 只是确实也相当耗费内存空间

方案二:使用集合

还是先来成用户在线记录数据:

//note set 一般聚合
for ($i=0; $i < 6; $i++) {
    $online_user_num = mt_rand(100000, 300000);     //online user between 100000 and 300000 

    for ($j=1; $j < $online_user_num; $j++) { 
        $user_id = mt_rand(1, 1000000);
        $redis->sadd('001|online_users_day_'.$i, $user_id);
    }
}

好了记下来我们就来看看都能统计出哪些信息来吧

//note 判断某个用户是否在线
var_dump($redis->sIsMember('001|online_users_day_5', 100030));

//note 每天在线用户总量的统计
for ($i=0; $i < 6; $i++) { 
    print_r($redis->ssize('001|online_users_day_'.$i). "\n");
}

//note 对不同时间段的在线用户名单进行聚合
print_r($redis->sInterStore('001|online_users_day_both_4and5', '001|online_users_day_4', '001|online_users_day_5'). "\n");

//note 对指定的时间段的在线用户名单进行统计
print_r($redis->sUnionStore('001|online_users_day_total_4add5', '001|online_users_day_4', '001|online_users_day_5'). "\n");

//note 哪天上线哪天没上线
print_r($redis->sDiffStore('001|online_users_day_diff_4jian5', '001|online_users_day_4', '001|online_users_day_5'). "\n");

是不是也挺不错的,先不要着急, 我们接着往下看

方案三:使用hyperloglgo

先来成用户在线记录数据:

// note HyperLogLog 只需要知道在线总人数
for ($i=0; $i < 6; $i++) {
    $online_user_num = mt_rand(100000, 300000);     //online user between 100000 and 300000 
    var_dump($online_user_num);
    for ($j=1; $j < $online_user_num; $j++) { 
        $user_id = mt_rand(1, 1000000);
        $redis->pfadd('002|online_users_day_'.$i, [$user_id]);
    }
}

这种方案,我们来看看都能实现哪些业务呢

$count = 0;
for ($i=0; $i < 3; $i++) { 
    $count += $redis->pfcount('002|online_users_day_'.$i);
    print_r($redis->pfcount('002|online_users_day_'.$i). "\n");
}
var_dump($count);

//note  3 days total online num
var_dump($redis->pfmerge('002|online_users_day_both_3', ['002|online_users_day_0', '002|online_users_day_1', '002|online_users_day_2']));
var_dump($redis->pfcount('002|online_users_day_both_3'));

好少啊,是的, 这种方案仅仅只能统计出某个时间段在线人数的总量, 对在线用户的名单却无能为力,但是却挺节省内存的,对统计数据要求不多情况下 ,我们便可以考虑这种方案。

方案四:使用bitmap

笔者对这种方案其实挺喜欢的,消耗的内存空间不多, 统计的信息却挺多的,还是老步骤,先来生成数据:

//note bitmap 综合前面3个的优缺点
for ($i=0; $i < 6; $i++) {
    $online_user_num = mt_rand(100000, 300000);     //online user between 100000 and 300000 

    for ($j=1; $j < $online_user_num; $j++) { 
        $user_id = mt_rand(1, 1000000);
        $redis->setbit('003|online_users_day_'.$i, $user_id, 1);
    }
}

接下来我们看看能满足的统计信息吧

//note userid today whether online 
var_dump($userid = mt_rand(1, 1000000));
var_dump($redis->getbit('003|online_users_day_5', $userid));

//note how many user is online
var_dump($redis->bitcount('003|online_users_day_5'));

//note 6 days both online
var_dump($redis->bitop('AND', '003|online_users_day_both_6', '003|online_users_day_0', '003|online_users_day_1', '003|online_users_day_2', '003|online_users_day_3', '003|online_users_day_4', '003|online_users_day_5'));
var_dump($redis->bitcount('003|online_users_day_both_6'));

//note 6 days total online
var_dump($redis->bitop('OR', '003|online_users_day_total_6', '003|online_users_day_0', '003|online_users_day_1', '003|online_users_day_2', '003|online_users_day_3', '003|online_users_day_4', '003|online_users_day_5'));
var_dump($redis->bitcount('003|online_users_day_total_6'));

//note 6 days only one online
var_dump($redis->bitop('XOR', '003|online_users_day_only_one_6', '003|online_users_day_0', '003|online_users_day_1', '003|online_users_day_2', '003|online_users_day_3', '003|online_users_day_4', '003|online_users_day_5'));
var_dump($redis->bitcount('003|online_users_day_only_one_6'));

怎么样? 是不是集合能统计的 这家伙也能统计出来? 而且消耗的内容还少。

对于这几种方案其实各有各的好处, 根据业务统计信息 来取相应的方案来实施吧,这样内存利用也就更合理了

### 实现方案 为了实现基于 Java 和 Redis在线人数实时统计功能,可以采用多种方法来确保准确性与性能。以下是几种常见的解决方案: #### 方法一:使用简单的增减计数器 这种方法适用于小型应用,在用户登录时增加计数器,在登出时减少计数器。然而,这种方式容易出现误差,尤其是在未正常退出的情况下。 ```java // 增加在线人数 jedis.incr("online_users"); // 减少在线人数 jedis.decr("online_users"); ``` 此方式可能导致统计数据不准确[^2]。 #### 方法二:利用有序集合 (ZSET) 更可靠的方法是使用 Redis 的 ZSET 数据结构来跟踪活跃用户。每当用户活动时更新其最后活动时间戳,并定期清理超过一定时限的条目。 ```java long now = System.currentTimeMillis(); String userId = "user:" + user.getId(); // 添加或更新成员及其分数(即当前时间戳) jedis.zadd("active_users", now, userId); // 删除5分钟前的数据 jedis.zremrangeByScore("active_users", 0, now - 5 * 60 * 1000); ``` 通过 `zcount` 可以方便地查询特定时间段内的在线用户数量[^3]。 #### 方法三:运用位图 (BITMAP) 对于大规模应用场景,还可以考虑使用 Redis 提供的 BITMAP 功能。每位代表一个唯一标识符(如用户ID),设置该位置表示用户处于在线状态;反之则置零。 ```java // 设置某位为1,表示用户上线 jedis.setbit("users_online", userId.longValue(), true); // 获取指定范围内有多少个1,即在线用户总数 Long onlineCount = jedis.bitcount("users_online"); ``` 这种方法能够有效节省内存空间并支持高效的批量操作[^5]。 综上所述,推荐优先选用 **方法二** 或者 **方法三** 来构建更加稳定可靠的在线人数统计系统。这两种策略不仅能提供精确的结果,而且具有良好的扩展性和维护性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值