不推荐直接用 Redis 的 SET/HSET 存原始日志,应使用 LPUSH+BRPOP 模拟队列,配合后台消费者批量写入中心存储,并开启 AOF、设置超时、优雅退出、连接数监控及消费者存活告警。

Redis 作为日志暂存队列是否可靠
不推荐直接用 Redis 的 SET 或 HSET 存原始日志行——它不是消息队列,没 ACK、没重试、没消费位点,进程崩溃或网络抖动时日志就丢了。
真正可行的做法是把 Redis 当作轻量级缓冲池,用 LPUSH + BRPOP 模拟简易队列,配合后台常驻脚本(如 ThinkPHP 的 command)定时 RPOP 批量写入中心存储(如 ELK、MySQL 或文件)。这样既利用了 Redis 的高性能写入,又规避了单点丢失风险。
- 必须设置
timeout参数(如BRPOP log:queue 5),避免消费者永久阻塞 - 批量拉取时建议
LRANGE+LTRIM组合,比多次RPOP更少网络往返 - Redis 实例需开启
appendonly yes,否则宕机后缓冲日志全丢
ThinkPHP 日志驱动如何对接 Redis
原生 think-logging 不支持 Redis 目标,得自己写一个 RedisHandler 类继承 think\log\driver\Driver,核心是重写 save() 方法:把日志格式化为字符串后 LPUSH 到指定 key,而不是写本地文件。
注意别在 save() 里做耗时操作(如直连 Redis 写磁盘),否则拖慢业务响应。应该只做序列化 + 入队,由独立进程消费。
立即学习“PHP免费学习笔记(深入)”;
- 日志结构建议用 JSON 字符串,含
level、message、time、server_ip(多实例需区分来源) - key 命名要带环境前缀,比如
log:prod:queue,避免测试服和生产服混写 - 不要在
__construct()里初始化 Redis 连接,应延迟到首次save()时,防止配置错误导致应用启动失败
多实例并发写 Redis 队列会不会丢日志
会,但不是因为 Redis 并发问题,而是 PHP-FPM 进程各自缓存日志、未及时刷入 Redis 导致的。ThinkPHP 默认有日志缓冲(buffer 配置),如果设成 100,那每 100 条才 LPUSH 一次,进程突然退出(如超时 kill)就会丢最后一批。
解决办法不是关缓冲(性能掉得厉害),而是加一层“优雅退出钩子”:监听 SIGTERM,在进程结束前强制调用 flush()。
- 在命令行启动的守护进程中,可用
pcntl_signal()注册信号处理器 - Web 场景下无法捕获 SIGTERM,只能靠缩短缓冲阈值(如设为
10)+ 提高LPUSH失败重试次数 - 务必检查 Redis 连接数上限(
maxclients),20 台服务器 × 每台 50 个 PHP 进程 = 1000 连接,别让 Redis 拒绝新连接
聚合后的日志怎么查才不卡
别直接在 Redis 里查——LRANGE log:queue 0 -1 是 O(N) 操作,队列一过万条就明显延迟。聚合动作本身应由消费者完成:从 Redis 拉出日志,解析时间戳和 level,按 date +%Y%m%d 分表写入 MySQL,或推到 logstash 走 ES。
临时排查时,可以用 redis-cli --scan --pattern 'log:prod:*' | xargs -n 1 redis-cli LLEN 快速看各队列积压量,比全量读快得多。
- ES 索引名建议按天滚动(
logs-2024.06.15),别用单个大索引 - MySQL 表必须给
create_time加索引,且查询时带上时间范围条件,否则SELECT * FROM logs WHERE level='error'会扫全表 - Redis 中的原始日志保留不超过 2 小时,靠
EXPIRE自动清理,避免内存涨爆
最常被忽略的是消费者进程的存活监控——它挂了,日志就永远卡在 Redis 里。得配个简单的 ps aux | grep consumer.php + 报警,别指望它自己会复活。



















