老舊話題:從新看看當年感受很難的session

原文地址:https://t.ti-node.com/thread/...

這基本上算是個老舊的話題了,幾乎全部phper在第一次面試的時候都會被問到關於session的問題,若是不出意外,每每是以下三板斧:php

  1. php的session是什麼東西
  2. php的session存在什麼地方、時候過時
  3. php的session和cookie有什麼區別

這三個問題堪稱是關於php session三大基礎問題了,要是掌握很差,直接致使面試掛掉,使人唏噓不已。node

就以上三個問題簡單回顧一下:mysql

  1. session翻譯成中文,大抵就是會話。咱們知道http協議是一個無狀態的協議,用極爲粗暴的土話表示無狀態就是說「你壓根不知道這個http請求是哪一個犢子發起的」或者「你永遠不知道這個http請求的那頭究竟是上次那個哈士奇仍是上上次那個胖子」,因此爲了解決這個問題,php引入session來額外標記「到底誰發起的這個http請求」。在php中,php會爲每一個不一樣的用戶生成一個隨機的session id,每一個人擁有的session id都是不一樣的。用戶在與服務器產生的每一次交互中,都是利用session id來辨別的用戶。讓php產生session是一件很容易的事情,直接調用session_start()函數就能夠了,以下圖就是產生的session文件:


其中sess是文件前綴,後面的「njjf8l3lh**ff6」就是你的session id了。可是如今session文件內容是空的,若是咱們用以下代碼,就能夠產生session文件的內容:linux

<?php
// 開啓session
session_start();
$_SESSION['hello'] = 'world';

刷新一下網頁,而後在再次查看原來的session文件,其中就會有以下內容:

就是說,你往全局變量$_SESSION保存的內容本質上都是PHP用文本形式給你存儲到服務器上了。服務器根據你的session id讀取相應的session文件而後把其中內容讀出來,你就會獲得你的$_SESSION數據。nginx

  1. php的session默認是以文件的形式存儲的系統磁盤中,在運行於ubuntu 16.04系統的php 7.0.28中,session是存儲於/var/lib/php/sessions文件夾下,能夠經過php.ini配置文件中的session.save_path來查看肯定。php的過時時間是由php配置項session.gc_maxlifetime來肯定的,值的單位是秒鐘,默認是1440,也就是說當這個session文件具體上次修改時間超過了1440秒後這個session文件就算是過時了。值得注意的是,過時了不表明這個session文件會立刻被垃圾回收機制刪除掉,仍是有可能會殘存一段時間的。那麼,究竟什麼時候會被刪除?這取決於session.gc_probability和session.gc_divisor兩個配置項了。這兩個選項的比值 ( session.gc_probability / session.gc_divisor ) 就是觸發垃圾回收機制的機率,好比 ( 1 / 100 ) 就能夠簡單粗暴的理解爲「每產生100個請求,就有1次會觸發php垃圾回收機制去刪除過時的session文件」,因此你記住了:在php中若是你想要一個精確過時的session文件,最起碼默認的session配置是絕對不可能的。話說回來,還不都是由於php並無啓動一個單獨的線程或者進程去掃描垃圾,因此,也只能用這個「機率」這種粗暴的方式來解決這個問題,又不是不能用
  2. 開篇說了,爲了搞明白「究竟是哈士奇仍是胖子」的問題,不得不引入額外標記數據才行,因此實際上,先有的cookie而有後的session,都是爲了解決這個問題而產生的。兩者的恩怨情仇在於:web

    • cookie存在於客戶端,而session存在於服務器端,因此session相對更安全
    • 服務器能夠讀取session和cookie,可是客戶端(也就是瀏覽器)只能讀取cookie
    • 默認狀況下,session是離不開cookie的,說到底「安全的session使用方式」必須依靠cookie才能混口飯吃。由於session id就是依靠cookie保存起來的,客戶端瀏覽器每次發送請求都會攜帶上該cookie,該cookie默認名稱是PHPSESSID,數值就是session id。
    • 若是說就是用不了cookie,那麼session也並非真的不能用。禁用cookie的狀況下,session能夠經過配置利用URL來傳遞,也就是query string直接暴露在網址中,很是不安全很是嚇人,嚴重不推薦這種方式,甚至應該直接禁用!
    • cookie大小稍微有限制(聽說考慮用localstorage代替?),session相對寬鬆

大概就這些,再也不贅述,我是建議你們配合php.ini文件去研究上面三個問題。面試

若是說真的只回顧一下這三個問題,那豈不是真的應了「一看標題猛如虎,打開內容1-5」?我說過了的,我這裏是個正經的博客網站,是個真正的有些內涵的php文化網站,不能只講些個初級的內容,是個話題都都要不管如何強塞點兒看起來高端的玩意進去撐場面。redis

剛叨叨過了,默認配置下session是以文本文件形保存在服務器的某個文件夾中的,有心的人應該知道「一個目錄中文件過可能是會下降讀取效率的」,因此,在用一些PC軟件的時候能夠看到這些軟件會把TA須要的數據分散開來到不一樣的次級目錄中去。php的session文件也能夠這麼幹,整體來講是比較簡單粗暴的。咱們須要關注下兩個php配置項:sql

  • 一個是session.save_handler,默認這貨的值是「files」,也就是文件
  • 一個是session.save_path,默認這貨的值是一個目錄路徑,好比/var/lib/php/session。如今咱們將這個值改爲相似於session.save_path = "N;/path"這樣的,其中N是一個正整數,這個數值的含義就是指將目錄分紅幾個層次,好比咱們修改爲session.save_path = 「2;/var/lib/php/sessions」,而後重啓一下apache或者fpm進程管理器,而後執行以下代碼:
<?php
echo "let rock session";
session_start();

刷新網頁,以下圖所示:

錯誤緣由相比你們看到了,大概意思就是說「/var/lib/php/sessions/n/j/」這個文件夾不存在,那麼切換到這個目錄下看看,以下圖:

果真是空的,也就說沒有/n/j這個子目錄,看來得手工建立了。然而,真的不能去手工建立,由於你哪兒知道文件夾的名字是啥?回到配置文件一頓研究,在session.save_path配置項附近發現以下英文字樣:apache

; NOTE 1: PHP will not create this directory structure automatically.
;         You can use the script in the ext/session dir for that purpose.
; NOTE 2: See the section on garbage collection below if you choose to
;         use subdirectories for session storage

英文比較蹩腳昂,大概翻譯一下,多包涵:

; NOTE 1: PHP壓根不會幫你建立這些文件夾,您本身個兒下載php源碼包,到ext目錄的session目中去找那個腳本去建立
; NOTE 2: 若是你要用子目錄存儲session的話,記得看下垃圾回收,不看就有坑。(坑在這裏直接告訴你們吧,大概就是說你要本身搞子目錄存session,那我那個靠信仰和機率才能觸發的垃圾回收機制就壓根就不觸發了,你本身想辦法搞定你的過時session,我無論了)

因此呢,咱們下載一個php源碼包,最好是和你運行環境版本同樣的php源碼包並解壓,命令行切到ext/session目錄下,以下圖:

看到那個mod_files.sh沒?Linux下就這腳本。mod_files.bat就是給windows用的。給這個腳本chmod +x mod_files.sh加個執行權限,而後查看下使用方式:

爲了幫助眼近視的讀者,友情翻譯一下使用方式:

./mod_files.sh 'session文件根目錄' 目錄深度 哈希函數比特量
對應個人php開發環境就是:
./mod_files.sh /var/lib/php/sessions/ 2 5
其中第一項就是你存儲session的根目錄,第二項就是那個N,第三項查看session.hash_bits_per_character配置項

而後執行,以下圖所示:

此時到/var/lib/php/sessions中查看下,果真有目錄了,那麼,再次刷新網頁,本覺得很順利的你可能依然會遇到錯誤,以下:

session_start(): open(/var/lib/php/sessions/n/j/sess_njjf8l3lhfrpq8nrlnl1d9qff6, O_RDWR) failed: Permission denied (13)

模模糊糊認得Permission denied這幾個字母,好像是權限的問題,難道是由於當前apache進程用戶或者fpm進程用戶沒有權限往這些目錄寫數據嗎?改下這些目錄的擁有者撒,改爲www-data(我係統中fpm的運行用戶),再試試,果真好了!

總有刁民覺得這就能夠解決很大的問題了,然而很悲劇的是:並非。當前這個方案必定程度能夠解決session文件過多的問題,可是依然有兩個問題沒有獲得解決:

  1. 依然是文件存儲,若是訪問量太大的話,session文件從硬盤的讀取IO或許會成爲程序的瓶頸,固然SSD速度必定會好不少
  2. 若是網站分別部署到了兩臺服務器上,session沒法共享,出現故障。什麼意思呢?就是爲了保證高可用,網站程序分別在A服務器上和B服務器上,而後最外面使用一臺nginx擋在最前面作負載均衡,路人甲的某次http請求可能會被分配到A上,也可能會被分配到B上。路人甲在A上產生的session文件會被保存到A服務器硬盤上,可是服務器B上卻沒有,若是該用戶請求被打到B上的時候,很不幸,session丟失了,一些數據也就會丟失,路人甲八成會罵娘罵客服。也就說,A服務器和B服務器須要共享同一套session!

藉此,就引入一個問題,就是分佈式web部署中,如何解決session共享的問題!

關子我就不賣了,沒意思,首先想到的是redis,爲A和B提供一臺C redis服務器就能夠了,這樣能夠「 多快好省 」地一舉解決問題!按照預想,引入redis後能夠順利解決三個問題:

  • 內存級的讀寫速度,唰唰唰!
  • session輕鬆easy實現了共享,哪怕之後業務服務器繼續橫向擴展到服務器D
  • session的過時終於能夠精確到秒了,說沒就沒,不用再靠信仰和機率了

將session存入redis須要修正以下兩處php配置,首先設置session.save_handler = redis,其次是設置ession.save_path = "tcp://127.0.0.1:6379",而後重啓apache或者fpm,刷新一下網頁,若是網頁不報什麼錯誤,理論上session數據就已經到redis中去了,鏈接redis查看下key,以下圖:

從上至下我一共執行了五次redis命令,分別表示:

  1. 查看全部keys從而獲取php分配給個人session文件名稱
  2. 獲取這個key的剩餘時間,過時後redis該key算失效了
  3. 查看這個key的數據類型,能夠看出是string類型
  4. 使用get直接獲取string的值
  5. 過了一段時間,我又刷新了一下網頁,而後再次用ttl看該key的剩餘時間,再次被延長到1440秒了

除了redis外(memcache我就不舉例了,和redis相似),還有一種方案就是經過nfs共享來實現,大概原理就是弄一臺服務器,經過內網共享對全部php業務服務器開放讀寫,你們知道linux下磁盤是能夠掛載在某個文件夾下的,因此將nfs掛載到各個php業務服務器的某個目錄下,而後按照上述文章修改響應配置就能夠了。這個,我也沒有嘗試過也懶得本身去嘗試了,因此偷個懶直接給你們拋個鏈接吧,是老葉博客上的一篇文章,《iMySQL | 老葉茶館,PHP實現多服務器session共享之NFS共享》

裝逼完畢,若有問題,火速留言指正!

相關文章
相關標籤/搜索