PHP 默認 session.save_handler = files,session_start() 會對 session 文件加獨佔鎖 (flock)。同一用戶並發請求 (如多個 AJAX) 會串行等待,嚴重拖慢響應。本文提供三種解決路徑。
session_start(); // 讀取/寫入 session 變量 $_SESSION['user_id'] = 123; $_SESSION['cart'] = $cartData; // 關鍵:業務邏輯執行前釋放鎖 session_write_close(); // 後續耗時操作:DB 查詢、外部 API、複雜計算... sleep(2); echo "響應已發送,session 已釋放,並發請求不再阻塞";
原理:session_write_close() 將數據寫回文件並釋放鎖,後續代碼可繼續讀取 $_SESSION (已在內存) 但不再寫入。請求結束時不再自動保存。
class RedisSessionHandler implements SessionHandlerInterface {
private $redis;
public function __construct() {
$this->redis = new Redis();
$this->redis->connect('127.0.0.1', 6379);
}
public function open($path, $name) { return true; }
public function close() { return true; }
public function read($id) { return $this->redis->get("sess:$id") ?: ''; }
public function write($id, $data) { return $this->redis->setex("sess:$id", 1800, $data); }
public function destroy($id) { return $this->redis->del("sess:$id"); }
public function gc($maxlifetime) { return true; } // Redis 自動過期
}
$handler = new RedisSessionHandler();
session_set_save_handler($handler, true); // true = 註冊 shutdown function
session_start();
優勢:Redis 單線程但極快、支援集群、天然解決多機部署 session 共享、無文件鎖阻塞。
class MySessionHandler extends SessionHandler {
public function __construct($savePath = '') {
// 可在這裡初始化 Redis 連接池
parent::__construct($savePath);
}
// 只需覆寫需要修改的方法,如 read/write 加入壓縮、加密
public function write($id, $data) {
$compressed = gzcompress($data);
return parent::write($id, $compressed);
}
public function read($id) {
$data = parent::read($id);
return $data ? gzuncompress($data) : '';
}
}
session_set_save_handler(new MySessionHandler(), true);
session_start();
| 方案 | 改動量 | 並發效能 | 集群支援 | 適用場景 |
|---|---|---|---|---|
| session_write_close | 極小 | 中 (仍受限於文件 IO) | 否 | 快速優化、單機、讀多寫少 |
| Redis 自定義 Handler | 中 | 極高 | 是 | 高併發、分佈式、需共享 |
| 繼承 SessionHandler | 小 | 高 | 視底層實現 | 需在文件基礎上增強 (壓縮、加密) |
session_write_close()session.gc_maxlifetime 配合 session.gc_probability 定期清理