[精選] 統計線上人數,用php 如何來實現 ?

Weiwen發表於2022-06-07

​一個業務系統網站每天人數的訪問量是多少,線上人數是多少?這種業務我們在開發中就要預留,也是在我們的設計範圍內的咯!因為一個正在運營的網站,每天都會用到統計。

那線上人數是如何統計的呢,這裡有幾種方案,程式碼用laravel框架。可以作為開發中參考。

1 用表統計方式

用資料表統計線上人數,這種方式只能用在併發量不大的情況下。

首先我們先新建表:user_login

編輯

user_login表

模擬使用者登入,不存在使用者就存入表,存在的則更新登入資訊

// 客戶端唯一的識別碼
$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')
      ]);
}

這裡還需要定期清理無任何操作的使用者,假如使用者一個小時內無任何操作,我們可以記為無效使用者

程式碼如下:

// 客戶端唯一的識別碼
$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)指定使用者是否線上

// 可實現功能一:當前總共線上人數
$c = DB::table('user_login')->count();
echo '當前線上人數:' . $c . '<br />';

// 可實現功能二:某時間段內線上人數
$begin_date = '2020-08-13 09:00:00';
$end_date = '2020-08-13 18:00:00';

$c = DB::table('user_login')
    ->where('create_time', '>=', $begin_date)
    ->where('create_time', '<=', $end_date)
    ->count();
echo $begin_date . '-' . $end_date . '線上人數:' . $c . '<br />';

// 可實現功能三:最新上線的使用者
$newest = DB::table('user_login')
    ->orderBy('create_time', 'DESC')
    ->limit(10)
    ->get();
echo '最新上線的使用者有:';

foreach ($newest as $value) {
    echo $value->username . ' ';
}
echo '<br />';

// 可實現功能四:指定使用者是否線上
$username = 'user_1111';
$online = DB::table('user_login')
    ->where('username', $username)
    ->exists();
echo $username . ($online ? '線上' : '不線上');

2 使用redis有序集合實現線上人數統計

因為是記憶體中,所以效率很高,可以統計某個時間段內的線上人數,可以做各種聚合操作。但是如果線上人數比較多的情況下,會比較佔用記憶體。還有一點:

無法通過使用者操作時間清除掉無效使用者,只有手動登出的使用者才會從集合中刪除。

程式碼如下:

// 客戶端唯一的識別碼
$client_id = session()->getId();
echo $client_id . '<br />';

// 按日期生成key
$day = date('Ymd');
$key = 'online:' . $day;

// 是否線上
$is_online = Redis::zScore($key, $client_id);
if (empty($is_online)) {    // 不線上,加入當前客戶端
    Redis::zAdd($key, time(), $client_id);
}

// 可實現功能一:當前總共線上人數
$c = Redis::zCard($key);
echo '當前線上人數:' . $c . '<br />';

// 可實現功能二:某時間段內線上人數
$begin_date = '2020-08-13 09:00:00';
$end_date = '2020-08-13 18:00:00';
$c = Redis::zCount($key, strtotime($begin_date), strtotime($end_date));
echo $begin_date . '-' . $end_date . '線上人數:' . $c . '<br />';

// 可實現功能三:最新上線的使用者,時間從小到大排序
$newest = Redis::zRangeByScore($key, '-inf', '+inf', ['limit' => [0, 50]]);
echo '最新上線的使用者有:';
foreach ($newest as $value) {
    echo $value . ' ';
}
echo '<br />';

// 可實現功能四:指定使用者是否線上
$username = $client_id;
$online = Redis::zScore($key, $client_id);;
echo $username . ($online ? '線上' : '不線上') . '<br />';

// 可實現功能五:昨天和今天都上線的客戶
$yestoday = Carbon::yesterday()->toDateString();
$yes_key = str_replace('-', '', $yestoday);
$members = [];
Redis::pipeline(function ($pipe) use ($key, $yes_key, &$members) {
    Redis::zinterstore('new_key', [$key, $yes_key], ['aggregate' => 'min']);
    $members = Redis::zRangeByScore('new_key', '-inf', '+inf', ['limit' => [0, 50]]);
    //dump($members);
});

echo '昨天和今天都上線的使用者有:';
foreach ($members as $value) {
    echo $value . ' ';
}

3 使用hyperloglog做統計

跟有序集合方式不同,hyperloglog十分節約空間,但是實現的功能也非常單一,只能統計線上人數,不能實現其餘的任何功能。

Redis HyperLogLog 是用來做基數統計的演算法,HyperLogLog 的優點是,在輸入元素的數量或者體積非常非常大時,計算基數所需的空間總是固定 的、並且是很小的。

在 Redis 裡面,每個 HyperLogLog 鍵只需要花費 12 KB 記憶體,就可以計算接近 2^64 個不同元素的基 數。這和計算基數時,元素越多耗費記憶體就越多的集合形成鮮明對比。

但是,因為 HyperLogLog 只會根據輸入元素來計算基數,而不會儲存輸入元素本身,所以 HyperLogLog 不能像集合那樣,返回輸入的各個元素。

// note 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->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'));

這種方案僅僅只能統計出某個時間段線上人數的總量, 對線上使用者的名單卻無能為力,但是卻挺節省記憶體的,對統計資料要求不多情況下 ,我們便可以考慮這種方案。

4 使用bitmap統計

bitmap就是通過一個bit位來表示某個元素對應的值或者狀態,其中的key就是對應元素本身。我們知道8個bit可以組成一個Byte,所以bitmap本身會極大的節省儲存空間。

bitmap常用來做比如使用者簽到、活躍使用者、線上使用者等功能。

程式碼如下

// 模擬當前使用者
$uid = request('uid');

$key = 'online_bitmap_' . date('Ymd');

// 設定當前使用者線上
Redis::setBit($key, $uid, 1);

// 可實現功能1:線上人數
$c = Redis::bitCount($key);
echo '線上人數:' . $c . '<br />';

// 可實現功能2:指定使用者是否線上
$online = Redis::getBit($key, $uid);
echo $uid . ($online ? '線上' : '不線上') . '<br />';

// 可實現功能3:昨天和今天均上線的使用者總數
$yestoday = Carbon::yesterday()->toDateString();
$yes_key = str_replace('-', '', $yestoday);
$c = 0;
Redis::pipeline(function ($pipe) use ($key, $yes_key, &$c) {
    Redis::bitOp('AND', 'yest', $key, $yes_key);
    $c = Redis::bitCount('yest');
});
echo '昨天和今天都上線的使用者數量有:' . $c . '<br />';

bitmap消耗的記憶體空間不多, 統計的資訊卻挺多的,這種方案是值得推薦一下的。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章