最近在一個基於 Web 的 IM 項目中,我採用異步向服務器發起請求拉取最新的聊天內容,服務器端經過 PHP 處理拉取請求,拉取過程是用 10 次循環查詢數據庫是否有最新的聊天內容。如發現新內容,則當即向瀏覽器輸出,並結束掉本次請求的進程。在這 10 次的循環中,每次查詢數據庫後,均經過 Sleep 函數讓進程暫停 1 秒,那麼這個 PHP 進程可能會在服務器端保持 10 秒。php
在測試過程當中,我發現當這個拉取請求運行期間,其餘向服務器端 PHP 發起的請求,均受到影響,響應變的很是慢。數據庫
通過一系列的排查,問題始終得不到解決,但當把代碼中涉及到 SESSION 的部分所有跳過期,狀況發生了變化,全部 PHP 進程都恢復正常的響應速度了。由此,聯想到問題可能出在了 SESSION 阻塞機制上了。瀏覽器
關於 PHP 的 SESSION 阻塞機制,咱們要先了解其工做狀態,先看以下代碼:服務器
<?php // 第 1 次打印 SESSION 狀態 echo 'Status(1):' . session_status() . '<br>'; // 1 // 開啓 SESSION session_start(); // 第 2 次打印 SESSION 狀態 echo 'Status(2):' . session_status() . '<br>'; // 2 $_SESSION['test'] = 'hello world'; // 等價於 write and close session_commit(); echo $_SESSION['test'] . '<br>'; // 第 3 次打印 SESSION 狀態 echo 'Status(3):' . session_status() . '<br>'; // 1 ?>
上述代碼輸出的結果以下:session
Status(1):1
Status(2):2
hello world
Status(3):1異步
經過 session_status() 這個函數能夠獲得 SESSION 的狀態,返回值含義以下:
0 - 會話是被禁用的。
1 - 會話是啓用的,但不存在當前會話。
2 - 會話是啓用的,並且存在當前會話。函數
當上邊的代碼中第一次經過 session_status() 函數獲取 SESSION 狀態時,返回值爲1,表明當前 SESSION 功能是可用的,但尚未處於激活狀態的會話。測試
用咱們很是熟悉的 session_start() 函數開啓會話後,再次用 session_status() 函數獲取狀態,發現返回值已經變爲2,這說明當前已經有了激活狀態的會話。code
重點在 session_commit() 這個函數被執行後,再次獲取狀態,返回值又變爲1。進程
PHP 的 session_start() 函數執行時至關於完成了會話的 open 和 read 兩個步驟,而 session_commit() 執行時至關於進行了會話的 write 和 close 兩個步驟,與 session_write_close() 函數做用是一致的。
回到最初遇到的問題上,當 PHP 的 SESSION 開啓後,進程會對會話的臨時文件加鎖,以保證同一時刻此文件只被一個進程修改。此時,若是會話沒有 close 而其餘進程又開啓了會話,後來的進程就會被 PHP 暫時阻塞,等待臨時文件解鎖。
接下來看兩段代碼:
a.php
<?php session_start(); $_SESSION['t1'] = time(); sleep(10); echo $_SESSION['t1']; ?>
b.php
<?php session_start(); $_SESSION['t2'] = time(); echo $_SESSION['t1']; ?>
咱們將上邊兩段代碼分別保存爲文件 a.php 和 b.php,首先運行 a.php,緊接着運行 b.php,咱們發如今 a.php 沒有結束還處於 sleep 狀態時,b.php始終被阻塞在那裏遲遲沒法輸出結果,緣由就是上邊咱們分析的會話臨時文件被加鎖,後來的進程被暫時阻塞的問題。
爲了解決這個問題,咱們能夠在進程進入 sleep 前,經過 session_commit() 函數將會話 close 掉,從而讓當前進程解鎖會話臨時文件,以便讓其餘進程得到文件的鎖。
修改後的 a.php 代碼以下:
<?php session_start(); $_SESSION['t1'] = time(); // write and close session_commit(); sleep(10); echo $_SESSION['t1']; ?>
按上邊的代碼修改 a.php 後,咱們再次在瀏覽器中運行兩個文件,a.php 在 sleep 狀態下,b.php 已經能夠很正常的運行了。