Redis更合适需结构化缓存、原子操作或持久化的场景;Memcached更合适纯临时、高并发、扁平键值缓存。两者在数据结构、线程模型、持久化、PHP客户端行为及缓存策略上存在本质差异。

PHP里用 redis 和 memcached 做数据缓存,本质区别在哪?
不是“哪个更好”,而是“谁更合适”。redis 和 memcached 在 PHP 中都通过扩展提供客户端接口,但底层行为差异直接决定你能不能用、怎么用、会不会踩坑。
关键区别有三点:
-
redis支持多种数据结构(string、hash、list、zset),适合缓存带结构的数据,比如用户会话 + 权限列表 + 最近操作时间戳;memcached只支持扁平的key → string,所有数据必须序列化后存入 -
redis默认单线程,命令原子性强,INCR、HGETALL这类操作天然安全;memcached多线程,但没有事务或原子复合操作,get + set组合在并发下可能被覆盖 -
redis有 RDB/AOF 持久化,重启不丢数据(可配置);memcached一断电全清空,只适合纯临时缓存
PHP连接 redis 时,set 和 setex 到底该选哪个?
别无脑用 setex。它只是 set 加过期时间的语法糖,但 PHP 的 Redis 扩展中,set 已经支持传入过期参数,更灵活:
$redis->set('user:1001', json_encode($data), ['EX' => 3600]); // 推荐:显式、可读、兼容新旧版本
$redis->setex('user:1001', 3600, json_encode($data)); // 旧写法,语义弱,且不支持 PX(毫秒级)
注意两个易错点:
立即学习“PHP免费学习笔记(深入)”;
- 如果值是数组或对象,必须手动
json_encode或serialize,redis不自动序列化 - 用
set带选项时,第三个参数是数组,不是字符串;传错类型会静默失败或抛RedisException - 毫秒级过期(如防刷限流)必须用
['PX' => 500],setex不支持
PHP调用 memcached 时,add、set、replace 的行为陷阱
这三个方法看着像只是“存”,实际语义完全不同,混用会导致缓存覆盖或写入失败却不报错:
-
add:仅当 key 不存在时才写入,存在则返回false—— 适合初始化缓存,比如首次生成 token -
set:无条件覆盖,不管 key 是否存在 —— 最常用,但要注意并发写入时最后写入者胜出 -
replace:仅当 key 已存在时才写入,不存在则返回false—— 少用,容易因 key 被提前删除而静默失败
典型误用场景:
// 错误:以为 add 能防止重复生成,但没检查返回值
$memcached->add('cache:report', $data, 300);
// 正确:必须判断返回值,否则失败了还继续走后续逻辑
if (!$memcached->add('cache:report', $data, 300)) {
error_log("cache:report already exists or write failed");
}
另外:memcached 的过期时间超过 30 天(2592000 秒)会被当作 UNIX 时间戳处理,极易误设为“永不过期”或“过去时间”,导致缓存立即失效。
缓存穿透、雪崩、击穿在 PHP 代码里怎么拦?
这三类问题不会报错,但会让数据库瞬间被打满。PHP 层能做的不是靠“加锁”或“重试”,而是前置拦截和兜底设计:
- 穿透:查一个根本不存在的
user_id,每次请求都打到 DB。解决办法是缓存空值($redis->set('user:999999', 'null', ['EX' => 60])),注意要区分“查无此用户”和“查库失败”,后者不能缓存空值 - 雪崩:大量 key 同时过期,DB 瞬间承接所有请求。避免方式是给过期时间加随机偏移(
3600 + rand(1, 300)),而不是统一设为3600 - 击穿:热点 key 过期瞬间,大量并发请求穿透到 DB。可用互斥锁(
SET key lock EX 30 NX)+ 回源重建,但注意 PHP-FPM 子进程间无法共享内存锁,必须用redis的原子命令实现
真正容易被忽略的是:缓存降级逻辑必须写在业务主流程里,而不是放在“异常捕获块”中——因为缓存不可用时,get 返回 false 或空字符串是正常行为,不是异常。



















