1 頁 (共 1 頁)

SESSION MySQL

發表於 : 2009-12-07 08:58:42
yehlu
http://bbs.ecstart.com/thread-13976-1-1.html

來源:
http://www.chinahtml.com/program ... 1398215593520.shtml


一、問題起源

稍大一些的網站,通常都會有好幾個服務器,每個服務器運行著不同功能的模塊,使用不同的二級域名,而一個整體性強的網站,用戶系統是統一的,即一套用戶名、密碼在整個網站的各個模塊中都是可以登錄使用的。各個服務器共享用戶數據是比較容易實現的,只需要在後端放個數據庫服務器,各個服務器通過統一接口對用戶數據進行訪問即可。但還存在一個問題,就是用戶在這個服務器登錄之後,進入另一個服務器的別的模塊時,仍然需要重新登錄,這就是一次登錄,全部通行的問題,映射到技術上,其實就是各個服務器之間如何實現共享SESSION數據的問題。

二、PHP SESSION的工作原理

在解決問題之前,先來了解一下PHP SESSION的工作原理。在客戶端(如瀏覽器)登錄網站時,被訪問的PHP頁面可以使用 session_start()打開SESSION,這樣就會產生客戶端的唯一標識SESSION ID(此ID可通過函數 session_id()獲取/設置)。 SESSION ID可以通過兩種方式保留在客戶端,使得請求不同的頁面時,PHP程序可以獲知客戶端的 SESSION ID;一種是將SESSION ID自動加入到GET的URL中,或者POST的表單中,默認情況下,變量名為 PHPSESSID;另一種是通過COOKIE,將SESSION ID保存在COOKIE中,默認情況下,這個COOKIE的名字為 PHPSESSID。這裡我們主要以COOKIE方式進行說明,因為應用比較廣泛。
那麼SESSION 的數據保存在哪裡呢?當然是在服務器端,但不是保存在內存中,而是保存在文件或數據庫中。默認情況下,php.ini中設置的SESSION 保存方式是files(session.save_handler = files),即使用讀寫文件的方式保存SESSION數據,而 SESSION文件保存的目錄由session.save_path指定,文件名以sess_為前綴,後跟SESSION ID,如:sess_c72665af28a8b14c0fe11afe3b59b51b。文件中的數據即是序列化之後的SESSION 數據了。如果訪問量大,可能產生的SESSION文件會比較多,這時可以設置分級目錄進行SESSION 文件的保存,效率會提高很多,設置方法為:session.save_path="N;/save_path",N為分級的級數,save_path 為開始目錄。當寫入SESSION數據的時候,PHP會獲取到客戶端的SESSION_ID,然後根據這個SESSION ID到指定的 SESSION文件保存目錄中找到相應的SESSION文件,不存在則創建之,最後將數據序列化之後寫入文件。讀取SESSION 數據是也是類似的操作流程,對讀出來的數據需要進行解序列化,生成相應的SESSION變量。

三、多服務器共享SESSION的主要障礙及解決辦法

通過了解SESSION的工作原理,我們可以發現,在默認情況下,各個服務器會各自分別對同
一個客戶端產生SESSION ID,如對於同一個用戶瀏覽器,A服務器產生的SESSION ID是 30de1e9de3192ba6ce2992d27a1b6a0a,而B服務器生成的則是 c72665af28a8b14c0fe11afe3b59b51b。另外,PHP的SESSION數據都是分別保存在
本服務器的文件系統中。如下圖所示:

確定了問題所在之後,就可以著手進行解決了。想要共享SESSION數據,那就必須實現兩個目標:一個是各個服務器對同一個客戶端產生的 SESSION ID必須相同,並且可通過同一個COOKIE進行傳遞,也就是說各個服務器必須可以讀取同一個名為PHPSESSID的 COOKIE;
另一個是SESSION數據的存儲方式/位置必須保證各個服務器都能夠訪問到。簡單地說就是多
服務器共享客戶端的SESSION ID,同時還必須共享服務器端的SESSION數據。

第一個目標的實現其實很簡單,只需要對COOKIE的域(domain)進行特殊地設置即可,
默認情況下,COOKIE的域是當前服務器的域名/IP地址,而域不同的話,各個服務器所設
置的COOKIE是不能相互訪問的,如
四、代碼實現

首先創建數據表,MySQL的SQL語句如下:

CREATE TABLE `sess` (
`sesskey` varchar(32) NOT NULL default '',
`expiry` bigint(20) NOT NULL default '0',
`data` longtext NOT NULL,
PRIMARY KEY (`sesskey`),
KEY `expiry` (`expiry`)
) TYPE=MyISAM

sesskey為SESSION ID,expiry為SESSION過期時間,data用於保存SESSION數據。

默認情況下SESSION數據是以文件方式保存,想要使用數據庫方式保存,就必須重新定義SESSION各個操作的處理函數。 PHP提供了session_set_save_handle() 函數,可以用此函數自定義SESSION的處理過程,當然首先要先將session.save_handler改成user,可在PHP中進行設置:

session_module_name('user');


接下來著重講一下session_set_save_handle()函數,
此函數有六個參數:

session_set_save_handler ( string open, string close,
string read, string write, string destroy, string gc )

各個參數為各項操作的函數名,這些操作依次是:
打開、關閉、讀取、寫入、銷毀、垃圾回收。 PHP手冊中有詳細的例子,
在這裡我們使用OO的方式來實現這些操作,詳細代碼如下:

<?php
define('MY_SESS_TIME', 3600); //SESSION生存時長
//類定義
class My_Sess
{
function init()
{
$domain = '.infor96.com';
//不使用GET/POST變量方式
ini_set('session.use_trans_sid', 0);
//設置垃圾回收最大生存時間
ini_set('session.gc_maxlifetime', MY_SESS_TIME);

//使用COOKIE保存SESSION ID的方式
ini_set('session.use_cookies', 1);
ini_set('session.cookie_path', '/');
//多主機共享保存SESSION ID的COOKIE
ini_set('session.cookie_domain', $domain);

//將session.save_handler設置為user,
//而不是默認的files
session_module_name('user');
//定義SESSION各項操作所對應的方法名:
session_set_save_handler(
array('My_Sess', 'open'),
//對應於靜態方法My_Sess::open(),下同。
array('My_Sess', 'close'),
array('My_Sess', 'read'),
array('My_Sess', 'write'),
array('My_Sess', 'destroy'),
array('My_Sess', 'gc')
);
} //end function

function open($save_path, $session_name) {
return true;
} //end function

function close() {
global $MY_SESS_CONN;

if ($MY_SESS_CONN) { //關閉數據庫連接
$MY_SESS_CONN->Close();
}
return true;
} //end function

function read($sesskey) {
global $MY_SESS_CONN;

$sql = 'SELECT data FROM sess WHERE sesskey='
. $MY_SESS_CONN->qstr($sesskey) . ' AND expiry>=' . time();
$rs =& $MY_SESS_CONN->Execute($sql);
if ($rs) {
if ($rs->EOF) {
return '';
} else { //讀取到對應於SESSION ID的SESSION數據
$v = $rs->fields[0];
$rs->Close();
return $v;
} //end if
} //end if
return '';
} //end function

function write($sesskey, $data) {
global $MY_SESS_CONN;

$qkey = $MY_SESS_CONN->qstr($sesskey);
$expiry = time() + My_SESS_TIME; //設置過期時間

//寫入SESSION
$arr = array(
'sesskey' => $qkey,
'expiry' => $expiry,
'data' => $data);
$MY_SESS_CONN->Replace('sess', $arr, 'sesskey', $autoQuote = true);
return true;
} //end function

function destroy($sesskey) {
global $MY_SESS_CONN;

$sql = 'DELETE FROM sess WHERE sesskey=' . $MY_SESS_CONN->qstr($sesskey);
$rs =& $MY_SESS_CONN->Execute($sql);
return true;
} //end function

function gc($maxlifetime = null) {
global $MY_SESS_CONN;

$sql = 'DELETE FROM sess WHERE expiry<' . time();
$MY_SESS_CONN->Execute($sql);
//由於經常性的對錶sess做刪除操作,容易產生碎片,
//所以在垃圾回收中對該表進行優化操作。
$sql = 'OPTIMIZE TABLE sess';
$MY_SESS_CONN->Execute($sql);
return true;
} //end function
} ///:~

//使用ADOdb作為數據庫抽象層。
require_once('adodb/adodb.inc.php');
//數據庫配置項,可放入配置文件中(如:config.inc.php)。
$db_type = 'mysql';
$db_host = '192.168.212.1';
$db_user = 'sess_user';
$db_pass = 'sess_pass';
$db_name = 'sess_db';
//創建數據庫連接,這是一個全局變量。
$GLOBALS['MY_SESS_CONN'] =& ADONewConnection($db_type);
$GLOBALS['MY_SESS_CONN']->Connect( $db_host, $db_user, $db_pass, $db_name);
//初始化SESSION設置,必須在session_start()之前運行! !
My_Sess::init();
?>

Re: SESSION MySQL

發表於 : 2009-12-07 09:01:18
yehlu
CREATE TABLE IF NOT EXISTS `Sessions` (
`ses_id` varchar(32) NOT NULL,
`ses_time` int(11) NOT NULL default '0',
`ses_value` mediumtext NOT NULL,
PRIMARY KEY (`ses_id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

<?php
$MYSQL_HOST = '1.1.1.1';
$MYSQL_USER = 'root';
$MYSQL_PASS = '';
$MYSQL_DATA = 'sess';

@mysql_connect($MYSQL_HOST,
$MYSQL_USER,
$MYSQL_PASS) OR die("Error: ".mysql_error());
mysql_select_db($MYSQL_DATA) OR die("Error: ".mysql_error());

// Lifetime auf eine Stunde setzen
ini_set('session.gc_maxlifetime', 3600);
// gc mit einer Wahrscheinlichkeit von 1% aufrufen
ini_set('session.gc_probability', 1);
ini_set('session.gc_divisor', 100);

function ses_open($path, $name) {
return TRUE;
}

function ses_close() {
return TRUE;
}

function ses_read($ses_id) {
$sql = "SELECT
ses_value
FROM
Sessions
WHERE
ses_id = '".mysql_real_escape_string($ses_id)."'
";
$result = @mysql_query($sql);
// Fehler im Query, return leeren String
if (!$result)
return '';
// Session nicht gefunden, return leeren String
if (!mysql_num_rows($result))
return '';

// Session gefunden, return Daten der Session
$row = mysql_fetch_assoc($result);
return $row["ses_value"];
}

function ses_write($ses_id, $data) {

$sql = "REPLACE INTO
Sessions
(ses_id,
ses_time,
ses_value)
VALUES ('".mysql_real_escape_string($ses_id)."',
'".time()."',
'".mysql_real_escape_string($data)."')
";
return (bool)@mysql_query($sql);
}

function ses_destroy($ses_id) {
$sql = "DELETE FROM
Sessions
WHERE
ses_id = '".mysql_real_escape_string($ses_id)."'
";
return (bool)@mysql_query($sql);
}

function ses_gc($life) {
$ses_life = time()-$life;

$sql = "DELETE FROM
Sessions
WHERE
ses_time < ".$ses_life."
";
return (bool)@mysql_query($sql);
}

session_set_save_handler ('ses_open',
'ses_close',
'ses_read',
'ses_write',
'ses_destroy',
'ses_gc');
session_start();
$_SESSION["Counter"]++;
?>

當檔案太多時, 可以考慮切目錄來放那些 session file, 只要修改設定檔, 它就會自動建立目錄, 將檔案放進去

發表於 : 2009-12-07 09:02:44
yehlu
http://plog.longwin.com.tw/my_note-unix ... e-set-2008

PHP session 暫存檔過多的注意事項

註: 若您已經將 session 存入 MySQL 或 存入 memcache, 此文對您就沒有幫助囉~ :)

PHP 預設 session 是會存成 file, 預設檔名是 "sess_一串亂碼", FreeBSD 是存在 "/tmp" 或 "/var/tmp", Debian 會放在 "/var/lib/php5". (以下都以 Debian 為例)
PHP session 設定參數(php.ini)

* PHP session 的設定參數: PHP: Runtime Configuration - Manual

session 暫存檔過多可能產生的系統狀況

PHP session 暫存檔過多, 可能會造成下述的狀況:

* 作業系統變的很慢
* ls /var/lib/php5 (或 ls /tmp) 很慢 (檔案過多)
* Apache 回應很慢
* 前端頁面看到一片空白 (作業系統硬的碟滿了也會這樣)

session 暫存檔檢驗

照理說, 除非網站的量太大, 不然一般的量, sess_* 的檔案應該會自動回收清除(Debian 可見 "/etc/cron.d/php5" 檔, 此檔會去抓過期時間設定, 定時清除)

若沒有特別修改 php.ini, 通常 session 過期時間是 1440 秒(24分鐘), 所以要知道有沒有自動清除, 只要如下:

1. ls -lt /var/lib/php5
2. 看最後一個檔案的日期, 離現在時間有沒有超過 24 分鐘. (不過 PHP cron 清除時間是半小時跑一次, 所以可能要多觀察幾分鐘(Debian 是每小時的 09, 39 分跑.))
3. 有超過就代表沒有執行自動清除動作, 沒超過 24 分鐘, 但檔案還是太多, 下面再說解法.

sess_* 檔案時間存活超過設定時間

這代表沒有執行自動清除的動作, 或者是過期時間有另外設定(暫時不考慮程式另外設定過期時間), 這時後可以去 php.ini 找

; After this number of seconds, stored data will be seen as 'garbage' and
; cleaned up by the garbage collection process.
session.gc_maxlifetime = 1440

; NOTE: If you are using the subdirectory option for storing session files
; (see session.save_path above), then garbage collection does *not*
; happen automatically. You will need to do your own garbage
; collection through a shell script, cron entry, or some other method.
; For example, the following script would is the equivalent of
; setting session.gc_maxlifetime to 1440 (1440 seconds = 24 minutes):
; cd /path/to/sessions; find -cmin +24 | xargs rm

這邊有個注意事項, 他有寫說, 如果你有去修改預設的 session.save_path 的話, 系統將不會自動幫你清除過期的暫存檔, 需要自己手動管理回收暫存當的動作.

若有修改 session.save_path 的話, 需要手動管理, 可以試試下面兩種方法:

* 官方說明檔建議: cd /path/to/sessions; find -cmin +24 | xargs rm # 把這行寫入 cron, 定期清除
* 另外一種, 可以試著去修改 /etc/cron.d/php5 (不過這個我沒改過, 理論上應該修改就能動才對. XD)

sess_* 檔案時間有定時清除, 但檔案還是過多

這代表你的網站量應該還不小, 或者就是 session.gc_maxlifetime 時間設太長, 最好的解法當然是把 session 寫到 MySQL 或 memcache, 或寫 Cookie 也是好解法(記得加密)~

在此先不考慮 MySQL 和 memcache, 用最快的方法解決有下面幾種解法:

1. 若是每個 User 進來都會自動產生 session file 的話, 可以考慮將這行加入 cron (時間看要 3 ~ 5分鐘跑一次)

find /var/lib/php5 -cmin +5 -name "sess_*" -and -size 0 -delete > /dev/null 2>&1 # 清除 超過 5分鐘, 且容量為 0 的檔案

2. 另一個是修改 php.ini, 預設 session.save_path = /var/lib/php5

當檔案太多時, 可以考慮切目錄來放那些 session file, 只要修改設定檔, 它就會自動建立目錄, 將檔案放進去囉~

官方說明: (PHP: Runtime Configuration - Manual)

session.save_path = "N;MODE;/path"

There is an optional N argument to this directive that determines the number of directory levels your session files will be spread around in. For example, setting to '5;/tmp' may end up creating a session file and location like /tmp/4/b/1/e/3/sess_4b1e384ad74619bd212e236e52a5a174If.

依官方說明設定, 設定範例如下:
* 設定 session.save_path = '5;/tmp'
* session file 就會存成 /tmp/4/b/1/e/3/sess_4b1e384ad74619bd212e236e52a5a174If (切五層的目錄來存放)
* 這樣子就可以暫時先解決檔案過多造成的系統問題囉~

Re: SESSION MySQL

發表於 : 2009-12-13 21:49:48
yehlu
http://www.codecharge.com.tw/phpBB3/pos ... f=8&t=1047


php session management class with mySQL

PHP Code
+ 0 likes

Like it
Please Register to submit score.
Bookmark and Share
Average Score 6.0 (of 1 scores)
Date Added Aug 10, 2006
Last Updated Aug 10, 2006
Tags class management mysql php session with
Introduction
A class to handle sessions by using a mySQL database for session related data storage providing better security then the default session handler used by PHP. You don't need to modify a thing in your application - after instantianting the class just use sessions as you would normally. for the documentation go to <a href="http://stefangabos.blogspot.com/2006/08 ... er.html</a>


Grab the Code

<?php

/**
* A class to handle sessions by using a mySQL database for session related data storage providing better
* security then the default session handler used by PHP.
*
* To prevent session hijacking, don't forget to use the {@link regenerate_id} method whenever you do a
* privilege change in your application
*
* <i>Before usage, make sure you use the session_data.sql file from the install_sql folder to set up the table
* used by the class</i>
*
* After instantiating the class, use sessions as you would normally
*
* This class is an adaptation of John Herren's code from the "Trick out your session handler" article
* ({@link http://devzone.zend.com/node/view/id/141}) and Chris Shiflett's code from Chapter 8, Shared Hosting - Pg 78-80,
* of his book - "Essential PHP Security" ({@link http://phpsecurity.org/code/ch08-2})
*
* <i>Note that the class assumes that there is an active connection to a mySQL database and it does not attempt to create
* one. This is due to the fact that, usually, there is a config file that holds the database connection related
* information and another class, or function that handles database connection. If this is not how you do it, you can
* easily adapt the code by putting the database connection related code in the "open" method of the class.</i>
*
* The code is approx 9Kb in size but still heavily documented so you can easily understand every aspect of it
*
* This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivs 2.5 License.
* To view a copy of this license, visit {@link http://creativecommons.org/licenses/by-nc-nd/2.5/} or send a letter to
* Creative Commons, 543 Howard Street, 5th Floor, San Francisco, California, 94105, USA.
*
* For more resources visit {@link http://stefangabos.blogspot.com}
*
* @author Stefan Gabos <ix@nivelzero.ro>
* @version 1.0 (last revision: August 05, 2006)
* @copyright (c) 2006 Stefan Gabos
* @package dbSession
*/

error_reporting(E_ALL);

class dbSession
{

/**
* Constructor of class
*
* @return void
*/
function dbSession()
{

// get session lifetime
$this->sessionLifetime = get_cfg_var("session.gc_maxlifetime");
// register the new handler

session_set_save_handler(
array(&$this, 'open'),
array(&$this, 'close'),
array(&$this, 'read'),
array(&$this, 'write'),
array(&$this, 'destroy'),
array(&$this, 'gc')
);
register_shutdown_function('session_write_close');

// start the session
session_start();

}

/**
* Regenerates the session id.
*
* <b>Call this method whenever you do a privilege change!</b>
*
* @return void
*/
function regenerate_id()
{

// saves the old session's id
$oldSessionID = session_id();

// regenerates the id
// this function will create a new session, with a new id and containing the data from the old session
// but will not delete the old session
session_regenerate_id();

// because the session_regenerate_id() function does not delete the old session,
// we have to delete it manually
$this->destroy($oldSessionID);

}

/**
* Get the number of online users
*
* This is not 100% accurate. It depends on how often the garbage collector is run
*
* @return integer approximate number of users curently online
*/
function get_users_online()
{

// counts the rows from the database
$result = @mysql_fetch_assoc(@mysql_query("
SELECT
COUNT(session_id) as count
FROM session_data
"));

// return the number of found rows
return $result["count"];

}

/**
* Custom open() function
*
* @access private
*/
function open($save_path, $session_name)
{

return true;

}

/**
* Custom close() function
*
* @access private
*/
function close()
{
return true;
}

/**
* Custom read() function
*
* @access private
*/
function read($session_id)
{

// reads session data associated with the session id
// but only if the HTTP_USER_AGENT is the same as the one who had previously written to this session
// and if session has not expired
$result = @mysql_query("
SELECT
session_data
FROM
session_data
WHERE
session_id = '".$session_id."' AND
http_user_agent = '".$_SERVER["HTTP_USER_AGENT"]."' AND
session_expire > '".time()."'
");

// if anything was found
if (is_resource($result) && mysql_num_rows($result) > 0) {

// return found data
$fields = mysql_fetch_assoc($result);
// don't bother with the unserialization - PHP handles this automatically
return $fields["session_data"];

}

// if there was an error return an epmty string - this HAS to be an empty string
return "";

}

/**
* Custom write() function
*
* @access private
*/
function write($session_id, $session_data)
{

// first checks if there is a session with this id
$result = @mysql_query("
SELECT
*
FROM
session_data
WHERE
session_id = '".$session_id."'
");

// if there is
if (mysql_num_rows($result) > 0) {

// update the existing session's data
// and set new expiry time
$result = @mysql_query("
UPDATE
session_data
SET
session_data = '".$session_data."',
session_expire = '".(time() + $this->sessionLifetime)."'
WHERE
session_id = '".$session_id."'
");

// if anything happened
if (mysql_affected_rows()) {

// return true
return true;

}

// if this session id is not in the database
} else {

// insert a new record
$result = @mysql_query("
INSERT INTO
session_data
(
session_id,
http_user_agent,
session_data,
session_expire
)
VALUES
(
'".$session_id."',
'".$_SERVER["HTTP_USER_AGENT"]."',
'".$session_data."',
'".(time() + $this->sessionLifetime)."'
)
");

// if anything happened
if (mysql_affected_rows()) {

// return an empty string
return "";

}

}

// if something went wrong, return false
return false;

}

/**
* Custom destroy() function
*
* @access private
*/
function destroy($session_id)
{

// deletes the current session id from the database
$result = @mysql_query("
DELETE FROM
session_data
WHERE
session_id = '".$session_id."'
");

// if anything happened
if (mysql_affected_rows()) {

// return true
return true;

}

// if something went wrong, return false
return false;

}

/**
* Custom gc() function (garbage collector)
*
* @access private
*/
function gc($maxlifetime)
{

// it deletes expired sessions from database
$result = mysql_query("
DELETE FROM
session_data
WHERE
session_expire < '".(time() - $maxlifetime)."'
");

}

}
?>