關於統計在線用戶的功能。以前也做過,用的一些比較簡單的方法,但是缺點也很明顯:精確統計和服務器、數據庫壓力之間要做出平衡。
所以想找一個既能精確統計又能不占用太多服務器資源的方法。先說說一些平常的做法:
一,每次用戶操作更新其在線時間
這個方法很直接,在用戶表里加一個字段update_time,每次用戶進行操作,都更新這個字段為當前時間,一般是在一個被所有Action繼承的基類里寫這個操作。
然后定義一個過期時間,比如10分鍾,表示10分鍾沒進行任何操作的用戶默認為不在線。這樣,統計當前在線用戶的sql語句大概是這樣
select count(*) from think_user where update_time>now()-10*60
優點:實現簡單,通俗易懂
缺點:1,對“在線”的定義模糊,萬一用戶看一篇文章時間比較長,10分鍾內沒進行任何操作,他就被忽略了;
2,如果user表數據量很大,那效率將極差.
二,將在線用戶單獨放入一張表
對於方法一的改進。新建一張表think_inline,字段有user_id、update_time,每次用戶操作時,先判斷表里有沒有該用戶的記錄,沒有則新增,有就更新update_time。
並同時加上刪除失效數據操作
這樣,統計在線就可以直接count這張表就行了。而且這表的數據量不會很大(至少要比用戶表小的多)
delete from think_inline where update_time
優點:減少數據庫壓力
缺點:仍然對“在線”的定義不准確
三,用JS定時器
這個方法是綜合了一和二。新建一張表think_inline,也是在基類中定義每次用戶操作時更新時間,參考二的做法。
不同之處是,在每個html模板里,加上一個js定時器,setInterval('updateTime', 10*3600);每隔10分鍾發送一次ajax請求,更新update_time字段。這樣,即使用戶在一個頁面停留時間過長,也不會被誤認為不在線了。並且可以通過減少請求的間隔,來增加精確度,當然了,對服務器的壓力就更大了。
優點:對在線的判斷較為准確
缺點:仍然不能既精確又不增加服務器壓力,必須在兩者之間進行取舍。
四,使用TP的SessionDb驅動進行最優化設計
這也就是網上有人說的session存入數據庫的方法,這種方法優點很多。具體做法是。。。
1,為什么要將session存入數據庫?
session是存儲在服務器的一組臨時數據。一般情況下,我們在做用戶登錄時,會將用戶數據存入session。這樣,在任何頁面都可以方便調用,而且 每個客戶端會產生唯一的session_id,不會混餚。並且在關閉瀏覽器后,服務器會有session回收機制,自動刪除過期session。
這是session的優點:唯一性、方便調用、不會過多占用資源。但是也有缺點:在客戶端是以cookie方式保存的,禁用cookie就沒用了。
那么,服務器是如何存放session的呢?他是默認將session以文件的方式保存在硬盤上的。可是,對於我們碼農來說,操作數據庫要比讀文件方便的多,並且可以對session數據進行各種操作。
而統計在線用戶人數就是通過統計有多少條session記錄來實現的。
2,如何把session存入數據庫?
TP的SessionDb驅動就實現了這個功能。原理就是通過改寫PHP默認的session操作來實現,核心函數session_set_save_handler(),有興趣的可以研究一下。該驅動將session的增、讀、取、和刪都放入了數據庫。
使用方法也很簡單:
1,建表,驅動的注釋里的sql語句運行下就好
2,添加配置:
//Session配置 'SESSION_TYPE' => 'db', //數據庫存儲session 'SESSION_TABLE' => 'think_session', //存session的表 'SESSION_EXPIRE' => 600, //session過期
這樣,只要我們在程序里使用了session()函數,數據庫里就會有記錄。
3,利用數據庫session實現統計在線用戶
1,統計在線總人數
$map = array('session_expire'=>array('gt',NOW_TIME)); $inline = D('Session')->where($map)->count();
2,統計游客(未登錄)人數
$map = array('session_expire'=>array('gt',NOW_TIME),'session_data'=>array('eq','')); $huiyuan = D('Session')->where($map)->count();
3,統計會員(已登錄)人數
$map = array('session_expire'=>array('gt',NOW_TIME),'session_data'=>array('neq','')); $huiyuan = D('Session')->where($map)->count();
4,判斷一個用戶是否在線。
在用戶表里新增一個字段:session_id。
(1)在登錄操作里,保存該用戶的session_id,
$session_id = session_id(); D('User')->where(array('id'=>$user_id))->save('session_id'=>$session_id);
(2)檢查session表里是否存在該session_id,未過期並且有值,
$map = array('session_id'=>$session_id,'session_expire'=>array('gt',NOW_TIME),'session_data'=>array('neq','')); $res = D('Session')->where($map)->find();
4,總結
1,實現步驟:用戶表新增字段保存session_id;使用TP的SessionDb驅動。是不是很簡單?
2,優點:
(1)比上面三種方法對數據庫和服務器的壓力都小,操作簡單
(2)能夠區分在線用戶是會員還是游客(session_data字段是否有值),discuz就是這樣做的
(3)可以通過刪除某用戶的session記錄,實現將其“踢下線”的功能
3,缺點:
(1)仍然不能精確統計,只能說,XX秒內在線多少人
4,注意事項:
(1)由於該方法的SessionDb驅動必須使用session()函數才能觸發,所以必須配置自動開啟session(默認就是開啟)。TP在執行流程里會使用session()函數,所以你什么都不寫,session數據也會存入數據庫。
(2)因為數據庫的session數據是不會自己刪除的,所以對於過期的數據,必須調用session()方法才會刪。
這也就意味着,如果你的網站一個人都沒有訪問,那么數據庫的過期session記錄會一直存在。
也就是由於這種機制,對於一些突發事件(用戶直接X瀏覽器、直接關機、發生地震……),在其關閉瀏覽器的一段時間(session過期時間)后,其他用戶對網站的訪問,會觸發session回收,刪除過期記錄。所以,就不怕統計出來的數據不准確了。
當然了,就算沒人訪問,你也可以在count時加上session_expire>time()來統計。
但是也有誤差,就是session過期時間,5分鍾后過期,就有5分鍾的誤差。時間設的越小,對數據庫的操作就越頻繁;越大,精確度就越低。
(3)這個session過期時間就意味着,如果用戶5分鍾內不進行任何操作,其就會自動退出登錄。所以,為了用戶體驗好,請加上cookie自動登錄功能。目前官網就是這么做的。
(4)評論里有人提到,驗證碼也會存入session,所以我們判斷的時候,就不能值統計有值的記錄了。
需要先獲取有值的數據,再判斷里面有沒有保存用戶信息的參數名。雖然session_data字段是用二進制存儲的,但是查詢出來就是一個字符串。
比如,我們用的user下標來保存的用戶信息,
session('user',$data);//用戶登錄信息 //獲取真實會員數 //查詢有值的session記錄
$list = D('Session')->where(array('session_data'=>array('NEQ',''),'session_expire'=>array('lt',NOW_TIME)))->select(); //判斷值里是否有會員標識
foreach($list as $k=>$value){ if(strpos($value['session_data'],'user') === false){ $count++; } dump($count);//真實會員人數 }
(5)由於每種瀏覽器都有各自的session機制,所以,如果一個人在一台電腦上同時開了5種瀏覽器,則數據庫會保存5條不同的記錄
實際使用時,仍需要考慮搜索引擎的誤差。在其抓取我們的頁面時,也會產生session。
五,最終結論
由於HTTP協議的限制:請求完成后就會斷開與客戶端的連接。所以實際上,
我們根本無法精確而實時地統計在線人數
!盡管有各種各樣的方法來增加統計的精確度,然而都是治標不治本。唯有放棄HTTP協議,使用“長連接”的鏈接方式,才能精確判斷用戶在還是離 。
原文鏈接:http://www.it0662.com/jishutantao/52.html