Webman中Redis缓存失效主因是连接未复用、Key无版本、缓存穿透未防护;须用连接池、删缓存而非更新、Key含业务域/签名/版本、加请求级缓存与分布式锁防穿透。

Webman 应用里 Redis 缓存没提效?大概率是连接没复用、Key 没带版本、缓存穿透没拦住——不是 Redis 不快,而是用法卡在 PHP-FPM 思维里。
Redis 连接必须走连接池,别 new Redis()
Webman 是常驻进程模型,每个 Worker 长期存活。如果在控制器里写 new Redis() 或 redis_connect(),每次请求都新建连接,不出半天就会触发 TIME_WAIT 堆积、Redis 服务端拒绝新连接,甚至耗尽系统文件描述符。
正确做法是交由 webman/redis 扩展管理连接池:
- 安装:
composer require webman/redis - 配置
config/redis.php中的'pool' => ['min_connections' => 2, 'max_connections' => 20],避免空闲连接过多或高并发时取不到连接 - 业务中直接调用
Redis::get('key'),底层自动从池中取可用连接 - 绝对不要在
onWorkerStart里new Redis()后赋值给全局变量——多进程间无法安全共享资源,容易引发连接错乱或序列化失败
Cache-Aside 模式下,删缓存比更新缓存更安全
写操作时,先改 MySQL 再删 Redis key(Redis::del($key)),而不是 set 新值。这是防止双写不一致最简单有效的手段。
立即学习“PHP免费学习笔记(深入)”;
原因很实在:
- 更新缓存失败(比如网络抖动、序列化异常)会导致 Redis 里存着脏数据,且长期不会被覆盖 删除失败可补救:记录日志 + 异步重试队列(如 Webman 的
- 删操作幂等,重复执行无副作用;
set却可能因时序问题把旧数据又刷回去
timer 或 process),而更新失败几乎无法回滚
典型场景示例:Db::table('users')->where('id', $id)->update($data); Redis::del("user:profile:$id");
Key 必须含业务域、参数签名和版本号
写死 Cache::set('user_list', $data) 是线上事故高发点:换分页逻辑、加筛选条件、切环境后缓存全乱套。
Key 设计要能自我说明上下文和生命周期:
- 业务域前缀(如
user:、product:)隔离不同模块 - 参数签名用
md5(json_encode([$page, $size, $type])),不用serialize()——PHP 版本升级可能让反序列化失败 - 显式带版本号(如
:v2),一改就全量失效,比遍历KEYS user:*安全可控 - 敏感字段(如用户 ID)不直接拼进 key,防缓存探测攻击;可用
hash_hmac('sha256', $uid, $secret)匿名化
示例:$key = "user:list:type_{$type}:page_" . md5(json_encode([$page, $size])) . ":v2";
请求级缓存 + 分布式锁,防穿透不能只靠过期时间
缓存未命中 + 高并发查同一 key(比如爆款商品详情),大量请求会同时打到数据库,这就是缓存穿透。仅靠 setex 过期时间挡不住。
得在 Webman 里做两级防御:
- 请求内缓存:用
context()->get('cache')或静态变量暂存本次请求已查过的 key,避免同个请求多次查库 - 分布式锁兜底:查库前先
Redis::set('lock:user:123', 1, ['nx', 'ex' => 5]),成功才去查库并回填缓存;失败则sleep(10)后重试(注意别死循环) - 延迟双删可选:更新数据库后删一次,等主从同步完成(比如 100ms)再删一次,降低从库延迟导致的短暂不一致
最易被忽略的是:锁的 key 和业务 key 要分离,且过期时间必须大于业务查询耗时,否则锁提前释放,防穿失效。



















