前言:
在不久以前,本人去參加了某公司的實習面試,其中面試官問我關於 SESSION 實現的原理,當時我就懵逼了,由於在以前的開發中,我只知道 session 與 cookie 的區別在於:session 是保存在服務器端,cookie 保存在客戶端。那 session 在服務端是怎麼樣保存的?session_id 又是什麼?等等。我當時答不上來。回來後決定把這些搞懂。php
爲何要使用 SESSION?
是由於目前網絡中所使用的http協議形成的,http協議是無狀態協議,通俗點說就是當你發送一次請求道服務器端,而後再次發送請求到服務器端,服務器是不知道你的這一次請求和上一次請求是來源於同一我的發送的。而 session 就能很好解決這個問題。面試
在咱們的訪問期間,各個頁面間共享的數據放在session中,就好比說咱們的登錄信息,若是沒有 session 的話,當你在這個頁面登錄以後,在點擊下一個頁面的時候你須要再次登錄。redis
引入:
如今咱們來看看平時咱們是怎麼使用 session 的,你們看下面的例子數據庫
<?php #index.php 文件 session_start(); //啓動會話 $user = isset($_GET['user'])?$_GET['user']:"default" if(!isset($_SESSION['user'])){ $_SESSION['user'] = $user; //設置會話 } var_dump($_SESSION); unset($_SESSION['user']); //清除會話
如今咱們在瀏覽器 A 打開ubuntu
http://localhost/index.php?user=lsgogroup;
返回:vim
array(1){["user"]=>string(9)"lsgogroup"}
在瀏覽器 B 打開數組
http://localhost/index.php
返回:瀏覽器
array(1){["user"]=>string(7)"default"}
問題:緩存
session_start() 的做用是什麼?
爲何在瀏覽器 B 中返回的不是:服務器
array(1){[「user」]=>string(9)「lsgogroup」} ?
$_SESSION 數組是怎麼保存這些數據的?
理解 PHP SESSION 機制:
session 機制是一種服務器端的機制,服務器使用一種相似於散列表的結構來保存信息。
當程序須要爲某個客戶端的請求建立一個 session 的時候,服務器首先檢查這個客戶端的請求(Http Request)裏是否已包含了一個 session 標識-稱爲 sessionid,若是已包含一個 sessionid 則說明之前已經爲此客戶端建立過 session,服務器就按照 sessionid 把這個 session 檢索出來使用,若是客戶端請求不包含 sessionid,則爲此客戶端建立一個 session 而且生成一個與此 session 相關聯的 sessionid,sessionid的值應該是一個既不會重複,又不容易被找到規律以仿造的字符串,這個 sessionid 將被在本次響應中返回給客戶端保存。而這個 sessionid 就是做爲客戶端的惟一標識而存在的(即便在同一臺電腦上,瀏覽器 A 和瀏覽器 B 對於服務器來講都是不一樣的客戶端)。
上面一段話你可能暫時不會理解,不過沒關係,我會在下面做出解釋:
如今咱們來看看瀏覽器 A 和 瀏覽器 B 的 cookie:
瀏覽器 A (這裏對應是谷歌瀏覽器):
瀏覽器 B (這裏對應是火狐瀏覽器) :
對比能夠看到,兩個瀏覽器對於 localhost 都有一條名爲 PHPSESSID 的 cookie 記錄,而這個 PHPSESSID 就是上面所說的 sessionid,它告訴服務器請求是來自瀏覽器 A 仍是瀏覽器 B 。
如今咱們能夠回答上面的問題 2 了:
因爲瀏覽器 A 的 PHPSESSID 和瀏覽器 B 的 PHPSESSID 是不同的,所以服務器根據 sessionid 檢索 session 的數據也是不同的,也就是說瀏覽器 A 請求的 $_SESSION 數組和 瀏覽器 B 請求的 $_SESSION 數組也是不同的。
(固然,PHPSESSID 這個 id 名不是固定的,咱們能夠在 php.ini 文件中的 session.name 項進行修改。)
上面的例子是使用 COOKIE 保存 PHPSESSID,可是,因爲 cookie 能夠被人爲的禁止,必須有其餘機制以便在 cookie 被禁止時仍然可以把 sessionid 傳遞迴服務器。有兩種技術能夠解決這個問題:
URL重寫,就是把 sessionid 直接附加在URL路徑的後面:
http://localhost/index.php?user=lsgogroup&PHPSESSID=ByOK3vjFD75aPnrF7C2HmdnV6QZcEbzWoWiBYEnLerjQ99zWpBng
隱藏表單傳遞。
因爲這不是重點,這裏不展開講。
SESSION 是怎麼存儲數據的?
答:session 是以文件的形式保存的。
php.ini 中的配置項 session.save_handler = files;
默認爲 file,定義 session 在服務端的保存方式,file 意爲把 session 保存到一個臨時文件裏。
php.ini 中的配置項 session.save_path= 「」;
這個裏面填寫的路徑,將會使session文件保存在該路徑下。
session 文件的命名格式是:「sess_[PHPSESSID的值]」。每個文件,裏面保存了一個會話的數據。
咱們查看服務器端 session.save_path 目錄會發現不少相似 sess_vv9lpgf0nmkurgvkba1vbvj915 這樣的文件,這個其實就是 sessionid(也就是 PHPSESSID) 「vv9lpgf0nmkurgvkba1vbvj915″ 對應的數據。真相就在這裏,客戶端將 sessionid 傳遞到服務器,服務器根據 sessionid 找到對應的文件,讀取的時候對文件內容進行反序列化就獲得 session 的值($_SESSION數組中的數據),保存的時候先序列化再寫入。
因爲我作實驗的時候使用的是 ubuntu 系統,所以個人 session.save_path 默認實在 /var/lib/php/sessions 下,咱們來看看前面瀏覽器 A 生成的 session 文件是怎樣的(瀏覽器 A 的 PHPSESSID = ‘nqqleletmsb0nuf7d4ulvotk45’):
cd /var/lib/php/sessions #因爲session數據是很重要的數據,所以必須只能 root 用戶才能打開 sudo vim sess_nqqleletmsb0nuf7d4ulvotk45 #看看文件格式是否是 "sess_[PHPSESSID的值]"
文件內容:
user|s:9:"Jodieeeee";
從文件內容能夠看到,數據是通過序列化的,數據的讀取規則是這樣的:
每個session的值是以分號";"分開的。好比」user|s:9:「Jodieeeee」;「就是一個完整的session值結束,若是再添加 $_SESSION[‘name’]=「LSGOZJ」,則變成這樣 」user|s:9:「Jodieeeee」;name|s:7:「LSGOZJ」;「
裏面的讀取規則:符號「|」前面表示 session 名稱。符號後面是該 session 的具體信息。包括:數據類型,字符長度,內容。好比說 」user|s:9:「Jodieeeee」;「,$_SESSION[‘user’] 的值是 「Jodieeeee」,是一個長度爲 9 的字符串。
等等。。。
到了這裏,咱們就解決了上面的問題 3 了。
其實還有不少種存儲session的方式,若是咱們想自定義別的方式保存(好比用數據庫),則須要把該項設置爲 user;咱們還可使用 memcache、redis 等優秀的緩存系統(前提是你的服務器安裝了此類軟件)。
session_start()函數的做用是什麼?
瞭解的原理以後,所謂的 session 其實就是客戶端一個 sessionid 對應服務器端一個 session file,新建session 以前執行 session_start() 是告訴服務器要種一個 cookie 以及準備好 session 文件,要否則你的session 內容怎麼存;讀取 session 以前執行 session_start() 是告訴服務器,趕忙根據 sessionid 把對應的 session 文件反序列化。
說白了,當咱們使用 php 的內置函數 session_start( ) 的時候,就是到服務器的指定的磁盤目錄把 session 數據載入,實際上就是拿相似 sess_74dd7807n2mfml49a1i12hkc45 的文件。
只有一個 session 函數能夠在 session_start() 以前執行,session_name():讀取或指定 session 名稱(好比默認的就是」PHPSESSID」),這個固然要在session_start以前執行。
根據 http 的請求機制,當瀏覽器請求的時候,頭部信息會把瀏覽器中的 cookie 一塊兒發給服務器。PHPSESSID 這個 cookie 也是在其中發給了服務器,php 引擎經過讀取 PHPSESSID 的值來肯定要載入哪一個 session 文件。
好比值爲 74dd7807n2mfml49a1i12hkc45,載入的就是"sess_74dd7807n2mfml49a1i12hkc45"。
注:當你調用 php 的函數 session_start(),才代表你須要使用 session 文件了。否則無緣無故就去載入文件,浪費性能。
SESSION 的清理:
在平時咱們談論 SESSION 的機制的時候,經常聽到這樣一種誤解「只要關閉瀏覽器,session就消失了」(本人也是一度認爲這樣),其實能夠想象一下會員卡的例子,除非顧客主動對店家提出銷卡,不然店家絕對不會輕易刪除顧客的資料。
對 session 來講也是同樣的,除非程序通知服務器刪除一個 session,不然服務器會一直保留,程序通常都是在用戶作 logoff (註銷操做,相似於 session_destroy()操做)的時候發個指令去刪除 session。然而瀏覽器歷來不會主動在關閉以前通知服務器它將要關閉,所以服務器根本不會有機會知道瀏覽器已經關閉,之因此會有這種錯覺,是大部分 session 機制都使用會話 cookie 來保存 sessionid ,而關閉瀏覽器後這個sessionid就消失了,再次鏈接服務器時也就沒法找到原來的session,可是服務器上對應的 session file 依然存在。
爲何關閉瀏覽器後 sessionid 就會消失呢?這跟 cookie 在客戶端的存儲有關,若是在設置 cookie 的時候沒有指定生命週期,那麼 cookie 的數據是存儲在內存中的,當瀏覽器被關閉,內存被回收了,那麼cookie 也就沒有了(這就是爲何cookie在沒有指定生命週期的時候,其生命週期與瀏覽器生命週期同樣)。
若是服務器設置的 cookie 被保存到硬盤上(設置了生命週期),或者使用某種手段改寫瀏覽器發出的HTTP請求頭,把原來的 sessionid 發送給服務器,則再次打開瀏覽器仍然可以找到原來的session。
偏偏是因爲關閉瀏覽器不會致使 session 被刪除,迫使服務器爲 seesion 設置了一個失效時間,當距離客戶端下一次使用 session 的時間超過這個失效時間時,服務器就能夠認爲客戶端已經中止了活動,纔會把session 刪除以節省存儲空間。
咱們來看看服務器是怎樣刪除 session 數據的:
session.gc_probability = 1 session.gc_divisor = 100 session.gc_maxlifetime = 1440
這三個配置項組合構建服務端 session 的垃圾回收機制。
session.gc_probability 與 session.gc_divisor 構成執行 session 清理的機率,理論上的解釋爲服務端按期有必定的機率調用 gc(garbage collection 垃圾回收) 進程來對 session 進行清理,清理的機率爲:gc_probability/gc_divisor 好比:1/100 表示每個新會話初始化時,有 1% 的機率會啓動垃圾回收程序,清理的標準爲 session.gc_maxlifetime 定義的時間(清理過時的數據)。
我所用的系統是ubuntu,php.ini 中指定的 session.gc_probability = 0,也就是機率爲零,緣由是該系統是使用 cron 腳原本執行垃圾清理的。
後話:
session 還有不少須要整理和學習的地方,如:
session多服務器共享的問題,假若有多臺php服務器進行負載均衡的時候,用戶登陸時訪問的是第一臺服務器,沒準下一個頁面訪問的是第二臺服務器,可是 session 數據是存儲在第一臺服務器上的,所以在訪問下一個頁面的時候因爲沒有 session 數據(第二臺服務器上)致使用戶必須從新登錄。從上面的分析咱們也知道,php 中 session 默認經過文件的方式實現,可是若是訪問量大,可能產生的 SESSION 文件會比較多,從衆多的文件中選擇其中一個文件不是一件輕鬆的事情,並且每次都以打開文件、讀取文件的方式,也會產生大量的 I/O 操做,嚴重影響服務器的性能。