常见问题
消息堆积 / 服务延迟飙高
现象
某个服务 mq长度持续增长,skynet.stat看到 mqlen很大,同节点其他服务也跟着抖。
根因
-
单条 callback 太重:在 skynet.dispatch里做了同步重计算(协议编解码、JSON 序列化、大表遍历),阻塞了 Worker 线程,这条 mq 里的后续消息只能排队。
-
skynet.call链式 / 广播滥用:A→B→C→D 一长串 call,或者一广播就是几十个服务,发送方被全程挂起,接收方 mq 瞬间打满。
-
跨节点 call 没设 timeout:对方节点抖动,skynet.call的 coroutine 一直挂,session 映射表和 mq 都堆。
优化
-
重计算拆出去:CPU 密集型活丢给专用的"计算服"做 skynet.send+ 异步回传,或切 C 模块用 skynet_worker(如果有)甚至外部进程。
-
分帧 / fork:Lua 层大循环 for i=1,1e6 do ... end改成每帧处理一段,skynet.fork让出。
-
call 必带 timeout:skynet.call(addr, "lua", ..., 100)(0.6+ 语法)或外层用 pcall + timeout兜底。
-
广播用 multicast或 clusterd 批量,别自己 for 循环 send。
Lua GC 压力大
现象
-
服务 cpu占用里 GC 占比 > 30%
-
每隔几秒一次"微卡顿",时延 P99 跳
-
内存缓慢涨,不降
根因
-
每服务一个 LuaVM:Skynet 的 Lua 服务本质是 snlua,每个都独立 VM、独立 GC。服务多了(几千个 agent)GC 总开销线性放大。
-
临时对象疯狂造:skynet.call每次生成新 session 表、closure;协议 unpack 每消息 new 一个 table;字符串拼接用 ...。
-
热服务消息 QPS 高:网关、聊天、排行榜这类每秒上千消息的服,GC 被反复触发。
优化
-
对象复用:
-
协议层用 protobuf/sproto的 decode复用 table,或手写 C 解码。
-
session/coroutine 映射表定期清理,避免闭包捕获大对象。
-
-
LuaJIT 开起来:确认编译开了 LUAJIT_ENABLE,JIT 能把热循环编成机器码,GC 压力也间接降。
-
合批处理:网关类服务把多条消息攒一波再 dispatch,减少 per-message 的 Lua 调用次数。
-
超大服拆小:单服 agent 过多时,按 uid 区间拆多个 snlua 进程,GC 并行度反而上来了。
全局消息队列(global_queue)争抢
现象
-
Worker 线程数开得多(≥ 8)时,吞吐量不再涨,甚至略降
-
perf top看到 spinlock/ atomic相关指令占比高
根因
global_queue 是无锁循环数组 + CAS 实现的(3.x 后改成了更轻量的版本,但高并发下仍有争抢)。当:
-
Worker 数 ≈ 核数 × 2
-
单节点 QPS 极高(> 50w msg/s)
-
大量服务 mq 频繁入队(Timer/Socket 两个生产者狂 push)
多个 Worker 同时 pop+ Timer/Socket 同时 push,CAS 失败重试变多。
优化
-
Worker 数 = CPU 物理核数(别超,超了反而自旋空转)。
-
热点服务拆分到不同节点,让 global_queue 的入队源少一个 Socket/Timer 集中点。
-
3.x 之后确认用的是新版本,老版 2.x 的 global_queue 锁更重。
Socket 层瓶颈
现象
-
连接数上了 1w+,CPU 花在 socket_server上多
-
收发包延迟涨,但业务服 mq 并不满
根因
-
每一帧 Socket 线程把所有 fd 事件转成消息 push 到 snlua-gate的 mq,gate 服瞬间被冲。
-
socket.write大包阻塞:单个包 > 64KB 或 send buffer 满时,Socket 线程会自旋等。
-
TCP_NODELAY / 缓冲区没调:小包频发时 Nagle + Lua 层频繁 write 来回折腾。
优化
-
gate 服分片:按连接 ID % N 拆多个 gate 服务,每个管一部分 fd。
-
合包 / 限流:Lua 层协议加长度前缀,避免半包反复 callback;上行消息频率做简单 throttle。
-
socket 参数调优:socket.limit控制 send buffer;内核侧 somaxconn、tcp_mem一起调。
Timer 模块抖动
现象
-
大量短周期 timer(比如每 100ms 一个 agent 心跳),某帧突然卡 10ms+
-
skynet.timeout精度漂移
根因
Skynet 定时器是四级时间轮(近似 Linux 实现),单线程(Timer 线程)扫。当:
-
同一时刻到期 timer 太多(比如 1w 个 agent 同时加的 1s 超时)
-
timer 回调里又 skynet.call或做重活
Timer 线程这一帧就拖长,间接影响全局消息生产节奏。
优化
-
timer 回调只做轻量事:发一条消息给 Worker 服去干活,别在 timer 线程里 call。
-
同类 timer 合并:1w 个 agent 各自 1s 心跳 → 改成一个"心跳服" 1s 触发一次,再 send通知所有 agent(agent 只注册一次,用个计数器就行)。
-
长周期用 cron思路,别用大量 100ms 短 timer 轮询。
跨节点(Cluster)性能问题
现象
-
跨节点 call延迟比本地高一个数量级
-
集群间带宽打满
根因
-
序列化开销:cluster 默认用 Lua Table 序列化,跨机每消息编解码一次。
-
connection 单 TCP 连接:默认两个节点间只有一条 TCP,所有服务共享,头部争抢 + 串行化。
-
没做 batch:高频小消息一条一条发,TCP 小包多。
优化
-
换 sproto / protobuf 做 cluster 序列化,比原生 table 序列化省 CPU 也省带宽。
-
高频通道拆独立 connection(3.x 支持多 harbor 或多 TCP 通道)。
-
批量 call:多个小请求攒一波,一次 send+ 一次回包。
排查工具链
-
skynet.stat()------ 看 mqlen、cpu、gc 时间
-
skynet.mem_limit()------ 看内存
-
profile模块 ------ Lua 层函数级耗时
-
debug控制台:list、stat、mem、netstat
-
perf top/ perf record------ C 层热点(global_queue 自旋、socket_server 等)
-
jeprof(LuaJIT) ------ 内存泄漏定位