首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP Session 鎖機制與並發問題深度剖析:阻塞、資料遺失與解決方案

     編輯:PHP 綜合知識

問題現場

登入頁面同時載入表單 (CSRF Token) 與驗證碼圖片,兩個請求並發存取 Session,導致:

  • CSRF 驗證失敗
  • 驗證碼比對錯誤 (明明輸入正確)

根本原因:Session 讀寫機制

PHP 預設行為:

  1. 請求開始 → session_start() → 讀取 Session 檔案/Redis 並加鎖
  2. 程式碼執行中 → $_SESSION['x'] = 'y' 只操作記憶體陣列
  3. 請求結束或 session_write_close() → 寫回存儲並釋放鎖

關鍵點:單一請求只讀寫一次,鎖保護整個請求週期。

並發災難演示

請求 A (表單頁)          請求 B (驗證碼圖)
─────────────────        ─────────────────
session_start() ──────►  等待鎖 (阻塞)
讀取 Session
產生 CSRF Token
$_SESSION['csrf'] = 'abc'
                    session_start() (鎖釋放後)
                    讀取 Session (無 csrf!)
                    產生 Captcha
                    $_SESSION['captcha'] = '1234'
                    寫回、釋放鎖
寫回、釋放鎖 (覆蓋 captcha!)

結果:請求 A 寫回時覆蓋請求 B 的 captcha,或反之,導致資料遺失。

解決方案比較

方案一:盡早釋放鎖 (推薦)

在輸出內容前呼叫 session_write_close(),後續邏輯不再寫入 Session:

// 視圖渲染完成後
$eventManager->attach('view:afterRender', function() {
    session_write_close();
});

// 登入驗證成功後重導向前
$eventManager->attach('dispatch:afterDispatchLoop', function() {
    session_write_close();
});

方案二:使用不加鎖的 Session 處理器

Redis 可用 Lua 腳本實現 CAS (Compare-And-Swap) 無鎖寫入,或使用 session_set_save_handler 自訂存取邏輯。

方案三:減少 Session 依賴

  • CSRF Token 改用 JWT 或雙重提交 Cookie 模式
  • 驗證碼存入快取 而非 Session
  • 無狀態 API 改用 Token 認證

最佳實踐總結

  1. 預設啟用 session_write_close() 於視圖渲染後
  2. 避免長時間請求持有 Session 鎖
  3. 高併發場景評估無鎖存儲或無狀態架構
  4. 監控 Session 鎖等待時間 (Redis INFO 中的 keyspace_hits/misses 等指標)
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved