PHP实现统计在线人数的多种方法:表统计、Redis有序集合、Hyperloglog详解
这篇文章给大家带来了 PHP 的相关知识,主要介绍了实现统计在线人数的方法,包括利用表统计方式、用 redis 有序集合统计以及用 hyperloglog 做统计等。下面一起来看看吧,希望能对大家有所帮助。

一个业务系统网站每天的访问量是多少呢?每天的在线人数又是多少呢?这种业务在我们的开发过程中需要预留,它也在我们的设计范围之内哦!因为对于一个正在运营的网站来说,每天都要进行统计。
在线人数是如何进行统计的呢?这里存在着几种方案,并且代码采用的是 laravel 框架,这些方案可以在开发过程中作为参考。
1 用表统计方式
用数据表统计在线人数,这种方式只能用在并发量不大的情况下。
首先我们先新建表:user_login
编辑
user_login 表
模拟用户登录,不存在用户就存入表,存在的则更新登录信息
// 客户端唯一的识别码
$client_id 等于 session 函数获取到的 id 。
//用户是否已存在
DB 用于操作名为“user_login”的表,其中的数据被赋值给变量 $user 。
在其中的('token',$client_id)处
->first();
//不存在则插入数据
if (empty($user)) {
$data = [
“token”被映射为$client_id。
使用'username'来模拟用户,将其转换为'user_'加上$client_id
模拟用户 id 为通过 mt_rand(10000000, 99999999) 生成的数字。
'create_time' 被赋予了 'Y-m-d H:i:s' 格式的日期时间值;
'update_time' 被转换为 'Y-m-d H:i:s' 格式的日期时间
];
使用“DB::table('user_login')”这个表进行插入操作,插入的数据是$data。
} else {
// 存在则更新用户登录信息
DB 对 'user_login' 这个表进行操作。
在 'token' 这个位置,有 $client_id 。
->update([
'update_time' 被更新为 'Y-m-d H:i:s' 格式的日期时间
]);
}
这里需要定期清理没有任何操作的用户。如果用户在一个小时内没有任何操作,那么我们可以将其记为无效用户。
代码如下:
// 客户端唯一的识别码
$client_id = session()->getId();
//用户是否已存在
$user = DB::table('user_login')
->where('token', $client_id)
->first();
//不存在则插入数据
if (empty($user)) {
$data = [
'token' => $client_id,
'username' => 'user_' . $client_id, // 模拟用户
'uid' => mt_rand(10000000, 99999999), //模拟用户id
'create_time' => date('Y-m-d H:i:s'),
'update_time' => date('Y-m-d H:i:s')
];
DB::table('user_login')->insert($data);
} else {
// 存在则更新用户登录信息
DB::table('user_login')
->where('token', $client_id)
->update([
'update_time' => date('Y-m-d H:i:s')
]);
}
我们可以实现的功能:
1)当前在线人数
2)某时间段内在线人数
3)最新上线的用户
4)指定用户是否在线
// 可实现功能一:当前总共在线人数 通过 DB 操作,对表名为 'user_login' 的表进行计数操作,得到的结果赋值给变量 c,即 c 等于对 'user_login' 表进行计数后的数量。 当前在线人数为:$c 。
'; // 可实现功能二:某时间段内在线人数 $begin_date 的值为 '2020-08-13 09:00:00'; $end_date 的值为 '2020-08-13 18:00:00'; $c 等于 DB 操作的表名为 'user_login' 的数据;$c 是从名为 'user_login' 的表中获取的数据;$c 是对 'user_login' 这个表进行操作后得到的结果;$c 是与 'user_login' 表相关的数据;$c 是关于 'user_login' 表的内容。 where 的 'create_time' 大于等于 $begin_date 在 'create_time' 这个条件下,其值为<=', $end_date) ->count(); 输出开始日期 $begin_date 与结束日期 $end_date 之间的在线人数信息,即在线人数为 $c ,格式为开始日期 - 结束日期 在线人数:$c
'; // 可实现功能三:最新上线的用户 $newest 等于对表 'user_login' 进行操作的结果,即通过 DB::table 方法获取到的相关数据。 按照“create_time”字段进行排序,排序顺序为“DESC”。 ->limit(10) ->get(); echo '最新上线的用户有:'; foreach ($newest as $value) { 输出 $value 的 username 属性值并添加一个空格;输出一个空格;继续后续操作。 } echo '
'; // 可实现功能四:指定用户是否在线 $username 的值为 'user_1111'。 $online 等于从名为“user_login”的表中获取数据。 where 的条件为 'username' 且等于 $username ->exists(); 输出用户名以及判断用户是否在线,如果在线则输出“在线”,如果不在线则输出“不在线”。
2 使用 redis 有序集合实现在线人数统计
内存中使得效率很高,能够统计某时间段内的在线人数,也可以进行各种聚合操作。然而,当在线人数较多时,会占用较多内存。另外,还有:
无法通过用户操作时间来清除无效用户。只有手动登出的用户才会从集合中被删除。
代码如下:
// 客户端唯一的识别码 $client_id = session()->getId(); echo $client_id . '
'; // 按日期生成key $day = date('Ymd'); $key 等于 'online:' 与 $day 进行连接;$key 是由 'online:' 和 $day 组合而成的;$key 为 'online:' 加上 $day 所形成的。 // 是否在线 Redis 利用 zScore 方法获取键 $key 中客户端 ID 为 $client_id 的值并赋值给 $is_online 。 如果($is_online 为空),那么(不在线,加入当前客户端)。 Redis 执行了将当前时间和客户端 ID 添加到指定键的集合中的操作,即 Redis::zAdd($key, time(), $client_id)。 } // 可实现功能一:当前总共在线人数 $c = Redis::zCard($key); echo '当前在线人数:' . $c . '
'; // 可实现功能二:某时间段内在线人数 $begin_date = '2020-08-13 09:00:00'; $end_date = '2020-08-13 18:00:00'; 使用 Redis 的 zCount 方法,传入键名 $key 以及开始时间 strtotime($begin_date) 和结束时间 strtotime($end_date),将返回的结果赋值给变量 $c 。 echo $begin_date . '-' . $end_date . '在线人数:' . $c . '
'; // 可实现功能三:最新上线的用户,时间从小到大排序 $newest 等于 Redis 通过 zRangeByScore 方法获取的结果,获取的键为 $key,分数范围是从负无穷('-inf')到正无穷('+inf'),并且限制返回的数量为从索引 0 开始的 50 个。 echo '最新上线的用户有:'; foreach ($newest as $value) { 输出拼接后的结果。 } echo '
'; // 可实现功能四:指定用户是否在线 $username = $client_id; $online$的值为$Redis$的$zScore$方法获取的结果,该方法的参数为$key$和$client_id$。 输出用户名以及该用户是否在线的状态信息,若在线则显示“在线”,若不在线则显示“不在线”,最后以特定字符结尾。
'; // 可实现功能五:昨天和今天都上线的客户 昨天等于 Carbon 的昨天方法调用后再转换为日期字符串。 $yes_key 等于将字符串 $yestoday 中的 '-' 替换为空字符串。 $members = []; Redis 进行管道操作,在函数内部使用 $pipe ,同时使用 $key 、$yes_key 以及引用 $members 。具体如下: Redis 使用 zinterstore 方法创建了一个新的键 'new_key',它是通过对键 $key 和 $yes_key 进行操作得到的,并且设置了聚合方式为'min'。 $members 是通过 Redis::zRangeByScore 方法获取的,参数为 'new_key',分数范围是 '-inf' 到 '+inf',并且设置了限制条件 'limit' 为 [0, 50]。 //dump($members); }); echo '昨天和今天都上线的用户有:'; 对于每一个 $members 中的 $value ,进行如下操作 : echo $value . ' '; }
3 使用 hyperloglog 做统计
hyperloglog 与有序集合方式不同,它十分节约空间。不过,它实现的功能较为单一,仅仅能够统计在线人数,无法实现其他任何功能。
Redis HyperLogLog 是用于进行基数统计的算法。当输入元素的数量或者体积非常大时,HyperLogLog 的优点在于,计算基数所需的空间始终是固定的,而且这个空间很小。
Redis 中,每个 HyperLogLog 键仅需 12 KB 内存,便能计算接近 2^64 个不同元素的基数。而在计算基数时,元素越多耗费内存就越多的集合与之形成鲜明对比。
但是,HyperLogLog 仅会依据输入元素来计算基数,不会储存输入元素自身。因此,HyperLogLog 无法像集合那样,将输入的各个元素返回。
HyperLogLog 只需知晓在线的总人数。
for ($i=0; $i < 6; $i++) {
$online_user_num = mt_rand(10000000, 99999999); //模拟在线人数
var_dump($online_user_num);
for ($j=1; $j < $online_user_num; $j++) {
$user_id = mt_rand(1, 100000000);
$redis->向 '002|online_users_day_' 加上 $i 这个键,并将 $user_id 作为值添加到这个键对应的集合中。
}
}
$count = 0;
for ($i=0; $i < 3; $i++) {
$count += $redis->调用 pfcount 函数,传入参数为 '002|online_users_day_'.$i ;
输出 $redis->pfcount('002|online_users_day_'.$i) 的结果并换行
}
var_dump($count);
总共 3 天的在线数量
var_dump 的结果为合并后的结果。
使用 var_dump 函数来输出 Redis 中名为 '002|online_users_day_both_3' 的键的基数计数。
这种方案只能统计出某个时间段在线人数的总量,无法获取在线用户的名单,不过它挺节省内存的。在对统计数据要求不多的情况下,我们可以考虑采用这种方案。
4 使用 bitmap 统计
bitmap 是通过一个 bit 位来表示某个元素对应的价值或者状况的。其中的 key 就是对应的元素自身。我们知晓 8 个 bit 能够组成一个 Byte。所以 bitmap 自身能够极大地节省存储空间。
bitmap 常被用于统计在线用户。
代码如下
// 模拟当前用户
uid 等于通过 request 获取到的 'uid' ;uid 是由 request 所获取的 'uid' ;request 获取到的 'uid' 赋值给了 uid ;uid 是通过 request 获得的 'uid' ;获取到的 'uid' 通过 request 被赋值给了 uid 。
$key 为 'online_bitmap_' 与 date('Ymd') 进行连接后的内容;
// 设置当前用户在线
Redis::setBit($key, $uid, 1);
// 可实现功能1:在线人数
$c = Redis::bitCount($key);
输出“在线人数:”以及变量$c的值。
';
// 可实现功能2:指定用户是否在线
通过 Redis 获取指定键 $key 中用户标识 $uid 对应的位值,将其赋值给变量 $online 。
输出 $uid ,如果在线则显示“在线”,如果不在线则显示“不在线” ,后面还有其他内容(此处未给出完整信息)。
';
// 可实现功能3:昨天和今天均上线的用户总数
$yestoday = Carbon::yesterday()->toDateString();
$yes_key = str_replace('-', '', $yestoday);
$c = 0;
Redis 使用 pipeline 函数,在函数内部使用 $pipe 作为参数,同时使用 $key、$yes_key 和引用变量 $c。具体的操作是在 pipeline 中进行相关的处理。
Redis 执行位操作“AND”,目标键为“yest”,操作的键为$key 和$yes_key 。
使用 Redis 的 bitCount 方法来计算 'yest' 的比特位数量,将结果赋值给变量 c 。
});
昨天上线的用户数量有:$c ,今天上线的用户数量也有:$c 。昨天和今天都上线的用户数量为:$c 。
';
bitmap 消耗的内存空间相对较少,而统计的信息却较为丰富,这种方案是值得被推荐的。