簡單理解session

前言:

今天就來徹底的學一些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 頁面中,輸入如下代碼:

session_start();

echo “SID: “.SID.”<br>”;

echo “session_id(): “.session_id().”<br>”;

echo "COOKIE: ".COOKIE["PHPSESSID"];</span></p>a.php</div><divstyle="color:rgb(69,69,69);fontfamily:Arial;fontsize:14px;"><spanstyle="color:rgb(51,51,51);fontfamily:arial,,sansserif;lineheight:24px;textindent:28px;"><br></span></div><divstyle="color:rgb(69,69,69);fontfamily:Arial;fontsize:14px;"><spanstyle="color:rgb(51,51,51);fontfamily:arial,,sansserif;lineheight:24px;textindent:28px;"><imgsrc="https://imgblog.csdn.net/20141015104513437?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdGhpbmsybWU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast"alt=""style="border:none;"><br></span></div><divstyle="color:rgb(69,69,69);fontfamily:Arial;fontsize:14px;"><spanstyle="color:rgb(199,37,78);fontfamily:Menlo,Monaco,Consolas,CourierNew,monospace;lineheight:26px;">SID</span><spanstyle="color:rgb(199,37,78);fontfamily:Menlo,Monaco,Consolas,CourierNew,monospace;lineheight:26px;">sessionid()</span>sessionid_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。最後_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 “SID: “.SID.”<br>”;

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]

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,好處有哪些?
  1. 更快的讀取和寫入速度。redis是直接操縱內存數據的,肯定是要比文件的形式快很多。
  2. 更好的設置好過期時間。文件存儲的sess_sdewfrsf文件其實被刪除掉還是要考運氣的和概率的,很有可能造成sess_文件沒即時刪除,造成存儲磁盤空間過多,和讀取SESSION就變慢了。
  3. 更好的分佈式同步。設置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(SESSION[word]);//sessionredisredis1440unset(_SESSION['word']); //讀session。會從redis讀到,解序列後,讀出這個值。redis 1440秒過期後,將讀不到。 unset(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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章