1
2
3
4
5
6
7
8
9
10
11

知識拓展

發(fā)布時間:2017-07-20 08:03   發(fā)布人:毛書朋   浏覽次數:6486

     當用戶量非常大,需要多台服務器提供應用的時候,使用MySQL存儲會話相對(duì)使用會話文件具有一定的優越性。比如具有最小的存儲開(kāi)銷,比如可以避免文件共享帶來的複雜性,比如可以更好(hǎo)的避免發(fā)生碰撞,比如相比會話文件共享具有更好(hǎo)的性能(néng)。總體上來說,當訪問量劇增的時候,如果使用數據庫保存會話帶來的問題是線性增長(cháng)的,那麼(me)使用會話文件帶來的問題幾乎是爆炸性的。好(hǎo)吧,換一個更直白的說法吧:如果您的應用用戶量不大,其實讓PHP自己處理session就好(hǎo)了,沒(méi)必要考慮MySQL。

具體思路如下:

     1、使用MySQL保存session,需要保存三個關鍵性的數據:session id、session數據、session生命期。

     2、考慮到session的使用方式,沒(méi)必要使用InnoDB引擎,MyISAM引擎可以獲得更好(hǎo)的性能(néng)。如果環境允許,可以嘗試使用MEMORY引擎。

     3、保存session數據的列,有需要的話,可以使用utf8或utf8mb4字符集;保存session id的列則沒(méi)有必要,一般情況使用ascii字符集就可以了,可以節約存儲成(chéng)本。

     4、保存session生命期的列,可以根據工程需要進(jìn)行設計。比如datetime類型、timestamp類型、int類型。對(duì)于datetime、int類型可以保存session生成(chéng)時間或過(guò)期時間。

     5、如果有必要可以擴展session表的列并修改讀、寫函數以支持(維護)相關列來保存諸如用戶名等信息。

     6、當前版本,隻要通過(guò)session_set_save_handler注冊自定義的會話維護函數就可以,不需要在其之前使用session_module_name('user')函數。

     7、當read函數獲取數據并返回,PHP會自動對(duì)其進(jìn)行反序列化,一般情況請不要對(duì)數據進(jìn)行更改。

     8、PHP傳遞給write函數的date參數是序列化之後(hòu)的session數據,直接保存即可,一般情況請不要對(duì)數據進(jìn)行更改。

     9、按照本段代碼的邏輯,PHP配置選項關于會話生命期的設置已經(jīng)不再有效,這(zhè)個值可以自行維護,不一定需要通過(guò)get_cfg_var獲取。

     10、sessionMysqlId()函數是爲了避免大用戶量、多台Web服務器情況下的碰撞,一般情況PHP自動生成(chéng)的session id是可以滿足用戶要求的。

創建數據表代碼如下:

CREATE TABLE `session` (

  `skey` char(32) CHARACTER SET ascii NOT NULL,

  `data` text COLLATE utf8mb4_bin,

  `expire` int(11) NOT NULL,

  PRIMARY KEY (`skey`),

  KEY `index_session_expire` (`expire`) USING BTREE

) ENGINE=MyISAM DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

PHP代碼如下:

  1 <?php

  2 /*

  3  * 連接數據庫所需的DNS、用戶名、密碼等,一般情況不會在代碼中進(jìn)行更改,

  4  * 所以使用常量的形式,可以避免在函數中引用而需要global。

  5  */

  6 define('SESSION_DNS', 'mysql:host=localhost;dbname=db;charset=utf8mb4');

  7 define('SESSION_USR', 'usr');

  8 define('SESSION_PWD', 'pwd');

  9 define('SESSION_MAXLIFETIME', get_cfg_var('session.gc_maxlifetime'));

 10 

 11 //創建PDO連接

 12 //持久化連接可以提供更好(hǎo)的效率

 13 function getConnection() {

 14     try {

 15         $conn = new PDO(SESSION_DNS, SESSION_USR, SESSION_PWD, array(

 16             PDO::ATTR_PERSISTENT => TRUE,

 17             PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,

 18             PDO::ATTR_EMULATE_PREPARES => FALSE

 19         ));

 20         return $conn;

 21     } catch (Exception $ex) {

 22 

 23     }

 24 }

 25 

 26 //自定義的session的open函數

 27 function sessionMysqlOpen($savePath, $sessionName) {

 28     return TRUE;

 29 }

 30 

 31 //自定義的session的close函數

 32 function sessionMysqlClose() {

 33     return TRUE;

 34 }

 35 /*

 36  * 由于一般不會把用戶提交的數據直接保存到session,所以普通情況不存在注入問題。

 37  * 且處理session數據的SQL語句也不會多次使用。因此預處理功能(néng)的效益無法體現。

 38  * 所以,實際工程中可以不必教條的使用預處理功能(néng)。

 39  */

 40 /*

 41  * sessionMysqlRead()函數中,首先通過(guò)SELECT count(*)來判斷sessionID是否存在。

 42  * 由于MySQL數據庫提供SELECT對(duì)PDOStatement::rowCount()的支持,

 43  * 因此,實際的工程中可以直接使用rowCount()進(jìn)行判斷。

 44  */

 45 //自定義的session的read函數

 46 //SQL語句中增加了“expire > time()”判斷,用以避免讀取過(guò)期的session。

 47 function sessionMysqlRead($sessionId) {

 48     try {

 49         $dbh = getConnection();

 50         $time = time();

 51         

 52         $sql = 'SELECT count(*) AS `count` FROM session '

 53                 . 'WHERE skey = ? and expire > ?';

 54         $stmt = $dbh->prepare($sql);

 55         $stmt->execute(array($sessionId, $time));

 56         $data = $stmt->fetch(PDO::FETCH_ASSOC);

 57         if ($data['count'] = 0) {

 58             return '';

 59         }

 60         

 61         $sql = 'SELECT `data` FROM `session` '

 62                 . 'WHERE `skey` = ? and `expire` > ?';

 63         $stmt = $dbh->prepare($sql);

 64         $stmt->execute(array($sessionId, $time));

 65         $data = $stmt->fetch(PDO::FETCH_ASSOC);

 66         return $data['data'];

 67     } catch (Exception $e) {

 68         return '';

 69     }

 70 }

 71 

 72 //自定義的session的write函數

 73 //expire字段存儲的數據爲當前時間 session生命期,當這(zhè)個值小于time()時表明session失效。

 74 function sessionMysqlWrite($sessionId, $data) {

 75     try {

 76         $dbh = getConnection();

 77         $expire = time()   SESSION_MAXLIFETIME;

 78 

 79         $sql = 'INSERT INTO `session` (`skey`, `data`, `expire`) '

 80                 . 'values (?, ?, ?) '

 81                 . 'ON DUPLICATE KEY UPDATE data = ?, expire = ?';

 82         $stmt = $dbh->prepare($sql);

 83         $stmt->execute(array($sessionId, $data, $expire, $data, $expire));

 84     } catch (Exception $e) {

 85         echo $e->getMessage();

 86     }

 87 }

 88 

 89 //自定義的session的destroy函數

 90 function sessionMysqlDestroy($sessionId) {

 91     try {

 92         $dbh = getConnection();

 93 

 94         $sql = 'DELETE FROM `session` where skey = ?';

 95         $stmt = $dbh->prepare($sql);

 96         $stmt->execute(array($sessionId));

 97         return TRUE;

 98     } catch (Exception $e) {

 99         return FALSE;

100     }

101 }

102 

103 //自定義的session的gc函數

104 function sessionMysqlGc($lifetime) {

105     try {

106         $dbh = getConnection();

107 

108         $sql = 'DELETE FROM `session` WHERE expire < ?';

109         $stmt = $dbh->prepare($sql);

110         $stmt->execute(array(time()));

111         $dbh = NULL;

112         return TRUE;

113     } catch (Exception $e) {

114         return FALSE;

115     }

116 }

117 

118 //自定義的session的session id設置函數

119 /*

120  * 由于在session_start()之前,SID和session_id()均無效,

121  * 故使用$_GET[session_name()]和$_COOKIE[session_name()]進(jìn)行檢測。

122  * 如果此兩(liǎng)者均爲空,則表明session尚未建立,需要爲新session設置session id。

123  * 通過(guò)MySQL數據庫獲取uuid作爲session id可以更好(hǎo)的避免session id碰撞。

124  */

125 function sessionMysqlId() {

126     if (filter_input(INPUT_GET, session_name()) == '' and

127             filter_input(INPUT_COOKIE, session_name()) == '') {

128         try {

129             $dbh = getConnection();

130             $stmt = $dbh->query('SELECT uuid() AS uuid');

131             $data = $stmt->fetch(PDO::FETCH_ASSOC);

132             $data = str_replace('-', '', $data['uuid']);

133             session_id($data);

134             return TRUE;

135         } catch (Exception $ex) {

136             return FALSE;

137         }

138         

139     }

140 }

141 

142 //session啓動函數,包括了session_start()及其之前的所有步驟。

143 function startSession() {

144     session_set_save_handler(

145             'sessionMysqlOpen',

146             'sessionMysqlClose',

147             'sessionMysqlRead',

148             'sessionMysqlWrite',

149             'sessionMysqlDestroy',

150             'sessionMysqlGc');

151     register_shutdown_function('session_write_close');

152     sessionMysqlId();

153     session_start();

154 }



  • 姜瑞祥 2018-01-07 17:34:06
    在建站的時候,都(dōu)會采用單一入口(多見爲從index1.html)的形式,我覺得,單一入口的好(hǎo)處有下面(miàn)兩(liǎng)點:   1.後(hòu)面(miàn)的程序可以統一處理,比如說要開(kāi)發(fā)時候用到某個第三方類庫,隻需要在入口文件引入,其餘的程序都(dōu)會可以引用   2.路徑可以統一處理。因爲都(dōu)是從index1.html開(kāi)始,所以引入文件隻要相對(duì)于index1.html的路徑即可。