Skip to content

缓存技术详解

缓存是提升应用性能的关键手段。本文介绍 PHP 中常用的缓存方案。

文件缓存(最简单)

php
class FileCache {
    private $cachePath;

    public function __construct($path = 'cache/') {
        $this->cachePath = $path;
        if (!is_dir($path)) {
            mkdir($path, 0755, true);
        }
    }

    public function get($key) {
        $file = $this->cachePath . md5($key) . '.cache';
        if (!file_exists($file)) return null;

        $data = unserialize(file_get_contents($file));
        if ($data['expire'] < time()) {
            unlink($file);
            return null;
        }
        return $data['value'];
    }

    public function set($key, $value, $ttl = 3600) {
        $file = $this->cachePath . md5($key) . '.cache';
        $data = ['expire' => time() + $ttl, 'value' => $value];
        return file_put_contents($file, serialize($data), LOCK_EX);
    }

    public function delete($key) {
        $file = $this->cachePath . md5($key) . '.cache';
        return file_exists($file) && unlink($file);
    }
}

// 使用示例
$cache = new FileCache();
$data = $cache->get('user_list');
if (!$data) {
    $data = fetchUsersFromDb();
    $cache->set('user_list', $data, 600);
}

Redis 缓存(推荐)

php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('password'); // 如果有密码

// 字符串缓存
$redis->set('config:site_name', '我的网站', 3600);
$name = $redis->get('config:site_name');

// 数组缓存(自动序列化)
$user = ['id' => 1, 'name' => '张三'];
$redis->setex('user:1', 3600, json_encode($user));
$user = json_decode($redis->get('user:1'), true);

// 缓存数据库查询结果
function getUserWithCache($id) {
    global $redis;
    $key = "user:$id";

    $cached = $redis->get($key);
    if ($cached) {
        return json_decode($cached, true);
    }

    $user = fetchUserFromDb($id);
    if ($user) {
        $redis->setex($key, 3600, json_encode($user));
    }
    return $user;
}

// 缓存失效
$redis->del('user:1');           // 删除单个
$redis->delete('user:1', 'user:2'); // 删除多个
$redis->flushDB();               // 清空当前库(慎用)

缓存穿透、击穿、雪崩解决方案

php
// 1. 缓存穿透(查询不存在的数据)- 布隆过滤器或缓存空值
function getDataWithNullCache($key) {
    $value = $redis->get($key);
    if ($value === 'null_marker') {
        return null; // 数据库确实不存在
    }
    if ($value) {
        return json_decode($value, true);
    }

    // 查询数据库
    $data = fetchFromDb($key);
    if ($data) {
        $redis->setex($key, 3600, json_encode($data));
    } else {
        // 缓存空值,短时间过期
        $redis->setex($key, 60, 'null_marker');
    }
    return $data;
}

// 2. 缓存击穿(热点数据过期)- 互斥锁
function getHotDataWithLock($key) {
    $value = $redis->get($key);
    if ($value) {
        return json_decode($value, true);
    }

    // 尝试获取锁
    $lockKey = "lock:$key";
    $locked = $redis->set($lockKey, 1, ['nx', 'ex' => 10]);

    if ($locked) {
        // 只有获得锁的进程查询数据库
        $data = fetchFromDb($key);
        $redis->setex($key, 3600, json_encode($data));
        $redis->del($lockKey);
        return $data;
    }

    // 未获得锁,短暂等待后重试
    usleep(100000); // 100ms
    return getHotDataWithLock($key);
}

// 3. 缓存雪崩(大量缓存同时过期)- 随机过期时间
function setWithRandomExpire($key, $value, $baseTtl = 3600) {
    $random = mt_rand(0, 300); // 0-5分钟随机
    $redis->setex($key, $baseTtl + $random, json_encode($value));
}

OPCache 配置优化

ini
; php.ini OPCache 推荐配置
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=4000
opcache.revalidate_freq=60
opcache.fast_shutdown=1
opcache.validate_timestamps=0  ; 生产环境关闭,需手动重启

注意事项

  1. 缓存更新策略:写数据库时同步/异步更新缓存
  2. 缓存粒度:根据业务选择合适粒度,避免缓存过大
  3. 序列化开销:大数据量缓存考虑使用更高效的序列化方式(msgpack/igbinary)
  4. 监控:关注缓存命中率,低于80%需要优化

Binstork