Skynet 日志雪崩怎么破:3 个配置项加 1 份 logrotate 把日志管住

发布时间:2026/9/12 5:40:05
Skynet 日志雪崩怎么破:3 个配置项加 1 份 logrotate 把日志管住 Skynet 日志雪崩怎么破3 个配置项加 1 份 logrotate 把日志管住【免费下载链接】skynetA lightweight online game framework项目地址: https://gitcode.com/GitHub_Trending/sk/skynet凌晨两点磁盘报警。你登上一台跑了一周的线上机ls -lh一看单个日志文件 200GB还在涨更糟的是游戏逻辑线程时快时慢strace一抓多个服务在同一个文件上排队等 I/O。这是用 skynet轻量级在线游戏框架搭服务时最容易翻车的场景日志没有轮转写日志还写在业务线程里。skynet 的解法不是内置一个按大小切割的轮转器而是三件事的组合——C 层的 logger 服务统一收日志、SIGHUP 触发的重开文件钩子、外加系统自带的 logrotate 负责切割和清理。本文先把为什么常规写法行不通讲清楚再拆开 skynet 的日志链路最后给你一份能直接照抄的配置组合。先看清楚问题到底卡在哪直觉上大家会踩三个坑每个都对应 skynet 架构里一个真实的约束。坑 1在服务里自己写文件最自然的写法是在每个 Lua 服务里io.open一个日志文件打一条写一条。问题是 skynet 的服务跑在固定数量的 worker 线程上一个服务阻塞在 I/O 上它所在线程上的其他服务一起卡住。再叠加 append 模式写同一文件的互斥竞争日志越多系统越慢——这就是日志雪崩里雪崩的真正含义日志反过来拖垮产生日志的人。坑 2多线程往同一个文件里写就算你给每个服务加了锁skynet 的多个 worker 线程并发写一个 fd 时write不保证原子性两行日志可能交错成半行加半行。要保证顺序正确就必须有单点串行写这个结构存在——这恰恰是 skynet 默认提供的。坑 3以为轮转是框架内置的很多人找了一圈配置项没找到按 100MB 切割的开关于是自己写定时任务 rename 文件。这条路走不通logger 进程一直握着旧的文件描述符rename 之后它继续往已经被搬走的 inode 里写新文件永远空的旧文件却改名了还在涨。切割动作必须让进程重开文件才能生效这一点决定了 skynet 把钩子留在了系统信号上后面细讲。它是怎么做到的一句话概括设计思想把日志 I/O 从所有业务服务里拿掉收敛成一个专用服务的串行输入。单点收口logger 服务skynet 启动时先拉起 logger 服务再跑 bootstrap见 skynet-src/skynet_start.c 的skynet_start。此后任何服务调skynet_errorC 层skynet-src/skynet_error.c把消息格式化好往名为 logger 的服务推一条PTYPE_TEXT消息就返回——发送方不碰任何文件。logger 服务是普通服务被某个 worker 线程串行调度所以所有日志写入天然是单线程的没有交错也没有跨服务的锁竞争。每行落盘fflushlogger 收到消息后拼上时间戳精确到 1/100 秒和来源服务地址写入后立刻fflush实现见 service-src/service_logger.c。好处是进程被 kill -9 也只丢正在写的那半行代价是每条日志一次刷盘这是它的天花板也是后面对话术的伏笔。SIGHUP 重开内置的轮转钩子timer 线程每 2.5ms 检查一次信号标志发现 SIGHUP 后给 logger 发一条PTYPE_SYSTEM消息logger 收到后用freopen按原路径重新打开文件append 模式。于是轮转可以这样闭环logrotate 先copytruncate备份并清空当前文件再kill -HUP主进程logger 重开后所有写入落到新文件不丢不串。logservice把 logger 整个换掉默认 logger 是 C 服务只认PTYPE_TEXT。配置里加一行logservice snlua就能换成 Lua 服务日志变成lua类型消息你在 Lua 里按服务地址分流到不同文件、对接外部采集都随你意图仓库里现成的例子examples/config.userlog 配了logger userloglogservice snlua。动手实践做把日志从 stdout 切到文件默认logger nil日志走标准输出。改配置文件两处即可logger skynet.log logpath ./logs看结果重启后每条日志形如11/09/26 02:14:33.41 [:0000000a] ...时间加来源服务号前缀tail -f能看到滚动输出。做挂一份 logrotate给日志文件配一个 logrotate 规则关键就三个点必须用copytruncate把备份清空合并成一步避免 create 模式换 inode 后 logger 还在写旧文件、压缩归档、限定保留份数./skynet.log { weekly rotate 4 copytruncate compress }看结果一周后同目录出现.gz归档当前文件始终很小postrotate里加kill -HUP pid还能触发重开双保险。做生产环境关掉二进制 dump配置里profile 0。profile默认开启每个服务会额外往logpath下写一份%08x.log的十六进制消息流水skynet-src/skynet_log.c排查协议时有用日常线上就是纯开销。看结果重启后logpath下不再新增按服务号命名的.log文件文本日志不受影响skynet_error的输出照常进skynet.log。不同场景怎么调| 场景 | logger | logrotate 策略 | 说明 | | 本地开发 |nil走 stdout | 不配 | 终端直接看配合daemon都没必要 | | 单机日常线上 | 文件路径 | 每周切、rotate 4、compress | 磁盘可控回溯一周 | | 高流量/集群 | 文件路径 | 每天切、rotate 14、compresspostrotate 发 HUP | 单文件当天量封顶日志异常暴涨先查刷屏的服务号 |一句话总结取舍切割频率决定单个文件上限保留份数决定磁盘总量两者相乘就是你给日志留的预算。容易踩的坑文件越写越大回头一看根本没配 logrotate。原因C 版 logger 以 append 打开文件没有内置大小上限它只负责写。解法挂上 logrotate并把它当成日志方案的一部分而不是可选项。配了 create 模式切割后日志消失了。原因rename 之后 logger 的句柄还指向旧 inode新文件永远收不到数据。解法改用copytruncate或保留 create 但在 postrotate 发 SIGHUP 触发重开。升级后日志静默没了进程却还活着。原因logger 服务注册失败时skynet_error找不到 logger 这个名字会直接丢弃消息skynet-src/skynet_error.c 里logger 0分支。解法先ps确认进程存在再怀疑框架然后检查logservice/logger配置。profile 0之后连错误日志都没了。原因误以为profile是总开关它只管按服务的二进制流水文本日志始终走 logger 服务。解法文本日志丢失查 logger 配置和文件权限别往profile上想。凌晨那个 200GB 的文件用这套组合拳管住之后就不会再失控——业务服务只管发消息logger 只管串行写logrotate 只管切和清。想跑自定义 Lua 日志服务照着 examples/config.userlog 抄即可。【免费下载链接】skynetA lightweight online game framework项目地址: https://gitcode.com/GitHub_Trending/sk/skynet创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考