什麼是 Sessionphp
在 web 應用開發中,Session 被稱爲會話。主要被用於保存某個訪問者的數據。
因爲 HTTP 無狀態的特色,服務端是不會記住客戶端的,對服務端來講,每個請求都是全新的。
既然如此,那麼服務端怎麼知道是哪一個訪問者在請求它呢?又如何將不一樣的數據對應上正確的訪問者?答案是,給訪問者一個惟一獲取 Session 中數據的身份標示。web
打個比方:當咱們去超市購物時,被保安告之咱們是不能帶物品進去的,必須將物品寄放在超市的儲物箱中。咱們把物品交給了他,他怎麼知道這些物品誰是誰的,因而他給了咱們不一樣的鑰匙。當咱們要取走咱們的物品時,用惟一的鑰匙打開對應的箱子便可。面試
就如同上面的比方同樣,能夠將 Session 理解爲存放咱們數據的「箱子」,固然,這些「箱子」都在服務端那。服務器給訪問者惟一的「鑰匙」,這個「鑰匙」被稱做 session_id。訪問者憑藉本身的 session_id,就能獲取到本身存在服務器端的數據。瀏覽器
session_id 經過兩種方式傳給訪問者(客戶端):URL 或 cookie。詳情參見:傳送會話ID
Session 和 Cookie 有什麼關係安全
Cookie 也是因爲 HTTP 無狀態的特色而產生的技術。也被用於保存訪問者的身份標示和一些數據。每次客戶端發起 HTTP 請求時,會將 Cookie 數據加到 HTTP header 中,提交給服務端。這樣服務端就能夠根據 Cookie 的內容知道訪問者的信息了。 能夠說,Session 和 Cookie 作着類似的事情,只是 Session 是將數據保存在服務端,經過客戶端提交來的 session_id 來獲取對應的數據;而 Cookie 是將數據保存在客戶端,每次發起請求時將數據提交給服務端的。服務器
上面提到,session_id 能夠經過 URL 或 cookie 來傳遞,因爲 URL 的方式比 cookie 的方式更加不安全且使用不方便,因此通常是採用 cookie 來傳遞 session_id。
服務端生成 session_id,經過 HTTP 報文發送給客戶端(好比瀏覽器),客戶端收到後按指示建立保存着 session_id 的 cookie。cookie 是以 key/value 形式保存的,看上去大概就這個樣子的:PHPSESSID=e4tqo2ajfbqqia9prm8t83b1f2。在 PHP 中,保存 session_id 的 cookie 名稱默認叫做 PHPSESSID,這個名稱能夠經過 php.ini 中 session.name 來修改,也能夠經過函數 session_name() 來修改。
爲何不推薦使用 PHP 自帶的 files 型 Session 處理器cookie
在 PHP 中,默認的 Session 處理器是 files,處理器能夠用戶本身實現(參見:自定義會話管理器)。我知道的成熟的 Session 處理器還有不少:Redis、Memcached、MongoDB……
爲何不推薦使用 PHP 自帶的 files 類型處理器,PHP 官方手冊中給出過這樣一段 Note:session
不管是經過調用函數 session_start() 手動開啓會話, 仍是使用配置項 session.auto_start 自動開啓會話, 對於基於文件的會話數據保存(PHP 的默認行爲)而言, 在會話開始的時候都會給會話數據文件加鎖, 直到 PHP 腳本執行完畢或者顯式調用 session_write_close() 來保存會話數據。 在此期間,其餘腳本不能夠訪問同一個會話數據文件。
上述引用參見:Session 的基本用法併發
爲了證實這段話,咱們建立一下 2 個文件: 文件:session1.phpmemcached
<?php
session_start();
sleep(5);
var_dump($_SESSION);
?>
文件:session2.php
<?php
session_start();
var_dump($_SESSION);
?>
在同一個瀏覽器中,先訪問 http://127.0.0.1/session1.php,而後在當前瀏覽器新的標籤頁馬上訪問 http://127.0.0.1/session2.php。實驗發現,session1.php 等了 5 秒鐘纔有輸出,而 session2.php 也等到了將近 5 秒纔有輸出。而單獨訪問 session2.php 是秒開的。在一個瀏覽器中訪問 session1.php,而後馬上在另一個瀏覽器中訪問 session2.php。結果是 session1.php 等待 5 秒鐘有輸出,而 session2.php 是秒開的。
分析一下形成這個現象的緣由:上面例子中,默認使用 Cookie 來傳遞 session_id,並且 Cookie 的做用域是相同。這樣,在同一個瀏覽器中訪問這 2 個地址,提交給服務器的 session_id 就是相同的(這樣才能標記訪問者,這是咱們指望的效果)。當訪問 session1.php 時,PHP 根據提交的 session_id,在服務器保存 Session 文件的路徑(默認爲 /tmp,經過 php.ini 中的 session.save_path 或者函數 session_save_path() 來修改)中找到了對應的 Session 文件,並對其加鎖。若是不顯式調用 session_write_close(),那麼直到當前 PHP 腳本執行完畢纔會釋放文件鎖。若是在腳本中有比較耗時的操做(好比例子中的 sleep(5)),那麼另外一個持有相同 session_id 的請求因爲文件被鎖,因此只能被迫等待,因而就發生了請求阻塞的狀況。
既然如此,在使用完 Session 後,馬上顯示調用 session_write_close() 是否是就解決問題了哩?好比上面例子中,在 sleep(5) 前面調用 session_write_close()。
確實,這樣 session2.php 就不會被 session1.php 所阻塞。可是,顯示調用了 session_write_close() 就意味着將數據寫到文件中並結束當前會話。那麼,在後面代碼中要使用 Session 時,必須從新調用 session_start()。
例如:
<?php
session_start();
$_SESSION['name'] = 'Jing';
var_dump($_SESSION);
session_write_close();
sleep(5);
session_start();
$_SESSION['name'] = 'Mr.Jing';
var_dump($_SESSION);
?>
官方給出的方案:
對於大量使用 Ajax 或者併發請求的網站而言,這多是一個嚴重的問題。 解決這個問題最簡單的作法是若是修改了會話中的變量, 那麼應該儘快調用 session_write_close() 來保存會話數據並釋放文件鎖。 還有一種選擇就是使用支持併發操做的會話保存管理器來替代文件會話保存管理器。
我推薦的方式是使用 Redis 做爲 Session 的處理器。
拓展閱讀:
爲何不能用 memcached 存儲 Session
如何使用 Redis 做爲 PHP Session handler
Session 數據是何時被刪除的
這是一道常常被面試官問起的問題。
先看看官方手冊中的說明:
session.gc_maxlifetime 指定過了多少秒以後數據就會被視爲"垃圾"並被清除。 垃圾蒐集可能會在 session 啓動的時候開始( 取決於 session.gc_probability 和 session.gc_divisor)。 session.gc_probability 與 session.gc_divisor 合起來用來管理 gc(garbage collection 垃圾回收)進程啓動的機率。此機率用 gc_probability/gc_divisor 計算得來。例如 1/100 意味着在每一個請求中有 1% 的機率啓動 gc 進程。session.gc_probability 默認爲 1,session.gc_divisor 默認爲 100。
繼續用我上面那個不太恰當的比方吧:若是咱們把物品放在超市的儲物箱中而不取走,過了好久(好比一個月),那麼保安就要清理這些儲物箱中的物品了。固然並非超過時限了保安就必定會來清理,也許他懶,又或者他壓根就沒有想起來這件事情。
再看看兩段手冊的引用:
若是使用默認的基於文件的會話處理器,則文件系統必須保持跟蹤訪問時間(atime)。Windows FAT 文件系統不行,所以若是必須使用 FAT 文件系統或者其餘不能跟蹤 atime 的文件系統,那就不得不想別的辦法來處理會話數據的垃圾回收。自 PHP 4.2.3 起用 mtime(修改時間)來代替了 atime。所以對於不能跟蹤 atime 的文件系統也沒問題了。 GC 的運行時機並非精準的,帶有必定的或然性,因此這個設置項並不能確保舊的會話數據被刪除。某些會話存儲處理模塊不使用此設置項。
對於這種刪除機制,我是存疑的。
好比 gc_probability/gc_divisor 設置得比較大,或者網站的請求量比較大,那麼 GC 進程啓動就會比較頻繁。
還有,GC 進程啓動後都須要遍歷 Session 文件列表,對比文件的修改時間和服務端的當前時間,判斷文件是否過時而決定是否刪除文件。
這也是我以爲不該該使用 PHP 自帶的 files 型 Session 處理器的緣由。而 Redis 或 Memcached 天生就支持 key/value 過時機制的,用於做爲會話處理器很合適。或者本身實現一個基於文件的處理器,當根據 session_id 獲取對應的單個 Session 文件時判斷文件是否過時。
爲何重啓瀏覽器後 Session 數據就取不到了
session.cookie_lifetime 以秒數指定了發送到瀏覽器的 cookie 的生命週期。值爲 0 表示"直到關閉瀏覽器"。默認爲 0。
其實,並非 Session 數據被刪除(也有多是,機率比較小,參見上一節)。只是關閉瀏覽器時,保存 session_id 的 Cookie 沒有了。也就是你弄丟了打開超市儲物箱的鑰匙(session_id)。
同理,瀏覽器 Cookie 被手動清除或者其餘軟件清除也會形成這個結果。
爲何瀏覽器開着,我好久沒有操做就被登出了
這個是稱爲「防呆」,爲了保護用戶帳戶安全的。
這個小節放進來,是由於這個功能的實現可能和 Session 的刪除機制有關(之因此說是可能,是由於這個功能不必定要借住 Session 實現,用 Cookie 也一樣能夠實現)。 說簡單一點,就是長時間沒有操做,服務端的 Session 文件過時被刪除了。
一個有意思的事情
在我試驗的過程當中,發現了小有意思的事情:我把 GC 啓動的機率設置爲 100%。若是隻有一個訪問者請求,該訪問者即便過了好久(超過了過時時間)後才發起第二次請求,那麼 Session 數據也仍是存在的('session.save_path' 目錄下面的 Session 文件存在)。是的,明明就超過了過時時間,卻沒有被 GC 刪除。這時,我用另一個瀏覽器訪問時(相對於另外一個訪問者),此次請求生成了新的 Session 文件,而上一個瀏覽器請求生成的那個 Session 文件終於沒有了(以前那個 Session 文件在 'session.save_path' 目錄下面的消失了)。
還有,發現 Session 文件被刪除後,再次請求,仍是會生成和以前文件名相同的 Session 文件(由於瀏覽器並無關閉,再次請求發送的 session_id 是相同的,因此從新生成的 Session 文件的文件名仍是同樣的)。可是,我不理解的是:這個從新出現的文件的建立時間居然是第一次的那個建立時間,難道它是從回收站中回來的?(確實,我作這個試驗時是在 window 下進行的)
我猜想的緣由是這樣:當啓動會話後,PHP 根據 session_id 找到並打開了對應的 Session 文件,而後才啓動 GC 進程。GC 進程就只檢查除了當前這個 Session 文件外的其餘文件,發現過時的就幹掉。全部,即便當前這個 Session 文件已通過期了,GC 也沒有刪除它。
我認爲這個不合理的。
因爲發生這種狀況影響也不大(畢竟線上請求不少,當前請求的過時文件被其餘請求喚起的 GC 幹掉的可能性是比較大的) + 我沒有信心去看 PHP 源代碼 + 我並不在線上使用 PHP 自帶的 files 型 Session 處理器。因此,這個問題我就沒有深刻研究了。請諒解。
<?php
// 過時時間設置爲 30 秒
ini_set('session.gc_maxlifetime', '30');
// GC 啓動機率設置爲 100%
ini_set('session.gc_probability', '100');
ini_set('session.gc_divisor', '100');
session_start();
$_SESSION['name'] = 'Jing';
var_dump($_SESSION);?>