高併發 Web 應用中,快取是降低資料庫壓力、提升回應速度的關鍵層。ThinkPHP 內建支援 10 種快取驅動,其中 File(檔案)快取 零配置、無額外依賴、適合中小型專案或開發環境。本文深度解析 ThinkPHP File 快取配置、核心 `S()` 函式用法、快取策略設計、常見陷阱與遷移建議。
<?php
return [
// 預設快取驅動
'default' => 'file',
// 快取連線設定(File 驅動無需 host/port)
'stores' => [
'file' => [
'type' => 'file',
'path' => app()->getRuntimePath() . 'cache' . DIRECTORY_SEPARATOR, // 儲存目錄
'prefix' => 'tp_', // 檔名前綴,避免衝突
'expire' => 0, // 預設永不過期(0),單位:秒
'path_depth' => 1, // 子目錄層級,大量快取時建議 1-2 避免單目錄檔案過多
'hash_type' => 'md5', // 雜湊演算法
],
// 可同時定義 redis、memcached 等其他驅動
],
// 全域快取標籤前綴(用於標籤清除)
'tag_prefix' => 'tag_',
];
`S()` 是 `think\facade\Cache::store('file')` 的快捷門面,支援鏈式呼叫。
<?php
use think\facade\Cache;
// 1. 寫入快取(預設永久)
S('user_list', $userList); // 永久
S('user_list', $userList, 3600); // 1 小時過期
S('user_list', $userList, 3600, 'user_tag'); // 帶標籤,方便批次清除
// 2. 讀取快取
$list = S('user_list'); // 不存在回傳 false
$list = Cache::get('user_list'); // 等效寫法
// 3. 判斷快取是否存在
if (Cache::has('user_list')) { ... }
// 4. 刪除快取
S('user_list', null); // 刪除單一
Cache::delete('user_list'); // 等效
// 5. 批次操作
Cache::setMultiple(['a' => 1, 'b' => 2], 3600);
$data = Cache::getMultiple(['a', 'b']);
Cache::deleteMultiple(['a', 'b']);
// 6. 標籤操作(File 驅動支援)
Cache::tag('user_tag')->set('user_1', $user1);
Cache::tag('user_tag')->set('user_2', $user2);
Cache::tag('user_tag')->clear(); // 一次清除所有 user_tag 快取
// 7. 遞增/遞減(計數器)
Cache::inc('page_view'); // +1
Cache::dec('stock', 5); // -5
// 8. 取得並刪除(原子操作)
$val = Cache::pull('temp_key');
// 9. 記憶體快取(請求生命週期內,不寫檔案)
Cache::store('file')->remember('heavy_compute', 3600, function() {
return expensiveCalculation();
});
<?php
namespace app\controller;
use think\facade\Cache;
use think\facade\Db;
class Index extends BaseController
{
public function index()
{
$cacheKey = 'home_news_list_v2'; // 版本化鍵名,便於結構變更時失效
// 嘗試讀取快取
$newsList = Cache::get($cacheKey);
if ($newsList === false) {
// 快取未命中:查詢資料庫
$newsList = Db::name('news')
->where('status', 1)
->order('id desc')
->limit(20)
->select()
->toArray();
// 寫入快取,標記 'news' 標籤,有效期 10 分鐘
Cache::tag('news')->set($cacheKey, $newsList, 600);
$this->log('從資料庫讀取新聞列表');
} else {
$this->log('從快取讀取新聞列表');
}
$this->assign('newsList', $newsList);
return $this->fetch();
}
// 後台發布/更新/刪除新聞時,清除相關快取
public function clearNewsCache()
{
Cache::tag('news')->clear();
return json(['code' => 1, 'msg' => '新聞快取已清除']);
}
}
| 策略 | 適用場景 | 實作要點 |
|---|---|---|
| 旁路快取 | 讀多寫少、容許短暫不一致 | 讀取→無則查 DB 寫入;寫入 DB 後→刪除/更新快取 |
| 讀寫穿透 | 強一致性要求 | 寫入時同步更新快取;讀取永遠命中 |
| 快取預熱 | 系統啟動/部署後、大促前 | 腳本主動載入熱點資料至快取 |
| 雙層快取 | 極高併發熱點 | 本地記憶體 + 分散式 Redis,本地優先 |
| 版本化鍵名 | 資料結構頻繁變更 | `user_list_v3`,部署時遞增版本號 |
當流量增長、需分散式快取、支援標籤/發布訂閱/原子操作時,只需修改配置與門面呼叫:
// config/cache.php
'stores' => [
'redis' => [
'type' => 'redis',
'host' => '127.0.0.1',
'port' => 6379,
'password' => '',
'select' => 0,
'prefix' => 'tp_',
'expire' => 3600,
],
],
// 控制器中切換驅動(或修改 default)
Cache::store('redis')->set('key', $val, 3600);
// 或全域預設
// 'default' => 'redis',
| 現象 | 原因 | 解決 |
|---|---|---|
| 快取不生效,每次都查 DB | 鍵名衝突、過期時間 0、序列化失敗 | 檢查 `Cache::get()` 回傳值、確認 `runtime/cache` 可寫入、檢查資料是否可序列化 |
| 快取無法清除 | 權限不足、路徑錯誤、標籤機制不支援 | 確認 `Cache::clear()` 權限、File 驅動標籤需檔案掃描較慢 |
| 併發下資料不一致 | 旁路快取固有特性 | 接受短暫不一致、或改用讀寫穿透/分散式鎖 |
| 記憶體耗盡 | 快取檔案過大、OPcache 記憶體不足 | 拆分快取、增加 `opcache.memory_consumption` |
ThinkPHP File 快取以零依賴、極簡配置、OPcache 加持下的高效能,成為中小型專案首選快取方案。掌握 `S()` / `Cache` 門面完整 API、設計合理的快取策略(版本化鍵名、標籤分組、預熱)、並建立監控與清理機制,即可充分發揮快取效益。隨著業務增長,平滑遷移至 Redis 只需修改配置與驅動名稱,代碼幾乎零改動。快取不是銀彈,正確的策略與維護才是關鍵。