摘要:主要被用于保存某個(gè)訪問者的數(shù)據(jù)。服務(wù)器給訪問者唯一的鑰匙,這個(gè)鑰匙被稱作。例如官方給出的方案對(duì)于大量使用或者并發(fā)請(qǐng)求的網(wǎng)站而言,這可能是一個(gè)嚴(yán)重的問題。例如意味著在每個(gè)請(qǐng)求中有的概率啟動(dòng)進(jìn)程。因此對(duì)于不能跟蹤的文件系統(tǒng)也沒問題了。
什么是 Session
在 web 應(yīng)用開發(fā)中,Session 被稱為會(huì)話。主要被用于保存某個(gè)訪問者的數(shù)據(jù)。
由于 HTTP 無狀態(tài)的特點(diǎn),服務(wù)端是不會(huì)記住客戶端的,對(duì)服務(wù)端來說,每一個(gè)請(qǐng)求都是全新的。
既然如此,那么服務(wù)端怎么知道是哪個(gè)訪問者在請(qǐng)求它呢?又如何將不同的數(shù)據(jù)對(duì)應(yīng)上正確的訪問者?答案是,給訪問者一個(gè)唯一獲取 Session 中數(shù)據(jù)的身份標(biāo)示。
打個(gè)比方:當(dāng)我們?nèi)コ匈徫飼r(shí),被保安告之我們是不能帶物品進(jìn)去的,必須將物品寄放在超市的儲(chǔ)物箱中。我們把物品交給了他,他怎么知道這些物品誰是誰的,于是他給了我們不同的鑰匙。當(dāng)我們要取走我們的物品時(shí),用唯一的鑰匙打開對(duì)應(yīng)的箱子即可。
就如同上面的比方一樣,可以將 Session 理解為存放我們數(shù)據(jù)的“箱子”,當(dāng)然,這些“箱子”都在服務(wù)端那。服務(wù)器給訪問者唯一的“鑰匙”,這個(gè)“鑰匙”被稱作 session_id。訪問者憑借自己的 session_id,就能獲取到自己存在服務(wù)器端的數(shù)據(jù)。
session_id 通過兩種方式傳給訪問者(客戶端):URL 或 cookie。詳情參見:傳送會(huì)話ID
Session 和 Cookie 有什么關(guān)系Cookie 也是由于 HTTP 無狀態(tài)的特點(diǎn)而產(chǎn)生的技術(shù)。也被用于保存訪問者的身份標(biāo)示和一些數(shù)據(jù)。每次客戶端發(fā)起 HTTP 請(qǐng)求時(shí),會(huì)將 Cookie 數(shù)據(jù)加到 HTTP header 中,提交給服務(wù)端。這樣服務(wù)端就可以根據(jù) Cookie 的內(nèi)容知道訪問者的信息了。
可以說,Session 和 Cookie 做著相似的事情,只是 Session 是將數(shù)據(jù)保存在服務(wù)端,通過客戶端提交來的 session_id 來獲取對(duì)應(yīng)的數(shù)據(jù);而 Cookie 是將數(shù)據(jù)保存在客戶端,每次發(fā)起請(qǐng)求時(shí)將數(shù)據(jù)提交給服務(wù)端的。
上面提到,session_id 可以通過 URL 或 cookie 來傳遞,由于 URL 的方式比 cookie 的方式更加不安全且使用不方便,所以一般是采用 cookie 來傳遞 session_id。
服務(wù)端生成 session_id,通過 HTTP 報(bào)文發(fā)送給客戶端(比如瀏覽器),客戶端收到后按指示創(chuàng)建保存著 session_id 的 cookie。cookie 是以 key/value 形式保存的,看上去大概就這個(gè)樣子的:PHPSESSID=e4tqo2ajfbqqia9prm8t83b1f2。在 PHP 中,保存 session_id 的 cookie 名稱默認(rèn)叫作 PHPSESSID,這個(gè)名稱可以通過 php.ini 中 session.name 來修改,也可以通過函數(shù) session_name() 來修改。
在 PHP 中,默認(rèn)的 Session 處理器是 files,處理器可以用戶自己實(shí)現(xiàn)(參見:自定義會(huì)話管理器)。我知道的成熟的 Session 處理器還有很多:Redis、Memcached、MongoDB……
為什么不推薦使用 PHP 自帶的 files 類型處理器,PHP 官方手冊(cè)中給出過這樣一段 Note:
無論是通過調(diào)用函數(shù) session_start() 手動(dòng)開啟會(huì)話, 還是使用配置項(xiàng) session.auto_start 自動(dòng)開啟會(huì)話, 對(duì)于基于文件的會(huì)話數(shù)據(jù)保存(PHP 的默認(rèn)行為)而言, 在會(huì)話開始的時(shí)候都會(huì)給會(huì)話數(shù)據(jù)文件加鎖, 直到 PHP 腳本執(zhí)行完畢或者顯式調(diào)用 session_write_close() 來保存會(huì)話數(shù)據(jù)。 在此期間,其他腳本不可以訪問同一個(gè)會(huì)話數(shù)據(jù)文件。
上述引用參見:Session 的基本用法
為了證明這段話,我們創(chuàng)建一下 2 個(gè)文件:
文件:session1.php
php
文件:session2.php
php
在同一個(gè)瀏覽器中,先訪問 http://127.0.0.1/session1.php,然后在當(dāng)前瀏覽器新的標(biāo)簽頁立刻訪問 http://127.0.0.1/session2.php。實(shí)驗(yàn)發(fā)現(xiàn),session1.php 等了 5 秒鐘才有輸出,而 session2.php 也等到了將近 5 秒才有輸出。而多帶帶訪問 session2.php 是秒開的。在一個(gè)瀏覽器中訪問 session1.php,然后立刻在另外一個(gè)瀏覽器中訪問 session2.php。結(jié)果是 session1.php 等待 5 秒鐘有輸出,而 session2.php 是秒開的。
分析一下造成這個(gè)現(xiàn)象的原因:上面例子中,默認(rèn)使用 Cookie 來傳遞 session_id,而且 Cookie 的作用域是相同。這樣,在同一個(gè)瀏覽器中訪問這 2 個(gè)地址,提交給服務(wù)器的 session_id 就是相同的(這樣才能標(biāo)記訪問者,這是我們期望的效果)。當(dāng)訪問 session1.php 時(shí),PHP 根據(jù)提交的 session_id,在服務(wù)器保存 Session 文件的路徑(默認(rèn)為 /tmp,通過 php.ini 中的 session.save_path 或者函數(shù) session_save_path() 來修改)中找到了對(duì)應(yīng)的 Session 文件,并對(duì)其加鎖。如果不顯式調(diào)用 session_write_close(),那么直到當(dāng)前 PHP 腳本執(zhí)行完畢才會(huì)釋放文件鎖。如果在腳本中有比較耗時(shí)的操作(比如例子中的 sleep(5)),那么另一個(gè)持有相同 session_id 的請(qǐng)求由于文件被鎖,所以只能被迫等待,于是就發(fā)生了請(qǐng)求阻塞的情況。
既然如此,在使用完 Session 后,立刻顯示調(diào)用 session_write_close() 是不是就解決問題了哩?比如上面例子中,在 sleep(5) 前面調(diào)用 session_write_close()。
確實(shí),這樣 session2.php 就不會(huì)被 session1.php 所阻塞。但是,顯示調(diào)用了 session_write_close() 就意味著將數(shù)據(jù)寫到文件中并結(jié)束當(dāng)前會(huì)話。那么,在后面代碼中要使用 Session 時(shí),必須重新調(diào)用 session_start()。
例如:
php
官方給出的方案:
對(duì)于大量使用 Ajax 或者并發(fā)請(qǐng)求的網(wǎng)站而言,這可能是一個(gè)嚴(yán)重的問題。 解決這個(gè)問題最簡單的做法是如果修改了會(huì)話中的變量, 那么應(yīng)該盡快調(diào)用 session_write_close() 來保存會(huì)話數(shù)據(jù)并釋放文件鎖。 還有一種選擇就是使用支持并發(fā)操作的會(huì)話保存管理器來替代文件會(huì)話保存管理器。
我推薦的方式是使用 Redis 作為 Session 的處理器。
拓展閱讀:
為什么不能用 memcached 存儲(chǔ) Session
如何使用 Redis 作為 PHP Session handler
Session 數(shù)據(jù)是什么時(shí)候被刪除的這是一道經(jīng)常被面試官問起的問題。
先看看官方手冊(cè)中的說明:
session.gc_maxlifetime 指定過了多少秒之后數(shù)據(jù)就會(huì)被視為"垃圾"并被清除。 垃圾搜集可能會(huì)在 session 啟動(dòng)的時(shí)候開始( 取決于 session.gc_probability 和 session.gc_divisor)。 session.gc_probability 與 session.gc_divisor 合起來用來管理 gc(garbage collection 垃圾回收)進(jìn)程啟動(dòng)的概率。此概率用 gc_probability/gc_divisor 計(jì)算得來。例如 1/100 意味著在每個(gè)請(qǐng)求中有 1% 的概率啟動(dòng) gc 進(jìn)程。session.gc_probability 默認(rèn)為 1,session.gc_divisor 默認(rèn)為 100。
繼續(xù)用我上面那個(gè)不太恰當(dāng)?shù)谋确桨桑喝绻覀儼盐锲贩旁诔械膬?chǔ)物箱中而不取走,過了很久(比如一個(gè)月),那么保安就要清理這些儲(chǔ)物箱中的物品了。當(dāng)然并不是超過期限了保安就一定會(huì)來清理,也許他懶,又或者他壓根就沒有想起來這件事情。
再看看兩段手冊(cè)的引用:
如果使用默認(rèn)的基于文件的會(huì)話處理器,則文件系統(tǒng)必須保持跟蹤訪問時(shí)間(atime)。Windows FAT 文件系統(tǒng)不行,因此如果必須使用 FAT 文件系統(tǒng)或者其他不能跟蹤 atime 的文件系統(tǒng),那就不得不想別的辦法來處理會(huì)話數(shù)據(jù)的垃圾回收。自 PHP 4.2.3 起用 mtime(修改時(shí)間)來代替了 atime。因此對(duì)于不能跟蹤 atime 的文件系統(tǒng)也沒問題了。
GC 的運(yùn)行時(shí)機(jī)并不是精準(zhǔn)的,帶有一定的或然性,所以這個(gè)設(shè)置項(xiàng)并不能確保舊的會(huì)話數(shù)據(jù)被刪除。某些會(huì)話存儲(chǔ)處理模塊不使用此設(shè)置項(xiàng)。
對(duì)于這種刪除機(jī)制,我是存疑的。
比如 gc_probability/gc_divisor 設(shè)置得比較大,或者網(wǎng)站的請(qǐng)求量比較大,那么 GC 進(jìn)程啟動(dòng)就會(huì)比較頻繁。
還有,GC 進(jìn)程啟動(dòng)后都需要遍歷 Session 文件列表,對(duì)比文件的修改時(shí)間和服務(wù)端的當(dāng)前時(shí)間,判斷文件是否過期而決定是否刪除文件。
這也是我覺得不應(yīng)該使用 PHP 自帶的 files 型 Session 處理器的原因。而 Redis 或 Memcached 天生就支持 key/value 過期機(jī)制的,用于作為會(huì)話處理器很合適?;蛘咦约簩?shí)現(xiàn)一個(gè)基于文件的處理器,當(dāng)根據(jù) session_id 獲取對(duì)應(yīng)的單個(gè) Session 文件時(shí)判斷文件是否過期。
session.cookie_lifetime 以秒數(shù)指定了發(fā)送到瀏覽器的 cookie 的生命周期。值為 0 表示"直到關(guān)閉瀏覽器"。默認(rèn)為 0。
其實(shí),并不是 Session 數(shù)據(jù)被刪除(也有可能是,概率比較小,參見上一節(jié))。只是關(guān)閉瀏覽器時(shí),保存 session_id 的 Cookie 沒有了。也就是你弄丟了打開超市儲(chǔ)物箱的鑰匙(session_id)。
同理,瀏覽器 Cookie 被手動(dòng)清除或者其他軟件清除也會(huì)造成這個(gè)結(jié)果。
為什么瀏覽器開著,我很久沒有操作就被登出了這個(gè)是稱為“防呆”,為了保護(hù)用戶賬戶安全的。
這個(gè)小節(jié)放進(jìn)來,是因?yàn)檫@個(gè)功能的實(shí)現(xiàn)可能和 Session 的刪除機(jī)制有關(guān)(之所以說是可能,是因?yàn)檫@個(gè)功能不一定要借住 Session 實(shí)現(xiàn),用 Cookie 也同樣可以實(shí)現(xiàn))。
說簡單一點(diǎn),就是長時(shí)間沒有操作,服務(wù)端的 Session 文件過期被刪除了。
在我試驗(yàn)的過程中,發(fā)現(xiàn)了小有意思的事情:我把 GC 啟動(dòng)的概率設(shè)置為 100%。如果只有一個(gè)訪問者請(qǐng)求,該訪問者即使過了很久(超過了過期時(shí)間)后才發(fā)起第二次請(qǐng)求,那么 Session 數(shù)據(jù)也還是存在的("session.save_path" 目錄下面的 Session 文件存在)。是的,明明就超過了過期時(shí)間,卻沒有被 GC 刪除。這時(shí),我用另外一個(gè)瀏覽器訪問時(shí)(相對(duì)于另一個(gè)訪問者),這次請(qǐng)求生成了新的 Session 文件,而上一個(gè)瀏覽器請(qǐng)求生成的那個(gè) Session 文件終于沒有了(之前那個(gè) Session 文件在 "session.save_path" 目錄下面的消失了)。
還有,發(fā)現(xiàn) Session 文件被刪除后,再次請(qǐng)求,還是會(huì)生成和之前文件名相同的 Session 文件(因?yàn)闉g覽器并沒有關(guān)閉,再次請(qǐng)求發(fā)送的 session_id 是相同的,所以重新生成的 Session 文件的文件名還是一樣的)。但是,我不理解的是:這個(gè)重新出現(xiàn)的文件的創(chuàng)建時(shí)間竟然是第一次的那個(gè)創(chuàng)建時(shí)間,難道它是從回收站中回來的?(確實(shí),我做這個(gè)試驗(yàn)時(shí)是在 window 下進(jìn)行的)
我猜測(cè)的原因是這樣:當(dāng)啟動(dòng)會(huì)話后,PHP 根據(jù) session_id 找到并打開了對(duì)應(yīng)的 Session 文件,然后才啟動(dòng) GC 進(jìn)程。GC 進(jìn)程就只檢查除了當(dāng)前這個(gè) Session 文件外的其他文件,發(fā)現(xiàn)過期的就干掉。所有,即使當(dāng)前這個(gè) Session 文件已經(jīng)過期了,GC 也沒有刪除它。
我認(rèn)為這個(gè)不合理的。
由于發(fā)生這種情況影響也不大(畢竟線上請(qǐng)求很多,當(dāng)前請(qǐng)求的過期文件被其他請(qǐng)求喚起的 GC 干掉的可能性是比較大的) + 我沒有信心去看 PHP 源代碼 + 我并不在線上使用 PHP 自帶的 files 型 Session 處理器。所以,這個(gè)問題我就沒有深入研究了。請(qǐng)諒解。
php
拓展閱讀:
如何設(shè)置一個(gè)嚴(yán)格 30 分鐘過期的 Session
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/21037.html
摘要:昨天下午糾結(jié),想做一個(gè)秒就自動(dòng)清除的會(huì)話。注下環(huán)境首先自定義關(guān)于的三個(gè)簡單文件讀寫回收寫入讀取和觸發(fā)按照官方說必須觸發(fā)才能開始回收那么問題來了,用瀏覽器一次訪問,,,然后秒中之后,再次訪問。會(huì)話結(jié)束時(shí)調(diào)用此外,會(huì)不時(shí)地調(diào)用回調(diào)函數(shù)。 昨天下午糾結(jié),想做一個(gè)10秒就自動(dòng)清除的會(huì)話。雖然后來采用了session+timestamp的方法,但是gc的回收機(jī)制卻困擾了我。 根據(jù)說明,sessi...
摘要:在中,會(huì)為每個(gè)不同的用戶生成一個(gè)隨機(jī)的,每個(gè)人擁有的都是不同的。值得注意的是,過期了不代表這個(gè)文件會(huì)馬上被垃圾回收機(jī)制刪除掉,還是有可能會(huì)殘存一段時(shí)間的。 原文地址:https://t.ti-node.com/thread/... 這基本上算是個(gè)老舊的話題了,幾乎所有phper在第一次面試的時(shí)候都會(huì)被問到關(guān)于session的問題,如果不出意外,往往是如下三板斧: php的sessi...
摘要:一前言夜晚十點(diǎn)鐘的時(shí)候源自拉群狂魔老板的問候今天下班前我自己都還登陸過的,咋突然間就不行了捏懷著郁悶的心情,首先看了下為什么不能登陸,打開了看了下錯(cuò)誤再接著看到控制臺(tái)打印的信息二問題追蹤因?yàn)橄瓤吹降氖羌t色的報(bào)錯(cuò),所以第一反應(yīng)是先檢查了。 一、前言 夜晚十點(diǎn)鐘的時(shí)候源自拉群狂魔老板的問候: showImg(https://segmentfault.com/img/bVbpqLI?w=59...
閱讀 3837·2021-11-17 09:33
閱讀 2049·2021-10-26 09:51
閱讀 1573·2021-09-29 09:44
閱讀 1715·2019-08-30 15:55
閱讀 1473·2019-08-30 15:52
閱讀 2355·2019-08-30 15:43
閱讀 3459·2019-08-29 17:00
閱讀 2328·2019-08-29 16:23