PHP缓存策略与性能优化实践缓存是提高应用性能最有效的手段之一。PHP的缓存可以分多个层级从OPcache到用户数据缓存每一层都有各自的用途。今天说说实际项目中怎么设计缓存策略。先从OPcache说起。OPcache缓存了PHP脚本编译后的opcode避免了每次请求都重新解析编译的开销。php// php.ini 推荐配置/*opcache.enable1opcache.memory_consumption256opcache.interned_strings_buffer16opcache.max_accelerated_files20000opcache.revalidate_freq60opcache.fast_shutdown1opcache.enable_cli1*/?用户数据缓存通常用Redis或Memcached。设计缓存时要注意缓存什么、缓存多久、何时失效。phpclass CacheManager{private Redis $redis;private string $prefix app:;public function __construct(){$this-redis new Redis();$this-redis-connect(127.0.0.1, 6379);}public function remember(string $key, callable $callback, int $ttl 3600): mixed{$cacheKey $this-prefix . $key;$cached $this-redis-get($cacheKey);if ($cached ! false) {return unserialize($cached);}$value $callback();$this-redis-setex($cacheKey, $ttl, serialize($value));return $value;}public function rememberForever(string $key, callable $callback): mixed{$cacheKey $this-prefix . $key;$cached $this-redis-get($cacheKey);if ($cached ! false) {return unserialize($cached);}$value $callback();$this-redis-set($cacheKey, serialize($value));return $value;}public function put(string $key, mixed $value, int $ttl 3600): void{$this-redis-setex($this-prefix . $key, $ttl, serialize($value));}public function get(string $key, mixed $default null): mixed{$value $this-redis-get($this-prefix . $key);return $value ! false ? unserialize($value) : $default;}public function forget(string $key): void{$this-redis-del($this-prefix . $key);}public function has(string $key): bool{return $this-redis-exists($this-prefix . $key);}public function flush(): void{$keys $this-redis-keys($this-prefix . *);if (!empty($keys)) {$this-redis-del($keys);}}public function tags(array $tags, int $ttl 3600): TaggedCache{return new TaggedCache($this-redis, $this-prefix, $tags, $ttl);}}class TaggedCache{private array $tags;private int $ttl;public function __construct(private Redis $redis,private string $prefix,array $tags,int $ttl) {$this-tags $tags;$this-ttl $ttl;}public function remember(string $key, callable $callback): mixed{$cacheKey $this-buildKey($key);$cached $this-redis-get($cacheKey);if ($cached ! false) {return unserialize($cached);}$value $callback();$this-redis-setex($cacheKey, $this-ttl, serialize($value));// 将key关联到tagforeach ($this-tags as $tag) {$tagKey $this-prefix . tag:{$tag};$this-redis-sAdd($tagKey, $cacheKey);$this-redis-expire($tagKey, $this-ttl 86400);}return $value;}public function flush(): void{foreach ($this-tags as $tag) {$tagKey $this-prefix . tag:{$tag};$keys $this-redis-sMembers($tagKey);if (!empty($keys)) {$this-redis-del($keys);$this-redis-del($tagKey);}}}private function buildKey(string $key): string{$tagHash md5(implode(,, $this-tags));return $this-prefix . {$tagHash}:{$key};}}?缓存穿透、缓存雪崩、缓存击穿是三种常见的缓存问题。php// 缓存穿透查询不存在的数据缓存和数据库都没有function getUserById(int $id): ?array{$cache new CacheManager();$key user:{$id};// 检查是否是空值缓存if ($cache-has($key . :null)) {return null;}$user $cache-remember($key, function () use ($id) {$result queryDatabase(SELECT * FROM users WHERE id ?, [$id]);// 如果数据库也没有缓存空值防止缓存穿透if (empty($result)) {return __NULL__;}return $result;}, 3600);return $user __NULL__ ? null : $user;}// 缓存雪崩大量缓存同时过期// 解决方案过期时间加随机值function getUserByIdRandom(int $id): ?array{$cache new CacheManager();$key user:{$id};// 基础过期时间加随机值避免同时过期$ttl 3600 rand(0, 600);return $cache-remember($key, function () use ($id) {return queryDatabase(SELECT * FROM users WHERE id ?, [$id]);}, $ttl);}// 缓存击穿热点key过期大量请求同时打到数据库// 解决方案互斥锁function getHotData(string $key, callable $callback, int $ttl 3600): mixed{$cache new CacheManager();$result $cache-get($key);if ($result ! null) {return $result;}// 尝试获取锁$lockKey lock:{$key};$redis new Redis();$redis-connect(127.0.0.1, 6379);if ($redis-setnx($lockKey, 1)) {$redis-expire($lockKey, 10); // 锁10秒过期$result $callback();$cache-put($key, $result, $ttl);$redis-del($lockKey);return $result;}// 没获取到锁等待后重试usleep(100000);return getHotData($key, $callback, $ttl);}?本地缓存加上Redis组成二级缓存性能更好phpclass TwoLevelCache{private array $local [];private Redis $redis;private string $prefix;public function __construct(string $prefix app:){$this-prefix $prefix;$this-redis new Redis();$this-redis-connect(127.0.0.1, 6379);}public function get(string $key, callable $callback, int $ttl 3600): mixed{$cacheKey $this-prefix . $key;// L1: 本地缓存if (array_key_exists($cacheKey, $this-local)) {echo L1缓存命中: $key\n;return $this-local[$cacheKey];}// L2: Redis$value $this-redis-get($cacheKey);if ($value ! false) {echo L2缓存命中: $key\n;$this-local[$cacheKey] unserialize($value);return $this-local[$cacheKey];}// 数据源echo 从数据源加载: $key\n;$value $callback();$this-local[$cacheKey] $value;$this-redis-setex($cacheKey, $ttl, serialize($value));return $value;}public function forget(string $key): void{$cacheKey $this-prefix . $key;unset($this-local[$cacheKey]);$this-redis-del($cacheKey);}public function flush(): void{$this-local [];$keys $this-redis-keys($this-prefix . *);if (!empty($keys)) {$this-redis-del($keys);}}}?缓存失效策略也很重要。常见的策略有过期时间、主动失效和版本号。php// 版本号缓存class VersionedCache{private Redis $redis;private string $prefix;public function __construct(){$this-redis new Redis();$this-redis-connect(127.0.0.1, 6379);$this-prefix app;}public function get(string $key, callable $callback, int $ttl 3600): mixed{$version $this-getVersion($key);$cacheKey {$this-prefix}:v{$version}:{$key};$cached $this-redis-get($cacheKey);if ($cached ! false) {return unserialize($cached);}$value $callback();$this-redis-setex($cacheKey, $ttl, serialize($value));return $value;}public function bumpVersion(string $key): void{$versionKey {$this-prefix}:version:{$key};$this-redis-incr($versionKey);}private function getVersion(string $key): int{$versionKey {$this-prefix}:version:{$key};return (int)$this-redis-get($versionKey) ?: 1;}}?缓存在提升性能方面立竿见影。但不是所有数据都适合缓存。频繁变化的数据、每次请求都不同的数据、对实时性要求高的数据就不适合缓存。先分析业务场景再选择合适的缓存策略效果才会好。