学完这篇,你能在 PHP 里把 Redis 的批量执行、事务和消息推送三件事都用对,知道每种模式该用在哪、哪些坑会让你半夜排障。
第一步:先把连接参数配好
用 phpredis 扩展(pecl install redis 或宝塔面板的 PHP 扩展里勾选 redis),连上并设好全局选项:
$redis = new Redis();
$redis->connect('127.0.0.1', 6379, 1.5); // 第三个参数是连接超时(秒)
$redis->auth('你的密码');
$redis->select(0);
$redis->setOption(Redis::OPT_PREFIX, 'bbs:'); // 统一键前缀
$redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
注意:OPT_PREFIX 会同时作用到 pub/sub 的频道名上。如果你的项目统一加了前缀,另一个没加前缀的服务就订阅不到消息。跨服务通信用频道时,要么显式写全名,要么在发布/订阅前 setOption(Redis::OPT_PREFIX, '')。
第二步:Pipeline——省的是往返时间
一次请求里要写 1000 个键,逐条 set 就是 1000 次网络往返。Pipeline 把命令攒起来一次性发:
$redis->multi(Redis::PIPELINE);
for ($i = 1; $i <= 1000; $i++) {
$redis->set("hot:$i", $i);
$redis->expire("hot:$i", 3600);
}
$res = $redis->exec(); // 返回 2000 条结果的数组
关键点:Pipeline 模式下命令是立即发送的(不是等 exec 才发),exec() 只是收集回包。
注意:Pipeline 里后面命令不能依赖前面命令的返回值。比如「先 incr 再根据结果 set」在 Pipeline 里做不到,得拆成两批或用 Lua。
第三步:事务——MULTI/EXEC 与 WATCH 乐观锁
$redis->watch('stock:1001');
$stock = (int)$redis->get('stock:1001');
if ($stock <= 0) {
$redis->unwatch(); // 放弃监视,必须调用
return '已售罄';
}
$redis->multi(); // 默认就是 Redis::MULTI
$redis->decr('stock:1001');
$redis->rPush('order:log', $userId);
$res = $redis->exec(); // 期间 stock:1001 被改过则返回 false
if ($res === false) {
return '并发冲突,请重试';
}
注意:Redis 事务没有回滚。命令语法错误会让 exec() 直接返回 false(整个事务不执行);但像对字符串执行 incr 这类运行时错误,只有那一条失败,其余照常生效。所以别把它当 MySQL 事务用。
第四步:发布订阅——必须跑在常驻进程里
发布端很简单:
$redis->publish('chan:order', json_encode(['orderId' => 123]));
订阅端是阻塞的,只能放命令行常驻脚本(php subscribe.php),不能放在 web 请求里:
$redis->setOption(Redis::OPT_READ_TIMEOUT, -1); // 永不读超时,防止 60 秒后断开
$redis->subscribe(['chan:order'], function ($redis, $chan, $msg) {
try {
handle($msg);
} catch (Throwable $e) {
error_log($e->getMessage()); // 异常必须自己吞掉,否则整个订阅循环退出
}
});
配 supervisor 或 systemd 守护,进程挂掉自动拉起。
注意:Redis 发布订阅是「发完就忘」,订阅端掉线期间的消息全部丢失,它不适合当可靠队列。要保证不丢,用 Redis 5+ 的 Stream(XADD/XREADGROUP + 消费组 + ACK)。
第五步:接进业务时的边界
如果你在维护类似 Clara BBS 这类自带缓存层的系统,业务代码里一般不需要自己 new Redis——后台「系统工具 → 缓存设置」可视化开启 Redis 即可,榜单、热点数据统一走 Cache::remember 一行式调用,键自带表前缀命名空间,多站共用一个 Redis 实例也不串。此时要注意两点:
- Redis 异常会自动降级到文件驱动,业务零感知,所以别把强一致逻辑压在缓存层上;
- Pipeline / 事务这类需要原生连接的操作,要单独封装并自己处理连接失败,不要假设缓存层永远可用。
小结
- Pipeline 解决网络往返,命令之间不能有依赖;批量写、批量读首选它。
- 事务靠
WATCH 做乐观锁,exec() 返回 false 就是冲突重试;它不支持回滚。
- 发布订阅只能跑常驻进程,读完设置
OPT_READ_TIMEOUT = -1,回调里必须 try/catch。
- 要可靠投递就用 Stream,不要用 pub/sub。
- 统配
OPT_PREFIX 时记得它会污染频道名。