深刻研究 PHP 的 SESSION 阻塞問題

最近在一個基於 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 已經能夠很正常的運行了。

相關文章
相關標籤/搜索