Memcached连接失败时new Memcached()不报错,因客户端采用懒连接机制,首次set()/get()才建连且静默返回false;应调用getStats()验证、检查网络及扩展启用情况。

连接 Memcached 服务失败,new Memcached() 没报错但 set() 直接返回 false
这通常不是代码写错了,而是服务根本没连上。Memcached 客户端默认使用「无异常模式」:构造 Memcached 实例不会尝试连接,直到第一次 set() 或 get() 才发起 TCP 连接;如果失败,就静默返回 false,不抛异常。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 调用
addServer()后立刻用getStats()验证连接:$m = new Memcached(); $m->addServer('127.0.0.1', 11211); var_dump($m->getStats()); // 返回空数组?说明没连上 - 检查
telnet 127.0.0.1 11211是否通;Docker 环境注意宿主机网络模式(别用localhost,改用host.docker.internal或实际 IP) - 确认 PHP 已启用
memcached扩展(不是memcache),运行php -m | grep memcached
set() 成功但 get() 返回 false,数据「凭空消失」
Memcached 是纯内存存储,没有持久化,所有数据在服务重启、内存满触发 LRU 清理、或显式 delete() 后都会丢失。更隐蔽的问题是过期时间设为 0 表示「永不过期」,但若传入负数(比如误写成 -1),PHP 的 memcached 扩展会把它当作「相对时间」处理,导致立即过期。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 写入时明确指定过期时间,避免依赖默认值:
$m->set('key', $value, 3600)(1 小时) - 不要传负数给第三个参数;若真想永不过期,传
0,不是null或-1 -
get()返回false不一定代表键不存在——也可能是值本身是false。用getResultCode()判断:$m->get('key'); if ($m->getResultCode() === Memcached::RES_NOTFOUND) { // 真的没这个 key }
缓存大数组或对象,serialize() 自动触发但反序列化失败
PHP 的 memcached 扩展默认启用 Memcached::OPT_SERIALIZER,对非标量值(数组、对象)自动调用 serialize() 存储。问题在于:反序列化时若类定义已变更、或环境缺失对应类,get() 会静默失败并返回 false,且不提示原因。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 优先缓存 JSON 可序列化的简单结构(字符串、数字、关联数组),避免直接塞
stdClass或自定义对象 - 如必须缓存对象,确保反序列化时类已加载(例如在
get()前require_once对应文件) - 禁用自动序列化更可控:
$m = new Memcached(); $m->setOption(Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_PHP); // 改为: $m->setOption(Memcached::OPT_SERIALIZER, Memcached::SERIALIZER_NONE); // 然后手动 json_encode/json_decode
并发写入同个 key,缓存击穿或脏数据
Memcached 本身不提供原子读-改-写操作,get() + set() 组合在高并发下必然竞争。典型表现:多个请求同时发现缓存失效,都去查 DB,再并发 set(),造成 DB 压力激增(缓存击穿),或后写入者覆盖先写入者的数据(脏写)。
实操建议:
立即学习“PHP免费学习笔记(深入)”;
- 用
add()做「占位锁」:if (!$m->add('lock:key', 1, 30)) { // 被别人抢先占了,等几毫秒重试或回退到 DB usleep(10000); continue; } - 更稳妥的做法是结合「逻辑过期」:缓存值里自带一个
expire_at字段,即使 key 过期也不立刻删,而是由第一个发现过期的请求异步刷新,其余请求继续用旧值 - 别依赖
cas()(check-and-set)来解决业务逻辑冲突——它只保证「同一 key 的最后一次 set 不被覆盖」,不解决业务语义上的竞态



















