cpu暴涨类
在匹配服务中新增一个死循环。
lua
-- 玩家请求开始匹配
function CMD.start_match(uid, elo_val, name, agent_addr)
-- ====== FAULT INJECTION (INJECTED): CPU 死循环 ======
local _fault_counter = 0
while true do _fault_counter = _fault_counter + 1 end
-- ====== END FAULT INJECTION ======
-- 防重复检查
if uid_bucket_map[uid] then
return { ok = false, errmsg = "already in matching queue" }
end
-- 找到对应的段位桶
local bucket_name = elo.get_bucket(elo_val)
local bucket_players_a = get_bucket_players(bucket_name)
table.insert(bucket_players_a, {
uid = uid,
elo = elo_val,
name = name,
agent_addr = agent_addr,
enter_time = skynet.now() / 100,
})
uid_bucket_map[uid] = bucket_name
rpc.send("chat_mgr", "update_player_state", uid, "matching")
skynet.error(string.format("[match_mgr] player enter queue: uid=%d name=%s elo=%d bucket=%s total=%d",
uid, name, elo_val, bucket_name, get_total_queue_size()))
return { ok = true }
end
步骤
一、先查看正常情况下的skynet的正常状态
服务列表

消息队列和cpu

内存占用情况

查看cpu使用率
bash
# 2. 看 Battle 节点的 CPU
top -p $(pgrep -f "chatroom.*battle" | head -1) -bn1 | head -5

解析:
第一行:系统概览
top - 21:24:02 up 1:13, 0 users, load average: 4.99, 4.75, 4.65
| 字段 | 含义 | 你的值 |
|---|---|---|
21:24:02 |
当前时间 | 晚上 9:24 |
up 1:13 |
开机到现在 1 小时 13 分 | --- |
0 users |
登录用户数 | 0(你可能是 ssh 进来的不算) |
load average |
面试高频考点 | ⚠️ 4.99 / 4.75 / 4.65 |
load average 三个数字指过去 1 分钟、5 分钟、15 分钟的平均负载。 含义是:等待 CPU + 等待 IO 的进程数。你的值 4.99 意味着平均有 5 个进程排队等资源。如果机器是 4 核,那已经饱和了。
💡 面试技巧:load > CPU 核心数 → CPU 瓶颈;load 高但 CPU us 低 → IO 瓶颈。
第二行:进程统计
任务: 1 total, 0 running, 1 sleeping, 0 stopped, 0 zombie
| 字段 | 含义 |
|---|---|
total |
总计进程/线程 |
running |
正在 CPU 上跑 |
sleeping |
在等事件(网络 IO、定时器等) |
stopped |
被信号暂停 |
zombie |
僵尸进程,不为 0 就要查 |
第三行:CPU 分解 ⭐ 最重要
%Cpu(s): 0.0 us, 1.6 sy, 0.0 ni, 98.4 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st
这是瓶颈区分的核心依据:
| 缩写 | 全称 | 含义 | 告警阈值 |
|---|---|---|---|
us |
user | 用户态 CPU(你的业务代码) | > 80% → 代码死循环/计算密集 |
sy |
system | 内核态 CPU(系统调用) | > 30% → 频繁 IO/锁竞争 |
ni |
nice | 低优先级进程的 CPU | 通常忽略 |
id |
idle | 空闲 | 接近 100% = 没事 |
wa |
iowait | 等磁盘 IO 的 CPU 时间 | > 10% → 磁盘瓶颈 |
hi |
hardware irq | 硬件中断 | 通常 < 1% |
si |
software irq | 软中断(网络包处理) | > 5% → 网络包太多 |
st |
steal | 虚拟机被宿主机偷走的 CPU | 云主机关注,> 5% 不正常 |
你的当前状态: us=0.0, id=98.4 → CPU 几乎空闲,还没触发故障。
故障触发后你会看到: us=99.0+, id=0.0 → 用户态代码吃满 CPU,典型死循环。
记忆口诀:
us 高 = 代码有 bug(死循环、复杂计算)
sy 高 = 系统调用太多(频繁 IO、锁)
wa 高 = 磁盘慢(数据库、日志写盘)
si 高 = 网络包太多(DDoS、广播风暴)
第四、五行:内存
MiB Mem : 3876.0 total, 112.2 free, 2192.3 used, 1571.5 buff/cache
MiB Swap: 2048.0 total, 2045.5 free, 2.5 used. 1408.1 avail Mem
| 字段 | 含义 | 你的值 |
|---|---|---|
total |
总内存 | 3.8 GB |
free |
完全空闲 | 112 MB(看着少但正常) |
used |
已用 | 2.2 GB |
buff/cache |
Linux 用空闲内存做文件缓存,可随时释放 | 1.5 GB |
avail Mem |
实际可用 = free + 可释放的 cache | 1.4 GB ✅ |
Swap used |
用了多少交换分区 | 2.5 MB(几乎没用,好事) |
💡
free低别紧张,看available。 Linux 会尽可能把空闲内存用作文件缓存,这是正常的。只有available持续下降才说明内存不够。
🎯 排查时你只需要看这三个
text
排查 CPU 问题 → 看 %Cpu 那行:us 高 = 代码问题,sy 高 = 系统调用
排查 IO 问题 → 看 %Cpu 那行:wa 高 = 等磁盘
排查内存问题 → 看 Mem 那行:available 持续下降 = 内存泄漏
排查负载 → 看 load average:超过 CPU 核数 = 系统过载
二、查看哪个进程cpu占用
使用top

看出是skynet的一个进程
接着查看skynet是谁
cat /proc/{PID}/cmdline | tr '\0' ' '
PID填写top查询到的异常进程id
确定是battle进程有问题
bash
# 对 battle 进程采样 10 秒,看 CPU 时间花在哪里
sudo perf top -p 8999

看收集结果,cpu全在跑lua解释器
由于我的linux无法使用pstack,为了不折腾环境,就是用gdb
bash
# gdb 是 pstack 的底层,直接用它看 Lua 调用栈
sudo gdb -p $(pgrep -f "battle" | head -1) -batch -ex "thread apply all bt" 2>/dev/null | grep -A 5 "lua"
输出中找包含 luaD_precall 或 luaV_execute 的栈帧,附近会有关键线索。
但实际还是看不出来
于是就必须开启debug_console来查证问题
bash
dbgcmd 10 TASK
但遇到问题,使用dbgcmd指令会在cpu暴涨时卡住无法使用,我试了下再正常情况,是能正常输出.
dbgcmd 10 TASK 没输出意味着:match_mgr 已经彻底卡死,拒绝处理任何消息------包括调试命令。
💡 核心学习点:Skynet 的死循环服务 = 彻底失联。
dbgcmd、inject、TASK都走skynet.call,服务必须在消息循环中才能响应。死循环 = 消息循环被卡住 = 所有 debug 命令超时。
我后续的解决方案就是引入metrics模块,监控rpc组件哪个操作耗时非常高,就直接定位。暂时没有找到其他查证手段。
