简单理解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,類似于這樣:4c83638b3b0dbf65583181c2f89168ecsession的全部機制也是基于這個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);font?family:Arial;font?size:14px;"><spanstyle="color:rgb(51,51,51);font?family:arial,′宋體′,sans?serif;line?height:24px;text?indent:28px;"><br></span></div><divstyle="color:rgb(69,69,69);font?family:Arial;font?size:14px;"><spanstyle="color:rgb(51,51,51);font?family:arial,′宋體′,sans?serif;line?height:24px;text?indent:28px;"><imgsrc="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><divstyle="color:rgb(69,69,69);font?family:Arial;font?size:14px;">我們看到居然還有一個警告。我們先一個一個的看。首先<spanstyle="color:rgb(199,37,78);font?family:Menlo,Monaco,Consolas,′CourierNew′,monospace;line?height:26px;">SID</span>這個常量,我們沒有給它賦值,它居然能有輸出,其次<spanstyle="color:rgb(199,37,78);font?family:Menlo,Monaco,Consolas,′CourierNew′,monospace;line?height: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。最后C?OOKIE["PHPSESSID"];</span></p>我們訪問一下a.php頁面,看能輸出什么?</div><divstyle="color:rgb(69,69,69);font?family:Arial;font?size:14px;"><spanstyle="color:rgb(51,51,51);font?family:arial,′宋體′,sans?serif;line?height:24px;text?indent:28px;"><br></span></div><divstyle="color:rgb(69,69,69);font?family:Arial;font?size:14px;"><spanstyle="color:rgb(51,51,51);font?family:arial,′宋體′,sans?serif;line?height:24px;text?indent:28px;"><imgsrc="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><divstyle="color:rgb(69,69,69);font?family:Arial;font?size:14px;">我們看到居然還有一個警告。我們先一個一個的看。首先<spanstyle="color:rgb(199,37,78);font?family:Menlo,Monaco,Consolas,′CourierNew′,monospace;line?height:26px;">SID</span>這個常量,我們沒有給它賦值,它居然能有輸出,其次<spanstyle="color:rgb(199,37,78);font?family:Menlo,Monaco,Consolas,′CourierNew′,monospace;line?height:26px;">sessioni?d()</span>這個系統方法是輸出本次生成的sessioni?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 “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,好處有哪些?
要做的第一件事,當然就是安裝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′]);//讀session。會從redis讀到,解序列后,讀出這個值。redis1440秒過期后,將讀不到。unset(_SESSION['word']); //讀session。會從redis讀到,解序列后,讀出這個值。redis 1440秒過期后,將讀不到。 unset(S?ESSION[′word′]);//讀session。會從redis讀到,解序列后,讀出這個值。redis1440秒過期后,將讀不到。unset(SESSION[‘hello’]); // 刪除 hello 的session 。會刪除 redis的hello值
session_unset(); // 清空redis 中這個session_id的所有值。
session_destroy(); // 刪除掉這個PHPREDIS_SESSION:i9envsatpki9q8kic7m4k68os5 key。
在做了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
總結
以上是生活随笔為你收集整理的简单理解session的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 分布领域驱动设计(DDD):领域接口化设
- 下一篇: 为什么要使用NoSQL