記憶體快取是高性能 PHP 架構的基石,能將資料庫查詢、複雜計算、外部 API 調用的結果以**微秒級延遲**讀取,大幅降低後端負載、提升響應速度。PHP 生態有三大主流記憶體快取方案:**APCu (本地進程內)、Memcached (分布式 Key-Value)、Redis (數據結構服務器)**。本文將從架構原理、功能特性、性能基準、運維複雜度、典型場景五個維度深度對比,並給出生產環境選型決策樹與最佳實踐代碼模式。
| 維度 | APCu (Alternative PHP Cache User) | Memcached (分布式內存對象快取) | Redis (Remote Dictionary Server) |
|---|---|---|---|
| **儲存位置** | **PHP 進程共享記憶體 (SHM)** 單機、進程隔離 (php-fpm 每池獨立) | **獨立進程/集群** 網絡訪問 (TCP 11211) | **獨立進程/集群** 網絡訪問 (TCP 6379) |
| **數據模型** | Key → String (序列化後) | Key → String (序列化後) | **豐富數據結構** String, Hash, List, Set, Sorted Set, Bitmap, HyperLogLog, Stream, Geo... |
| **持久化** | ❌ 無 (進程重啟/重載數據全失) | ❌ 無 (重啟數據全失) | ✅ **RDB 快照 + AOF 日誌** 可配置持久化策略 |
| **分布式/高可用** | ❌ 單機 (無法跨服務器共享) | ✅ Client 端一致性哈希分片 ✅ 官方無副本,需應用層/Proxy 實現 HA | ✅ **Redis Cluster** (自動分片+主從複製+故障轉移) ✅ Sentinel 高可用 |
| **過期策略** | TTL (秒)、定時掃描清理 | TTL (秒)、惰性過期 + 定期掃描 | TTL (秒/毫秒)、惰性 + 定期、 **記憶體淘汰策略** (LRU/LFU/Random/TTL...) |
| **原子操作** | apcu_cas (CAS)、增減 | increment/decrement、cas (1.4+) | **極豐富** INCR/DECR, Lua 腳本原子性, WATCH/MULTI/EXEC 事務, SET NX/XX/GET/EX... |
| **發布/訂閱** | ❌ | ❌ | ✅ **原生 PUB/SUB、Pattern、Stream** |
| **PHP 擴展** | apcu (核心擴展) | memcached (推薦, libmemcached)memcache (舊, 不推薦) | redis (phpredis, 高性能 C 擴展)predis (純 PHP, 兼容性好) |
| **典型延遲** | **~0.05-0.2 ms** (本地記憶體,無網絡) | ~0.5-2 ms (本地網絡) | ~0.5-2 ms (本地網絡) |
| **運維複雜度** | **極低** (隨 PHP 安裝,無獨立進程) | 低 (單一執行檔,配置簡單) | 中 (持久化、集群、記憶體管理、 慢日誌、監控指標多) |
apc.shm_size=256M,超出自動淘汰 (LRU)。<?php
interface CacheInterface {
public function get(string $key, mixed $default = null): mixed;
public function set(string $key, mixed $value, int $ttl = 0): bool;
public function delete(string $key): bool;
public function clear(): bool;
public function has(string $key): bool;
public function increment(string $key, int $step = 1): int|false;
public function decrement(string $key, int $step = 1): int|false;
}
// APCu 實現
class ApcuCache implements CacheInterface {
public function get(string $key, mixed $default = null): mixed {
$success = false;
$val = apcu_fetch($key, $success);
return $success ? $val : $default;
}
public function set(string $key, mixed $value, int $ttl = 0): bool {
return apcu_store($key, $value, $ttl);
}
public function delete(string $key): bool { return apcu_delete($key); }
public function clear(): bool { return apcu_clear_cache(); }
public function has(string $key): bool { return apcu_exists($key); }
public function increment(string $key, int $step = 1): int|false {
return apcu_inc($key, $step, $success) ? $success : false; // 注意返回值處理
}
public function decrement(string $key, int $step = 1): int|false {
return apcu_dec($key, $step, $success) ? $success : false;
}
}
// Redis 實現 (使用 phpredis)
class RedisCache implements CacheInterface {
private \Redis $redis;
public function __construct(\Redis $redis) { $this->redis = $redis; }
public function get(string $key, mixed $default = null): mixed {
$val = $this->redis->get($key);
return $val !== false ? $val : $default;
}
public function set(string $key, mixed $value, int $ttl = 0): bool {
return $ttl > 0 ? $this->redis->setex($key, $ttl, $value) : $this->redis->set($key, $value);
}
public function delete(string $key): bool { return (bool)$this->redis->del($key); }
public function clear(): bool { return $this->redis->flushDB(); }
public function has(string $key): bool { return $this->redis->exists($key); }
public function increment(string $key, int $step = 1): int|false { return $this->redis->incrBy($key, $step); }
public function decrement(string $key, int $step = 1): int|false { return $this->redis->decrBy($key, $step); }
// Redis 進階功能
public function hSet(string $key, string $field, mixed $value): int { return $this->redis->hSet($key, $field, $value); }
public function hGetAll(string $key): array { return $this->redis->hGetAll($key); }
public function zAdd(string $key, float $score, string $member): int { return $this->redis->zAdd($key, $score, $member); }
public function eval(string $script, int $numKeys, ...$keys): mixed { return $this->redis->eval($script, $numKeys, ...$keys); }
}
?>
<?php
class TieredCache implements CacheInterface {
private CacheInterface $l1; // APCu (極速、小容量、可丟失)
private CacheInterface $l2; // Redis (較大、持久、分布式)
private int $l1Ttl; // L1 存活時間較短 (如 60s),防止數據不一致時間過長
public function __construct(CacheInterface $l1, CacheInterface $l2, int $l1Ttl = 60) {
$this->l1 = $l1; $this->l2 = $l2; $this->l1Ttl = $l1Ttl;
}
public function get(string $key, mixed $default = null): mixed {
// 1. 先查 L1
$val = $this->l1->get($key, $sentinel = new class{});
if ($sentinel !== $val) return $val; // L1 命中
// 2. 查 L2
$val = $this->l2->get($key, $sentinel);
if ($sentinel !== $val) {
// 3. 回填 L1 (短 TTL)
$this->l1->set($key, $val, $this->l1Ttl);
return $val;
}
return $default;
}
public function set(string $key, mixed $value, int $ttl = 0): bool {
// 雙寫 (L2 為主,L1 短 TTL)
$ok2 = $this->l2->set($key, $value, $ttl);
$ok1 = $this->l1->set($key, $value, min($ttl ?: 3600, $this->l1Ttl));
return $ok1 && $ok2;
}
public function delete(string $key): bool {
// 雙刪 (先刪 L2 再刪 L1,或異步刪 L1)
$ok2 = $this->l2->delete($key);
$ok1 = $this->l1->delete($key);
return $ok1 && $ok2;
}
// 其他方法代理...
}
?>
<?php
class ProtectedCache {
private CacheInterface $cache;
private callable $loader; // 回源加載器 function(string $key): mixed
private int $lockTtl = 10; // 鎖超時
private int $emptyTtl = 60; // 空值快取 TTL (防穿透)
public function __construct(CacheInterface $cache, callable $loader) {
$this->cache = $cache; $this->loader = $loader;
}
public function get(string $key): mixed {
$val = $this->cache->get($key, $sentinel = new class{});
if ($sentinel !== $val) return $val; // 命中 (含 null 值)
// 未命中 -> 獲取分布式鎖 (防擊穿)
$lockKey = "lock:$key";
$gotLock = $this->cache->set($lockKey, 1, $this->lockTtl); // 簡易 SET NX (需 Redis SET NX 實現)
// 實際生產用 Redis SET key value NX EX 10
if ($gotLock) {
try {
$val = ($this->loader)($key); // 回源 (DB/API)
$ttl = $val === null ? $this->emptyTtl : 3600; // 空值短 TTL
$this->cache->set($key, $val, $ttl);
return $val;
} finally {
$this->cache->delete($lockKey); // 釋放鎖
}
} else {
// 未獲取鎖:短暫等待重試 或 返回舊值/預設值
usleep(50000); // 50ms
return $this->cache->get($key) ?? null;
}
}
}
?>
<?php
// php.ini
session.save_handler = redis
session.save_path = "tcp://redis1:6379?weight=1, tcp://redis2:6379?weight=1, tcp://redis3:6379?weight=1"
// 或單機
session.save_path = "tcp://127.0.0.1:6379?auth=password&database=2&prefix=session:"
// 關鍵設置
session.gc_maxlifetime = 7200
session.redis.lock_expire = 10 // 鎖超時 (秒)
session.redis.lock_retries = 10 // 鎖重試次數
session.redis.lock_wait_time = 2000 // 鎖重試間隔 (微秒)
// 以上需 phpredis 5.3.4+ 支援
?>
| 組件 | 關鍵參數 | 建議值/說明 |
|---|---|---|
| APCu | apc.enabled=1 | 啟用 |
apc.shm_size=256M | 根據機器記憶體,預留給 PHP-FPM/OS | |
apc.ttl=7200 | 條目默認 TTL (秒),0=永不超時 (風險:碎片化) | |
apc.entries_hint=10000 | 預估條目數,優化哈希表大小 | |
apc.serializer=igbinary | **強烈推薦 igbinary** 序列化 (更快、更小、支援類) | |
| Memcached | -m 1024 | 記憶體上限 (MB) |
-c 1024 | 最大並發連接數 | |
-t 4 | 工作線程數 (通常 = CPU 核心數) | |
PHP Memcached::OPT_BINARY_PROTOCOL = true | 啟用二進制協議 (更快、支援 CAS) | |
Memcached::OPT_TCP_NODELAY = true | 禁用 Nagle,降低延遲 | |
Memcached::OPT_NO_BLOCK = true | 非阻塞 I/O (配合 select/多路復用) | |
| Redis | maxmemory 2gb | 記憶體上限,超過觸發淘汰 |
maxmemory-policy allkeys-lru | 淘汰策略 (快取推薦 LRU/LFU) | |
save 900 1 / save 300 10 / save 60 10000 | RDB 持久化頻率 (快取場景可關閉或極低頻) | |
appendonly yes / appendfsync everysec | AOF 持久化 (快取場景建議關閉或 everysec) | |
lazyfree-lazy-eviction yes | 異步釋放記憶體 (Redis 4.0+),避免阻塞主線程 | |
PHP Redis::OPT_SERIALIZER = Redis::SERIALIZER_IGBINARY | **啟用 igbinary 序列化** (安裝 php-igbinary) | |
Redis::OPT_PREFIX = 'myapp:' | 自動鍵前綴,防衝突、便於清理 |
需要跨多台服務器共享快取?
├─ 否 (單機/容器單副本)
│ └─ 數據可接受進程重啟丟失?
│ ├─ 是 → **APCu** (極速、零運維、本地記憶體)
│ └─ 否 (需持久化/重啟保留) → **Redis** (單機模式,開啟 AOF/RDB)
│
└─ 是 (分布式/多實例)
├─ 純 KV、極高寫入吞吐、無持久化需求、運維極簡?
│ └─ 是 → **Memcached** (多線程、簡單、穩定)
│
└─ 需要以下任一特性?
• 複雜數據結構 (Hash/Set/ZSet/List/Bitmap/Stream/Geo)
• 持久化/數據不丟失
• 高可用/自動故障轉移
• 發布訂閲/Stream 消息隊列
• Lua 腳本原子操作/分布式鎖/限流
• 精細記憶體淘汰策略
→ **Redis** (功能最全、生態最豐、生產級首選)
💡 進階架構:多級快取 (L1 APCu 熱點 + L2 Redis 主快取) 兼得極速與一致性
根據業務階段、團隊運維能力、基礎設施預算、數據一致性要求、擴展性預期,選擇最適合的方案。**起步推薦 Redis 單機** (功能全、擴展平滑、社區活躍);**極致性能單機場景加 APCu**;**存量 Memcached 系統穩定則維持**。