這樣做會什麼有什麼區別嗎?我們直接看測試結果:
圖 1.2 使用session測試結果
不是每個請求只執行1s鐘嗎?為什麼ajax2.php消耗了2s,ajax3.php消耗3s?
二、WHO -- Session鎖
session鎖的官方定義:
Session data is usually stored after your script terminated without the need to call session_write_close(), but as session data is locked to prevent concurrent writes only one script may operate on a session at any time.
大致的意思是: 從調用session_start()開始,直到顯示調用session_write_close()或指令碼結束,session的資料都會被鎖起來,屆時同一個SESSIONID使用者的請求會被阻塞。
從圖1.2的測試結果看到,ajax2.php與ajax3.php分別多執行了1s和2s,很明顯session_start()操作造成了阻塞。
當index.php同時訪問ajax.php、ajax2.php、ajax3.php,ajax.php首先執行,此時ajax2.php與ajax3.php都在等在ajax.php釋放session鎖,都消耗1s。
當ajax.php執行完成,釋放了session鎖,ajax2.php與ajax3.php再次競爭session鎖,同理ajax3.php又等待了1s鐘。所以我們得到的結果:
ajax.php 消耗1s
ajax2.php 消耗2s
ajax3.php 消耗3s
三、WHEN -- 什麼時候會觸發Session鎖
在章節二中,session鎖定義為session_start()開始即會觸發session鎖,所以對於現有大部分php架構(使用原生php session的情況下)存在session鎖造成的使用者請求阻塞的問題。試想,有2個請求,其中請求A需要3s鐘才能返回結果,另外請求B僅需要10ms即能返回,前端同時請求這兩個介面,假如後台先處理了A請求,那麼B請求就要等待3s後再執行10ms才能返回結果。但是最優的情況是,同時發起請求,10ms後收到B請求返回,3s後收到A請求返回。
四、WHY -- Session內部執行機制
預設情況下php session採用檔案為服務端儲存介質。在php session模組的源碼中,有一個比較重要的結構體:
圖4.1 php session模組結構體 ps_module_struct
該結構體裡面的幾個函數指標分別對應了session操作的open、close、read、write、destory、gc回收等功能。看到這6個函數,是否想起了php的SessionHandlerInterface介面(版本>=php5.4)?
圖4.2 php SessionHandlerInterface介面
用這個介面配合session_set_save_handler可以重寫session的這幾個關鍵操作。於是我們可以使用以下代碼來探探php session在一個請求的生命週期中的運行順序:
圖4.3 session運行已排序的測試代碼
測試結果:
由測試結果可知一般的session執行順序,在session_start()調用時,php回去調用open和read操作,指令碼執行結束後(輸出了php script run),再會調用write和close操作。
現在我們不妨做一個大膽的猜想,php 在 session的open或者read操作時,開啟了session鎖,並write或close後釋放session鎖。這樣的猜想也符合我們在章節1的測試結果。
為了驗證我們的猜想,就需要去php的源碼探個究竟了。在php源碼檔案/ ext / session / mod_files.c中可以看到預設session的6個重要操作的部分實現。在156行(點擊開啟連結),看到open操作有一個開啟檔案操作:flock(data->fd, LOCK_EX);該操作以互斥鎖定的方式開啟檔案。在110行關掉檔案close(data->fd);。
看到這裡,我們應該得到的結論是:
在預設情況下,所謂的php session鎖其實就是檔案鎖
所以,當我們使用session_set_save_handler來自定session操作,改用memcache或其他介質時,只要我們在SessionHandlerInterface的介面中沒有鎖的邏輯,那麼session鎖自然也不會存在。作者私下也做了這樣的實驗,實踐證明也的確如此。
五、HOW -- 如何避免Session鎖帶來的阻塞現象
首先,session鎖不一定是壞事情,在一種情況下就非常好用,例如某介面對與同一個使用者的請求預設同一時刻只能執行一次。這種時候,就可以用seesion_start()和session_write_close()把要阻塞的代碼括起來。非常簡單暴力實用。
但是大部分時候我們還是要避免這種鎖的存在,解決方案:
1、在用完session的時候就馬上session_write_close()掉,釋放session鎖
2、採用沒有鎖的session操作,如章節4中所說的用session_set_save_handler來自訂一個沒有鎖的session操作。
3、再使用預設php session時,個人比較中意的一個方案:大部分情況下,我們對session的操作基本上都是讀操作,寫操作一般都比較少。這種時候,我們可以自己寫一個session類。
建構函式:將session讀入cache,關閉session鎖
寫操作:開啟session鎖,寫入值,關閉session鎖
讀操作:直接讀cache
部分代碼如下:
//將session讀入全域變數$_SEESIONstatic private function init(){if(self::$not_init){ session_start(); session_write_close(); self::$not_init = false;}}
//讀sessionstatic public function get($name){self::init();return $_SESSION[$name];}
//寫sessionstatic public function set($name, $val){session_start();$_SESSION[$name] = $val;session_write_close();}
注意:如果是寫操作頻繁的操作,就不適合使用該方法。