应只缓存已成功编码的json字符串,先json_encode()并校验是否false,再apcu_store();避免未过滤用户输入拼接缓存键;配合cache-control头实现http缓存;禁用文件缓存除非满足严苛条件;redis中应存编码后字符串而非数组。

PHP 返回 JSON 时直接用 apcu_store() 缓存序列化结果
多数人以为缓存 JSON 就是“先 json_encode() 再存”,但容易忽略:如果原始数据结构复杂(比如含资源、循环引用),json_encode() 可能失败或静默截断。更稳妥的做法是,**只缓存已成功编码的字符串**,跳过重复编码开销。
实操建议:
- 先调用
json_encode($data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),检查返回值是否为false(可用json_last_error()辅助诊断) - 仅当编码成功,才用
apcu_store($key, $json_string, $ttl)缓存该字符串,而非缓存原始数组 - 避免在缓存键中拼接未过滤的用户输入(如
$_GET['id']),防止键冲突或注入,推荐用sprintf('api_user_%d', (int)$_GET['id'])
用 header('Cache-Control: public, max-age=300') 让浏览器也缓存 JSON 响应
后端缓存只解决服务器压力,前端不复用仍会反复请求。对不变或低频更新的 JSON 接口(如配置、地区列表),必须配合 HTTP 缓存头。
注意点:
立即学习“PHP免费学习笔记(深入)”;
-
max-age设太长会导致客户端拿不到更新,设太短失去意义;300 秒(5 分钟)是较安全的起点 - 若接口依赖登录态或用户角色,改用
private而非public,避免 CDN 或代理缓存用户敏感数据 - 搭配
ETag或Last-Modified可实现协商缓存,但需额外计算开销;简单场景优先用max-age
避免用 file_put_contents() 写 JSON 文件做缓存
本地文件缓存看似简单,但在并发请求下极易出问题:多个进程同时写同一文件,可能造成内容损坏、JSON 格式错误(如中间写入被截断),且文件 I/O 比内存缓存慢一个数量级。
除非满足以下全部条件,否则不建议:
- 服务器无 APCu / Redis 等扩展,且无法安装
- 数据更新频率极低(如每天一次),且可接受单点写锁(用
flock()) - 明确限制了并发访问量(如仅后台定时任务调用)
若真要用文件缓存,务必加锁并验证 JSON 合法性:json_decode(file_get_contents($cache_file), true) !== null,否则后续 json_encode() 输出可能直接崩掉整个接口。
Redis 缓存 JSON 时别用 SET 存原生数组
PHP 的 redis->set($key, $array) 实际会自动序列化(取决于 serialize 设置),但反序列化后得到的是 PHP 数组,不是 JSON 字符串——这意味着你仍需在每次读取后调用 json_encode(),没节省任何开销。
正确做法:
- 存:先
$json = json_encode($data),再$redis->setex($key, $ttl, $json) - 取:直接
$json = $redis->get($key),然后echo $json(无需再 encode) - 若需兼容其他语言读取,确保
json_encode()不含 PHP 特有类型(如DateTime对象需提前转为 ISO8601 字符串)
真正省掉的是 CPU 编码时间,而不是网络或存储成本——这点常被误判。











