前言:
今天就來徹底的學一些session是個啥東西,我羅列了幾個需要知道的要點:
1.session 是啥?
2.怎麼保存的?
3.如何運行?
4.有生命週期嗎?
5.關閉瀏覽器會過期嗎?
6.Redis 代替文件存儲session
7.分佈式session的同步問題
session是啥?
首先,我大致的知道,session是一次瀏覽器和服務器的交互的會話,會話是啥呢?就是我問候你好嗎?你回恩很好。就是一次會話,那麼對話完成後,這次會話就結束了,還有我也知道,我們可以將一個變量存入全部的$_SESSION['name'] 中,這樣php的各個頁面和邏輯都能訪問到,所以很輕鬆的用來判斷是否登陸。
這是我之前理解的session,當然也是對的,只是解釋的太膚淺,理解的太表面了,面試官如果聽到這樣的答案其實是不太滿意的。我參考了其他的很多資料,徹底理解清楚session。
在說session是啥之前,我們先來說說爲什麼會出現session會話,它出現的機理是什麼?我們知道,我們用瀏覽器打開一個網頁,用到的是HTTP協議,學過計算機的應該都知道這個協議,它是無狀態的,什麼是無狀態呢?就是說這一次請求和上一次請求是沒有任何關係的,互不認識的,沒有關聯的。但是這種無狀態的的好處是快速。
所以就會帶來一個問題就是,我希望幾個請求的頁面要有關聯,比如:我在www.a.com/login.php裏面登陸了,我在www.a.com/index.php 也希望是登陸狀態,但是,這是2個不同的頁面,也就是2個不同的HTTP請求,這2個HTTP請求是無狀態的,也就是無關聯的,所以無法單純的在index.php中讀取到它在login.php中已經登陸了!
那咋搞呢?我不可能這2個頁面我都去登陸一遍吧。或者用笨方法這2個頁面都去查詢數據庫,如果有登陸狀態,就判斷是登陸的了。這種查詢數據庫的方案雖然可行,但是每次都要去查詢數據庫不是個事,會造成數據庫的壓力。
所以正是這種訴求,這個時候,一個新的客戶端存儲數據方式出現了:cookie。cookie是把少量的信息存儲在用戶自己的電腦上,它在一個域名下是一個全局的,只要設置它的存儲路徑在域名www.a.com下 ,那麼當用戶用瀏覽器訪問時,php就可以從這個域名的任意頁面讀取cookie中的信息。所以就很好的解決了我在www.a.com/login.php頁面登陸了,我也可以在www.a.com/index.php獲取到這個登陸信息了。同時又不用反覆去查詢數據庫。
雖然這種方案很不錯,也很快速方便,但是由於cookie 是存在用戶端,而且它本身存儲的尺寸大小也有限,最關鍵是用戶可以是可見的,並可以隨意的修改,很不安全。那如何又要安全,又可以方便的全局讀取信息呢?於是,這個時候,一種新的存儲會話機制:session 誕生了。
我擦,終於把session是怎麼誕生的給圓清楚了,不容易啊!!!
好,session 誕生了,從上面的描述來講,它就是在一次會話中解決2次HTTP的請求的關聯,讓它們產生聯繫,讓2兩個頁面都能讀取到找個這個全局的session信息。session信息存在於服務器端,所以也就很好的解決了安全問題。
session的運行機制和是怎麼保存的?
既然,它也是一種服務區存儲數據的方式,肯定也是存在服務器的某個地方了。確實,它存在服務器的/tmp 目錄下,這一點我們接下來慢慢講。
我們先說下它的運行機制,是怎麼分配的。我們主要用PHP中session的機制,其實各種語言都差不多。
如果這個時候,我們需要用到session,那我們第一步怎麼辦呢?第一步是開啓session:
session_start();
這是個無任何返回值的函數,既不會報錯,也不會成功。它的作用是開啓session,並隨機生成一個唯一的32位的session_id,類似於這樣:
4c83638b3b0dbf65583181c2f89168ec
session的全部機制也是基於這個session_id,它用來區分哪幾次請求是一個人發出的。爲什麼要這樣呢?因爲HTTP是無狀態無關聯的,一個頁面可能會被成百上千人訪問,而且每個人的用戶名是不一樣的,那麼服務器如何區分這次是小王訪問的,那次是小名訪問的呢?所以就有了找個唯一的session_id 來綁定一個用戶。一個用戶在一次會話上就是一個session_id,這樣成千上萬的人訪問,服務器也能區分到底是誰在訪問了。
我們做個試驗,看看,是不是這樣的:
我們在php.iyangyi.com 域名下的a.php 頁面中,輸入如下代碼:
echo “SID: “.SID.”<br>”;
echo “session_id(): “.session_id().”<br>”;
echo "COOKIE: ".C O O K I E [ " P H P S E S S I D " ] ; < / s p a n > < / p > 我 們 訪 問 一 下 a . p h p 頁 面 , 看 能 輸 出 什 麼 ? < / d i v > < d i v s t y l e = " c o l o r : r g b ( 69 , 69 , 69 ) ; f o n t − f a m i l y : A r i a l ; f o n t − s i z e : 14 p x ; " > < s p a n s t y l e = " c o l o r : r g b ( 51 , 51 , 51 ) ; f o n t − f a m i l y : a r i a l , ′ 宋 體 ′ , s a n s − s e r i f ; l i n e − h e i g h t : 24 p x ; t e x t − i n d e n t : 28 p x ; " > < b r > < / s p a n > < / d i v > < d i v s t y l e = " c o l o r : r g b ( 69 , 69 , 69 ) ; f o n t − f a m i l y : A r i a l ; f o n t − s i z e : 14 p x ; " > < s p a n s t y l e = " c o l o r : r g b ( 51 , 51 , 51 ) ; f o n t − f a m i l y : a r i a l , ′ 宋 體 ′ , s a n s − s e r i f ; l i n e − h e i g h t : 24 p x ; t e x t − i n d e n t : 28 p x ; " > < i m g s r c = " h t t p s : / / i m g − b l o g . c s d n . n e t / 20141015104513437 ? w a t e r m a r k / 2 / t e x t / a H R 0 c D o v L 2 J s b 2 c u Y 3 N k b i 5 u Z X Q v d G h p b m s y b W U = / f o n t / 5 a 6 L 5 L 2 T / f o n t s i z e / 400 / f i l l / I 0 J B Q k F C M A = = / d i s s o l v e / 70 / g r a v i t y / S o u t h E a s t " a l t = " " s t y l e = " b o r d e r : n o n e ; " > < b r > < / s p a n > < / d i v > < d i v s t y l e = " c o l o r : r g b ( 69 , 69 , 69 ) ; f o n t − f a m i l y : A r i a l ; f o n t − s i z e : 14 p x ; " > 我 們 看 到 居 然 還 有 一 個 警 告 。 我 們 先 一 個 一 個 的 看 。 首 先 < s p a n s t y l e = " c o l o r : r g b ( 199 , 37 , 78 ) ; f o n t − f a m i l y : M e n l o , M o n a c o , C o n s o l a s , ′ C o u r i e r N e w ′ , m o n o s p a c e ; l i n e − h e i g h t : 26 p x ; " > S I D < / s p a n > 這 個 常 量 , 我 們 沒 有 給 它 賦 值 , 它 居 然 能 有 輸 出 , 其 次 < s p a n s t y l e = " c o l o r : r g b ( 199 , 37 , 78 ) ; f o n t − f a m i l y : M e n l o , M o n a c o , C o n s o l a s , ′ C o u r i e r N e w ′ , m o n o s p a c e ; l i n e − h e i g h t : 26 p x ; " > s e s s i o n i d ( ) < / s p a n > 這 個 系 統 方 法 是 輸 出 本 次 生 成 的 s e s s i o n i d 。 最 後 _COOKIE["PHPSESSID"];</span></p>我們訪問一下a.php頁面,看能輸出什麼?</div><div style="color:rgb(69,69,69);font-family:Arial;font-size:14px;"><span style="color:rgb(51,51,51);font-family:arial, '宋體', sans-serif;line-height:24px;text-indent:28px;"><br></span></div><div style="color:rgb(69,69,69);font-family:Arial;font-size:14px;"><span style="color:rgb(51,51,51);font-family:arial, '宋體', sans-serif;line-height:24px;text-indent:28px;"><img src="https://img-blog.csdn.net/20141015104513437?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdGhpbmsybWU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" style="border:none;"><br></span></div><div style="color:rgb(69,69,69);font-family:Arial;font-size:14px;">我們看到居然還有一個警告。我們先一個一個的看。首先<span style="color:rgb(199,37,78);font-family:Menlo, Monaco, Consolas, 'Courier New', monospace;line-height:26px;">SID</span>這個常量,我們沒有給它賦值,它居然能有輸出,其次<span style="color:rgb(199,37,78);font-family:Menlo, Monaco, Consolas, 'Courier New', monospace;line-height:26px;">session_id()</span>這個系統方法是輸出本次生成的session_id。最後 C O O K I E [ " P H P S E S S I D " ] ; < / s p a n > < / p > 我 們 訪 問 一 下 a . p h p 頁 面 , 看 能 輸 出 什 麼 ? < / d i v > < d i v s t y l e = " c o l o r : r g b ( 6 9 , 6 9 , 6 9 ) ; f o n t − f a m i l y : A r i a l ; f o n t − s i z e : 1 4 p x ; " > < s p a n s t y l e = " c o l o r : r g b ( 5 1 , 5 1 , 5 1 ) ; f o n t − f a m i l y : a r i a l , ′ 宋 體 ′ , s a n s − s e r i f ; l i n e − h e i g h t : 2 4 p x ; t e x t − i n d e n t : 2 8 p x ; " > < b r > < / s p a n > < / d i v > < d i v s t y l e = " c o l o r : r g b ( 6 9 , 6 9 , 6 9 ) ; f o n t − f a m i l y : A r i a l ; f o n t − s i z e : 1 4 p x ; " > < s p a n s t y l e = " c o l o r : r g b ( 5 1 , 5 1 , 5 1 ) ; f o n t − f a m i l y : a r i a l , ′ 宋 體 ′ , s a n s − s e r i f ; l i n e − h e i g h t : 2 4 p x ; t e x t − i n d e n t : 2 8 p x ; " > < i m g s r c = " h t t p s : / / i m g − b l o g . c s d n . n e t / 2 0 1 4 1 0 1 5 1 0 4 5 1 3 4 3 7 ? w a t e r m a r k / 2 / t e x t / a H R 0 c D o v L 2 J s b 2 c u Y 3 N k b i 5 u Z X Q v d G h p b m s y b W U = / f o n t / 5 a 6 L 5 L 2 T / f o n t s i z e / 4 0 0 / f i l l / I 0 J B Q k F C M A = = / d i s s o l v e / 7 0 / g r a v i t y / S o u t h E a s t " a l t = " " s t y l e = " b o r d e r : n o n e ; " > < b r > < / s p a n > < / d i v > < d i v s t y l e = " c o l o r : r g b ( 6 9 , 6 9 , 6 9 ) ; f o n t − f a m i l y : A r i a l ; f o n t − s i z e : 1 4 p x ; " > 我 們 看 到 居 然 還 有 一 個 警 告 。 我 們 先 一 個 一 個 的 看 。 首 先 < s p a n s t y l e = " c o l o r : r g b ( 1 9 9 , 3 7 , 7 8 ) ; f o n t − f a m i l y : M e n l o , M o n a c o , C o n s o l a s , ′ C o u r i e r N e w ′ , m o n o s p a c e ; l i n e − h e i g h t : 2 6 p x ; " > S I D < / s p a n > 這 個 常 量 , 我 們 沒 有 給 它 賦 值 , 它 居 然 能 有 輸 出 , 其 次 < s p a n s t y l e = " c o l o r : r g b ( 1 9 9 , 3 7 , 7 8 ) ; f o n t − f a m i l y : M e n l o , M o n a c o , C o n s o l a s , ′ C o u r i e r N e w ′ , m o n o s p a c e ; l i n e − h e i g h t : 2 6 p x ; " > s e s s i o n i d ( ) < / s p a n > 這 個 系 統 方 法 是 輸 出 本 次 生 成 的 s e s s i o n i d 。 最 後 _COOKIE[‘PHPSESSIID’] 沒有值,這個我們接下來說。
好,我們再次刷新這個頁面,我們能看到什麼?
奇怪的事情發生了。SID 沒有值了,
KaTeX parse error: Undefined control sequence: \wamp at position 2222: …的這個session是存在D:\̲w̲a̲m̲p̲\tmp 目錄裏的。我們先說是… _SESSION[‘hello’] = 123;
KaTeX parse error: Expected 'EOF', got '&' at position 158: …同樣的session_id :&̲nbsp;<span styl… _SESSION全局變量裏寫數據時,它會自動往這個文件裏寫入。讀取session的時候,也會根據session_id 找到這個文件,然後讀取需要的session變量。
這個sess文件不會隨着客戶端的PHPSESSID過期,也一起過期掉,它會一直存在,出息GC掃描到它過期或者使用session_destroy()函數摧毀,我們在下面講到session·回收的時候會說到。
我們大致總結下:
HTTP請求一個頁面後,如果用到開啓session,會去讀cookie中的PHPSESSID是否有,如果沒有,則會新生成一個session_id,先存入cookie中的PHPSESSID中,再生成一個sess_前綴文件。當有寫入$_SESSION的時候,就會往sess_文件裏序列化寫入數據。當讀取的session變量的時候,先會讀取cookie中的PHPSESSID,獲得session_id,然後再去找這個sess_sessionid文件,來獲取對應的數據。由於默認的PHPSESSID是臨時的會話,在瀏覽器關閉後,會消失,所以,我們重新訪問的時候,會新生成session_id和sess_這個文件。
好。session生成和保存將清楚了。我們再來看前面提到的幾個變量:
echo “session_id(): “.session_id().”<br>”;
echo "COOKIE: ".$_COOKIE[“PHPSESSID”];
SID 是一個系統常量,SID包含着會話名以及會話 ID 的常量,格式爲 “name=ID”,或者如果會話 ID 已經在適cookie 中設定時則爲空字符串,第一次顯示的時候輸出的是SID的值,當你刷新的時候,因爲已經在cookie中存在,所以顯示的是一個空字符串。
session_id() 函數用來返回當前會話的session_id,它會去讀取cookie中的name,也就是PHPSESSID值。
session的相關配置
上面巴拉巴拉廢話說了那麼多,應該是可以理解session的一套機制了的,我接下來看看,前面零星的提到了php.ini裏面有關於session相關的配置。我們打開php.ini來,搜索session相關,我主要把用到的幾個給列出來:
session.save_handler = files
session.save_path = “d:/wamp/tmp”
session.use_cookies = 1
session.name = PHPSESSID
session.auto_start = 0
session.cookie_lifetime = 0
session.serialize_handler = php
session.gc_divisor = 1000
session.gc_probability = 1
session.gc_maxlifetime = 1440
主要我們用到的,常見的大概就是這幾個。我們一個一個的說。
session.save_handler = files 表示的是session的存儲方式,默認的是files文件的方式保存,sess_efdsw34534efsdfsfsf3r3wrresa, 保存在 session.save_path = “d:/wamp/tmp” 裏,所有這2個都是可配值得。我們上面的例子就是用的這種默認的方式。
save_handler 不僅僅只能用文件files,還可以用我們常見的memcache 和 redis 來保存。這個我們後面來說。
session.use_cookies 默認是1,表示會在瀏覽器裏創建值爲PHPSESSID的session_id,session.name = PHPSESSID 找個配置就是改這個名字的,你可以改成PHPSB, 那這樣就再瀏覽器裏生成名字爲PHPSB的session_id 。`(∩_∩ )′
session.auto_start = 0 用來是否需要自動開啓session,默認是不開啓的,所有我們需要在代碼中用到session_start();函數開啓,如果設置成1,那麼session_id 也會自動就生成了。
session.cookie_lifetime = 0 這個是設置在客戶端生成PHPSESSID這個cookie的過期時間,默認是0,也就是關閉瀏覽器就過期,下次訪問,會再次生成一個session_id。所以,如果想關閉瀏覽器會話後,希望session信息能夠保持的時間長一點,可以把這個值設置大一點,單位是秒。
gc_divisor, gc_probability, gc_maxlifetime 這3個也是配合一起使用,他們是幹嘛的呢?他們是幹大事情的,回收這些sess_xxxxx 的文件,它是按照這3個參數,組成的比率,來啓動GC刪除這些過期的sess文件。gc_maxlifetime是sess_xxx文件的過期時間。具體可以參考這個,我覺得他說我比我清楚:
session的垃圾回收機制
session的垃圾回收
我們通過上面的各種,已經清楚session的種種了,它會產生各種的sess_前綴的文件,久而久之就會形成垃圾數據,而且正常的session讀取也會造成壓力,所以及時的清理是蠻有用的。
1. 代碼處理
php代碼中有幾個函數是用來清理過期的session信息的,主要是這幾個:
unset($_SESSION[‘hello’]);
session_unset();
session_destroy();
setcookie(session_name(), ‘’, time()-42000, ‘/’);
unset 這是是常用的銷燬標量的方法,不多說,唯一要說的是刪除session ,就是將這個sess_xxx的文件的hello變量給刪除了,其他的變量該有的都保存着。而 session_unset() 這個不穿參數,這個是銷燬sess_xxx文件中的所有變量,但是這個sess_xxx文件還是保存着。而session_destroy 則更狠角了,它是直接將這個sess_xxx文件給刪掉。
一般退出操作裏面,我們也會將session_name() 獲得到的PHPSESSID也給過期掉,刪掉,因爲網頁沒關,不這樣刪除的話,刷新之後,找個值是存在的,服務器將會重新創建一個一模一樣session_id的sess文件。
2. php gc 自動刪除
php.ini中的幾個銷燬sess_xxx文件的配置,在上面說了:
session.gc_divisor = 1000
session.gc_probability = 1
session.gc_maxlifetime = 1440
簡單說下,其實上面的一個超鏈接的博客講的很清楚了,php觸發gc刪除過期的sess_x的文件的概念是這樣計算的:概率= gc_probability/gc_divisor,上面的默認的參數,也就是說概念是1/1000的概念,在頁面啓動session_start() 函數時候,會觸發gc刪除過期的sess_文件。這個概率其實是蠻小的
所以,我們可以將這個概念調整大一點,比如:將gc_probability 也調成1000,那gc_probability/gc_divisor 就等於1了,也就是百分一百會觸發。這樣就垃圾回收概率就大的多。
用redis存儲session
上面七七八八說了很多關於session的存儲啊機制啊等。現在說說如果用redis 存儲session。之前說的都是用文件files存儲,現在想用redis,好處有哪些?
更快的讀取和寫入速度。redis是直接操縱內存數據的,肯定是要比文件的形式快很多。 更好的設置好過期時間。文件存儲的sess_sdewfrsf文件其實被刪除掉還是要考運氣的和概率的,很有可能造成sess_文件沒即時刪除,造成存儲磁盤空間過多,和讀取SESSION就變慢了。 更好的分佈式同步。設置redis 的主從同步,可以快速的同步session到各臺web服務器,比文件存儲更加快速。 總的說來,用redis來存儲SESSION速度更快,性能更高。
要做的第一件事,當然就是安裝redis了。具體安裝和配置php與redis,就不細說了,可以參考我寫的redis相關:
redis安裝與配置
redis 安裝好了之後,接下來就是修改php.ini了。將原來的files 改成redis:
session.save_handler = redis
session.save_path = “tcp://127.0.0.1:6381”
需要用到tcp來連接redis,如果你設置reids 有密碼訪問的話,這樣加上就可以了:tcp://127.0.0.1:6381?auth=authpwd
重啓web服務器後,你就可以正常使用SESSION了。和之前使用files存儲SESSION一模一樣。
我們看下redis 是怎麼存儲session的。它是用了有別於文件存儲使用sess_前綴的名字,它用PHPREDIS_SESSION: 前綴,再加上session_id 的形式,是一個string 字符串類型,帶生存週期的。
PHPREDIS_SESSION:i9envsatpki9q8kic7m4k68os5
你會發現,它的值和文件存儲session一模一樣,都是用php序列化後存儲,而且有明確的過期時間,是根據配置:session.gc_maxlifetime = 1440 來設定的,默認1440秒。當然你可以修改成其他的。
我們寫入和讀取每頁還是一模一樣,包括刪除和情況,都是一模一樣,沒有什麼變化:
session_start(); //開啓session,如果讀不到cookie,會重新生成一個session_id,redis裏面也會新生成一個。
echo “SID: “.SID.”<br>”;
echo “session_id(): “.session_id().”<br>”;
echo "COOKIE: ".$_COOKIE[“PHPSESSID”];
$_SESSION[‘hello’] = 123; // 寫入session 。會序列化後寫入redis中
$_SESSION[‘word’] = 456;
var_dump(S E S S I O N [ ′ w o r d ′ ] ) ; / / 讀 s e s s i o n 。 會 從 r e d i s 讀 到 , 解 序 列 後 , 讀 出 這 個 值 。 r e d i s 1440 秒 過 期 後 , 將 讀 不 到 。 u n s e t ( _SESSION['word']); //讀session。會從redis讀到,解序列後,讀出這個值。redis 1440秒過期後,將讀不到。
unset( S E S S I O N [ ′ w o r d ′ ] ) ; / / 讀 s e s s i o n 。 會 從 r e d i s 讀 到 , 解 序 列 後 , 讀 出 這 個 值 。 r e d i s 1 4 4 0 秒 過 期 後 , 將 讀 不 到 。 u n s e t ( SESSION[‘hello’]); // 刪除 hello 的session 。會刪除 redis的hello值
session_unset(); // 清空redis 中這個session_id的所有值。
session_destroy(); // 刪除掉這個PHPREDIS_SESSION:i9envsatpki9q8kic7m4k68os5 key。
session同步
在做了web集羣后,你肯定會首先考慮session同步問題,因爲通過負載均衡後,同一個IP訪問同一個頁面會被分配到不同的服務器上,如果不同的服務器用的是不同的reidis服務,那麼可能就會出現,一個登錄用戶,一會是登錄狀態,一會又不是登錄狀態。所以session這個時候就要同步了。剛好,我們選擇用redis作爲了存儲,是可以在多臺redis 服務器中同步的。
具體可以搜索 reidis主從同步或者redis 集羣
參考資料:
http://zhidao.baidu.com/link?url=2_phukSt0xI6SSIVKUE37TxzivLqdCz_JCPhIUPLMB3TX_IWgoVKL2lwDn1Gh7xTykyV3ezU1YQv9s6HD3uhO
http://blog.sina.com.cn/s/blog_5f54f0be0100xs7e.html
http://star0708.blog.163.com/blog/static/181091248201341710100381/
http://baike.baidu.com/view/25258.htm?fr=aladdin
http://www.cnblogs.com/hongfei/archive/2012/06/17/2552434.html