Skip to content

Linux 常用命令与排查

面试问 Linux 一般有两个目的:考察你日常是否真的用过服务器,以及遇到线上问题时有没有排查思路。

Q1: 怎么查看系统的 CPU、内存、负载? 「🟢 校招/初级」

考察点:最基础的监控命令。

参考答案

  • top / htop:实时查看 CPU、内存、进程占用;关注 load average(1/5/15 分钟)与核数的比值。
  • free -h:查看内存和 swap;注意 buffer/cache 也算"可用"。
  • vmstat 1:每秒输出一次,看上下文切换(cs)、中断、IO 等待(wa)。
  • uptime:快速看负载。

追问延伸

  • load average 高但 CPU 使用率不高,可能是什么原因?(IO 等待)
  • top 里的 %wa%si 分别代表什么?

Q2: 怎么排查一个 Java 进程 CPU 占用过高? 「🟡 中级」

考察点:经典的"三步定位法",出现频率极高。

参考答案

  1. top 找到高 CPU 的进程,记下 PID。
  2. top -Hp <pid> 找到高 CPU 的线程,记下 TID。
  3. printf "%x\n" <tid> 转十六进制,用 jstack <pid> | grep <十六进制tid> -A 20 定位到具体代码栈。
  • 工具替代:Arthas 的 thread -n 3 一步到位。

追问延伸

  • 如果发现是 GC 线程占 CPU 高,接下来怎么查?(jstat/jmap 看内存)
  • Go 程序怎么排查?(pprof)

Q3: 磁盘和文件相关命令? 「🟢 校招/初级」

考察点:日常运维操作。

参考答案

  • df -h:查看各分区磁盘使用率;du -sh *:统计当前目录各子目录大小。
  • ls -lhfind / -name "*.log" -size +100M:查找大文件。
  • tail -f app.log:实时看日志;grep -C 3 ERROR app.log:带上下文搜索;less 分页。
  • ln -s:软链接;chmodchown:权限。

追问延伸

  • 磁盘满了但 du 统计对不上,可能是什么原因?(已删除但未释放的文件句柄,lsof | grep deleted
  • 硬链接和软链接的区别?

Q4: 网络排查命令有哪些? 「🟡 中级」

考察点:定位网络问题的工具箱。

参考答案

  • curl -v:看完整请求响应过程;pingtraceroute:连通性与路径。
  • netstat -anp | grep <port> / ss -lntp:查看端口监听和连接状态。
  • dig / nslookup:DNS 解析验证。
  • tcpdump -i eth0 port 8080 -w dump.pcap:抓包,配合 Wireshark 分析。
  • lsof -i :8080:看哪个进程占用了端口。

追问延伸

  • ssnetstat 快在哪?
  • 大量 CLOSE_WAIT 连接说明什么问题?

Q5: 常用的进程与文本处理命令? 「🟢 校招/初级」

考察点:shell 基本功。

参考答案

  • 进程:ps -ef | grep javakill -15(优雅)/ kill -9(强制)、nohup& 后台。
  • 文本三剑客:grep(过滤)、sed(行编辑替换)、awk(按列统计)。
  • 示例:awk '{print $1}' access.log | sort | uniq -c | sort -rn | head 统计访问 IP Top10。

追问延伸

  • kill -9 为什么有时候杀不掉进程?(D 状态/僵尸进程)
  • 怎么让脚本异常退出时自动清理临时文件?(trap)

Q6: 怎么查看和管理 systemd 服务? 「🟡 中级」

考察点:现代 Linux 服务管理方式。

参考答案

  • systemctl status/start/stop/restart <service>:服务管理。
  • systemctl enable:开机自启;journalctl -u <service> -f:看服务日志。
  • 服务定义在 /etc/systemd/system/*.service,可配置重启策略、资源限制。

追问延伸

  • 和传统 init.d 脚本的区别?
  • 容器场景下还需要 systemd 吗?(一般用容器自身的进程管理)

Q7: ps 命令有哪些常用选项?展示了哪些信息? 「🟡 中级」

考察点:进程查看的基本功。

参考答案

常用选项:

  • ps -ef:查看所有进程,显示 UID、PID、PPID、C(CPU使用率)、STIME(启动时间)、TTY、TIME、CMD
  • ps aux:BSD 风格,显示 USER、PID、%CPU、%MEM、VSZ(虚拟内存)、RSS(物理内存)、STAT(状态)、START、TIME、COMMAND
  • ps -ef --forest:树形显示进程关系(父子进程)
  • ps -eLf:查看所有线程(LWP 列为线程 ID)

STAT 状态含义:

  • R:运行/就绪
  • S:可中断睡眠(等待事件)
  • D:不可中断睡眠(通常在等 IO,kill -9 也杀不掉)
  • Z:僵尸进程(父进程没回收)
  • T:暂停/被跟踪
  • +:前台进程组
  • s:会话组长
  • l:多线程
  • <:高优先级
  • N:低优先级

RSS vs VSZ:

  • VSZ:虚拟内存大小(含 swap 和映射文件,通常偏大)
  • RSS:常驻物理内存大小(实际占用的物理内存,更有参考价值)

追问延伸

  • 僵尸进程怎么处理?(找父进程 kill,或修复父进程的 wait 回收逻辑)
  • D 状态进程为什么 kill -9 无效?

Q8: 已知进程名,如何杀掉这个进程? 「🟢 校招/初级」

考察点:日常运维操作。

参考答案

方法一:两步走(先查 PID 再 kill)

bash
# 查 PID
ps -ef | grep nginx | grep -v grep
# 或
pgrep -f nginx

# 杀进程
kill -15 <pid>   # 优雅终止(SIGTERM,进程可以清理后退出)
kill -9 <pid>    # 强制杀死(SIGKILL,不可拦截)

方法二:一步到位

bash
pkill -f nginx          # 按进程名杀(发 SIGTERM)
pkill -9 -f nginx       # 强制杀(发 SIGKILL)
killall nginx           # 按精确进程名杀

kill 信号区别:

信号编号含义可拦截
SIGTERM15优雅终止
SIGKILL9强制杀死
SIGHUP1挂断/重载配置
SIGINT2Ctrl+C 中断
SIGQUIT3Ctrl+\ 退出

生产建议:先 kill -15,等 5-10 秒没退出再 kill -9

追问延伸

  • kill -9 杀不掉的进程怎么办?(D 状态等 IO,需修复 IO 或重启)
  • pkill 和 killall 的区别?

Q9: 怎么查看哪个端口被哪个进程占用?端口连通性怎么测? 「🟡 中级」

考察点:端口排查的常用命令。

参考答案

查看端口占用:

bash
# 方法1:lsof(最常用)
lsof -i :8080            # 查看 8080 端口被哪个进程占用

# 方法2:netstat
netstat -tunlp | grep 8080  # -t TCP -u UDP -n 不解析域名 -l 监听 -p 进程

# 方法3:ss(比 netstat 快)
ss -lntp | grep 8080

# 方法4:fuser
fuser 8080/tcp           # 查看占用 8080 的进程 PID

端口连通性测试:

bash
# TCP 端口
telnet 192.168.1.1 8080       # 旧但通用
nc -zv 192.168.1.1 8080       # netcat,推荐
curl -v telnet://192.168.1.1:8080  # curl 也可以

# UDP 端口
nc -zuv 192.168.1.1 161

# 批量扫描
nmap -p 80,443,8080 192.168.1.1

ss 比 netstat 快的原因:

  • netstat 遍历 /proc 下的所有 fd,进程多时很慢
  • ss 直接读取内核 socket 缓存,不依赖 /proc

追问延伸

  • 端口不通怎么排查?(防火墙、安全组、服务没启动、bind 地址不对)
  • TIME_WAIT 和 CLOSE_WAIT 大量出现分别说明什么?

Q10: top 和 free 都能看内存,有什么区别? 「🟡 中级」

考察点:系统监控工具的深入理解。

参考答案

free -h 输出:

              total        used        free      shared  buff/cache   available
Mem:           7.8G        2.1G        1.2G        0.1G        4.5G        5.3G
Swap:          2.0G          0B        2.0G
  • total:物理内存总量
  • used:已使用(不含 buffer/cache)
  • free:完全空闲
  • buff/cache:系统缓存(可以回收)
  • available:实际可用 ≈ free + 可回收的 buffer/cache

top 内存部分:

  • VIRT:虚拟内存总量(含映射文件、共享库,偏大,参考价值低)
  • RES:常驻物理内存(实际占用的物理内存,有参考价值)
  • SHR:共享内存

区别:

  • free 看系统整体内存,关注 available(不是 free!)
  • top 看单个进程内存,关注 RES
  • 判断内存够不够:看 available,不是 free(buffer/cache 是可以回收的)

内存是否够用的判断:

  • available > 总量的 20% → 够用
  • available < 总量的 10% → 需要关注
  • Swap 在使用 → 内存不够,系统在用交换分区(性能严重下降)
  • si/so(vmstat)持续大于 0 → 内存紧张

追问延伸

  • buffer 和 cache 的区别?(buffer 是写缓存,cache 是读缓存)
  • 为什么 free 很少但系统还正常?

Q11: 服务器 CPU 跑到 100%,排查思路是什么? 「🔴 高级」

考察点:线上故障排查的综合能力。

参考答案

排查流程(从整体到细节):

  1. 确认是哪种 CPU 100%

    • top%us(用户态)、%sy(内核态)、%wa(IO等待)、%si(软中断)
    • %us 高 → 应用代码问题(死循环、GC、加密计算)
    • %sy 高 → 系统调用频繁(大量线程切换、锁竞争)
    • %wa 高 → 磁盘 IO 瓶颈(不是 CPU 不够,是等 IO)
    • %si 高 → 网络中断频繁(大量小包、网卡多队列未开启)
  2. 定位到进程

    • top 按 CPU 排序,找到高 CPU 进程 PID
  3. 定位到线程(Java 应用):

    • top -Hp <pid> 找到高 CPU 线程 TID
    • printf "%x\n" <tid> 转十六进制
    • jstack <pid> | grep <hex_tid> -A 30 看线程栈
    • 或用 Arthas:thread -n 3
  4. 非 Java 应用

    • perf top:看 CPU 热点函数
    • strace -p <pid> -c:看系统调用统计
    • pidstat -t -p <pid> 1:按线程看 CPU
  5. 常见原因

    • 死循环 / 无限重试
    • Full GC 频繁(jstat -gc <pid> 1000
    • 正则表达式回溯爆炸
    • 大量日志写入
    • 加密/序列化消耗
    • 锁竞争导致大量自旋
  6. 临时缓解

    • 限流 / 降级
    • 重启进程(治标不治本)
    • 扩容到更多节点

追问延伸

  • %wa 高但 CPU 不忙,怎么排查磁盘 IO?
  • 怎么区分是 CPU 不够还是应用有 bug?

Q12: Git rebase 和 merge 有什么区别? 「🟡 中级」

考察点:Git 工作流的理解和选型。

参考答案

核心区别:

  • merge:把两个分支的修改合并成一次新提交,保留所有历史记录

    • 产生一个 merge commit(有多个父提交)
    • 历史记录是"有向无环图"(非线性的)
    • 优点:历史真实完整、操作安全
    • 缺点:历史记录杂乱(大量 merge commit)
  • rebase:把当前分支的提交"搬到"目标分支的最新提交之后

    • 变基:把当前分支的每个 commit 重新应用到目标分支末尾
    • 历史记录是线性的(干净整洁)
    • 优点:历史简洁清晰
    • 缺点:重写了历史(commit hash 变了),有冲突时要逐个 commit 解决

图示对比:

# merge
  A---B---C feature
 /         \
*---D---E---M main

# rebase
  A'--B'--C' feature
 /
*---D---E main

使用场景:

  • merge:feature 分支合入 main(保留 feature 的历史)
  • rebase:feature 分支拉取 main 的最新代码(保持 feature 历史线性)
  • 团队规范:很多团队要求 PR 合并前先 rebase main,保持历史干净

黄金法则:不要 rebase 已经推送到远程且其他人正在使用的分支(会覆盖别人的历史)。

bash
# merge
git checkout main
git merge feature

# rebase
git checkout feature
git rebase main
# 解决冲突后
git rebase --continue

Squash Merge(第三种选择):

  • git merge --squash feature:把 feature 的多个 commit 压缩成一个
  • 适合:feature 分支有很多临时 commit,合并时只想保留一个干净的 commit

追问延伸

  • rebase 有冲突怎么办?(逐个 commit 解决,git rebase --continue
  • cherry-pick 和 rebase 的区别?

Q13: Git 怎么合并分支?冲突了怎么解决? 「🟢 校招/初级」

考察点:日常 Git 操作。

参考答案

合并分支:

bash
# 切到目标分支
git checkout main
# 合并 feature 分支
git merge feature

冲突解决流程:

  1. Git 报冲突,文件中标记冲突区域:
<<<<<<< HEAD
当前分支的代码
=======
被合并分支的代码
>>>>>>> feature
  1. 手动编辑文件,保留正确的代码,删除冲突标记
  2. 标记冲突已解决:
bash
git add <冲突文>
  1. 继续合并:
bash
git commit            # merge 方式
git rebase --continue # rebase 方式

冲突产生的本质:两个分支修改了同一文件的同一区域,Git 无法自动决定用哪个。

减少冲突的技巧:

  • 频繁拉取主干代码(git pull --rebase
  • 拆分大 PR,减少改动范围
  • 团队分工明确,避免改同一文件
  • 代码格式化工具统一格式

追问延伸

  • 怎么撤销一个有冲突的 merge?(git merge --abort
  • 怎么查看冲突的文件列表?(git status

Q14: 怎么撤回已经提交的 Git commit? 「🟡 中级」

考察点:Git 历史修改的操作能力。

参考答案

三种撤销方式:

  1. git reset(最常用):
bash
# 撤回最近1个 commit,保留修改在工作区
git reset HEAD~1             # 默认 --mixed

# 撤回最近1个 commit,修改放回暂存区
git reset --soft HEAD~1

# 撤回最近1个 commit,丢弃所有修改(危险!)
git reset --hard HEAD~1
  • 适用:还没 push 的 commit
  • 注意:--hard 会丢失代码
  1. git revert(安全撤销):
bash
# 生成一个反向 commit,撤销指定 commit 的修改
git revert <commit-hash>
  • 适用:已经 push 的 commit(不改写历史,新增一个"撤销"提交)
  • 安全:多人协作时用这个
  1. git commit --amend(修改最后一次提交):
bash
# 修改最后一次 commit 的信息或追加文件
git add <遗漏的文>
git commit --amend -m "新的commit信息"
  • 适用:还没 push,想修改 commit 信息或补充文件
  • 注意:amend 会改变 commit hash

对比:

方式是否改写历史是否安全适用场景
reset --soft本地安全撤回未push的commit,保留修改
reset --hard危险完全丢弃修改
revert最安全撤回已push的commit
amend本地安全修改未push的commit

已 push 的 commit 撤销:

  • 推荐 git revert(不改写历史)
  • 如果是个人分支可以 git reset --hard + git push --force(覆盖远程)
  • 绝对不要 force push 到 main/master 分支

追问延伸

  • git reflog 是什么?(记录所有 HEAD 变更,可以恢复误操作)
  • 怎么找回 reset --hard 丢弃的代码?(git reflog 找到之前的 commit hash)

Q15: Linux 文件权限体系是怎样的?特殊权限位有哪些? 「🟡 中级」

考察点:对 Linux 权限模型的的理解深度。

参考答案

基本权限:

$ ls -l file
-rwxr-xr-- 1 root root 1024 Aug 24 10:00 file
 │└─┬─┘└─┬─┘└─┬─┘
 │  │    │    └── 其他用户(Other):r--
 │  │    └────── 组用户(Group):r-x
 │  └────────── 所有者(User):rwx
 └───────────── 文件类型(- 普通文件,d 目录,l 软链接)

权限含义:

权限文件目录
r读取内容列出目录内容(ls)
w修改内容在目录中创建/删除文件
x执行进入目录(cd)

chmod 数字表示法:

bash
chmod 755 file    # rwxr-xr-x
chmod 644 file    # rw-r--r--
chmod -R 750 dir  # 递归设置目录权限
  • r=4, w=2, x=1,三者之和就是数字

特殊权限位(3 个):

  1. SUID(Set UID)= 4

    • 作用在可执行文件上:执行时以文件所有者身份运行
    • 典型:/usr/bin/passwd(普通用户执行时以 root 身份修改密码)
    • chmod u+s filechmod 4755 file
  2. SGID(Set GID)= 2

    • 作用在目录上:在该目录下创建的文件继承目录的组
    • 典型:共享目录,所有人创建的文件都属于同一组
    • chmod g+s dirchmod 2755 dir
  3. Sticky Bit = 1

    • 作用在目录上:只有文件所有者和 root 才能删除该目录下的文件
    • 典型:/tmp 目录,所有人可写但只能删自己的文件
    • chmod o+t dirchmod 1777 dir

umask(默认权限掩码):

  • 文件默认权限 = 666 - umask(如 umask=022 → 644)
  • 目录默认权限 = 777 - umask(如 umask=022 → 755)
  • umask 027 设置临时 umask

追问延伸

  • 为什么 passwd 命令普通用户可以修改 /etc/shadow?(SUID 机制)
  • /tmp 目录权限是 1777,1 代表什么?

Q16: 写一个 Shell 脚本,统计 Nginx access.log 中访问量最高的 10 个 IP 「🟢 校招/初级」

考察点:文本处理能力 + Shell 基本功。

参考答案

bash
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10

逐步解析:

  1. awk '{print $1}':提取第一列(IP 地址),以空格/Tab 分隔
  2. sort:排序(相同 IP 排在一起,为 uniq 做准备)
  3. uniq -c:去重并统计次数(-c 表示 count)
  4. sort -rn:按数字倒序排序(-r reverse,-n numeric)
  5. head -10:取前 10 行

Shell 脚本基础语法:

bash
#!/bin/bash

# 变量
name="hello"      # 赋值,等号两边不能有空格
echo $name        # 使用变量
echo ${name}world # 字符串拼接

# 条件判断
if [ -f /etc/passwd ]; then
    echo "file exists"
elif [ -d /tmp ]; then
    echo "dir exists"
else
    echo "not found"
fi

# 循环
for i in 1 2 3 4 5; do
    echo $i
done

for ip in $(cat ips.txt); do
    ping -c 1 $ip > /dev/null && echo "$ip OK" || echo "$ip FAIL"
done

while read line; do
    echo $line
done < file.txt

# 函数
check_port() {
    local port=$1
    ss -lntp | grep ":$port " > /dev/null && return 0 || return 1
}

check_port 8080 && echo "running" || echo "not running"

# 数组
arr=(apple banana cherry)
echo ${arr[0]}     # apple
echo ${arr[@]}     # 所有元素
echo ${#arr[@]}    # 数组长度

常用内置变量:

变量含义
$0脚本名
$1-$9位置参数
$#参数个数
$@所有参数
$?上一条命令退出码(0 成功)
$$当前进程 PID
$!后台进程 PID

追问延伸

  • [ ][[ ]] 的区别?([[ ]] 是 Bash 扩展,支持通配符匹配和逻辑运算符)
  • 脚本调试怎么做?(bash -x script.sh 显示每步执行过程)

Q17: Linux 定时任务怎么做?crontab 和 systemd timer 有什么区别? 「🟡 中级」

考察点:定时任务管理能力。

参考答案

crontab 方式(最常用):

bash
# 编辑当前用户的定时任务
crontab -e

# 查看定时任务
crontab -l

# 格式:分 时 日 月 周 命令
# ┌───── 分 (0-59)
# │ ┌───── 时 (0-23)
# │ │ ┌───── 日 (1-31)
# │ │ │ ┌───── 月 (1-12)
# │ │ │ │ ┌───── 周 (0-7, 0和7都是周日)
# │ │ │ │ │
  * * * * * /path/to/command          # 每分钟
  0 2 * * * /backup.sh                # 每天凌晨 2 点
  */10 * * * * /check.sh              # 每 10 分钟
  0 9 * * 1-5 /work.sh                # 周一到周五 9 点
  0 0 1 * * /month_report.sh          # 每月 1 号 0 点

crontab 注意事项:

  • 命令要写绝对路径(环境变量不一定加载)
  • 日志重定向:>> /var/log/myjob.log 2>&1,否则输出会邮寄给用户
  • 脚本里 source ~/.bashrc 加载环境变量
  • 最小粒度是分钟,秒级需要其他方案

systemd timer 方式(更现代):

ini
# /etc/systemd/system/myjob.timer
[Unit]
Description=Run myjob every 10 minutes

[Timer]
OnCalendar=*:0/10          # 每 10 分钟
Persistent=true             # 错过的任务开机后补执行

[Install]
WantedBy=timers.target
ini
# /etc/systemd/system/myjob.service
[Unit]
Description=My backup job

[Service]
Type=oneshot
ExecStart=/opt/scripts/backup.sh
bash
systemctl enable myjob.timer
systemctl start myjob.timer
systemctl list-timers          # 查看所有 timer
维度crontabsystemd timer
精度分钟级秒级
日志自行管理journalctl 统一管理
依赖可配置依赖关系
错过补偿不支持Persistent=true 支持
资源限制可配 CPU/内存限制
适用简单定时生产级定时任务

追问延伸

  • 秒级定时任务怎么实现?(systemd timer、while+sleep 循环、消息延迟队列)
  • crontab 任务没执行怎么排查?(cron 服务是否运行、路径问题、权限、日志 /var/log/cron

Q18: IO 重定向和管道是什么? tee 和 xargs 怎么用? 「🟢 校招/初级」

考察点:Shell IO 机制的理解。

参考答案

Linux 三个标准流:

描述符名称默认
0stdin键盘输入
1stdout终端屏幕
2stderr终端屏幕

重定向操作符:

bash
# 输出重定向
echo "hello" > file.txt      # 覆盖写入
echo "world" >> file.txt     # 追加写入

# 错误重定向
command 2> error.log         # 只重定向错误
command > /dev/null 2>&1     # 正常和错误都丢弃
command &> /dev/null         # 同上(简写)

# 输入重定向
wc -l < /etc/passwd          # 从文件读取标准输入
mysql -u root < dump.sql     # 从文件读取 SQL

# Here Document
cat <<EOF
多行文本
变量会被展开:$HOME
EOF

# Here String
grep "pattern" <<< "some text"

管道(|):

bash
# 前一个命令的 stdout 作为后一个命令的 stdin
cat access.log | grep "ERROR" | wc -l
ps -ef | grep java | grep -v grep

tee 命令(同时输出到屏幕和文件):

bash
# 管道中间分流:既看屏幕又存文件
command | tee output.txt
# 追加模式
command | tee -a output.txt
# 同时输出到多个文件
command | tee file1.txt file2.txt

xargs 命令(把 stdin 转为命令参数):

bash
# 找到所有 .log 文件并删除
find /var/log -name "*.log" | xargs rm -f

# 批量 kill 进程
ps -ef | grep java | grep -v grep | awk '{print $2}' | xargs kill -15

# 批量 ping
cat hosts.txt | xargs -I{} ping -c 1 {}

# -n 限制每次传入的参数个数
echo "1 2 3 4 5" | xargs -n 2 echo
# 输出:1 2
#      3 4
#      5

# -P 并行执行
find . -name "*.py" | xargs -P4 -I{} python {}

常见陷阱:

  • xargs 默认以空格/换行分隔参数,文件名含空格会出错
    • 解决:find . -name "*.txt" -print0 | xargs -0 grep "pattern"
  • 管道只传递 stdout,stderr 不在管道中(需 2>&1 先合并)

追问延伸

  • |; 的区别?(管道传递数据,分号顺序执行不传递)
  • > file 2>&12>&1 > file 有什么区别?(顺序不同,结果不同)

Q19: Git stash、cherry-pick、tag 的使用场景是什么? 「🟡 中级」

考察点:Git 高级操作的实际使用能力。

参考答案

git stash(暂存工作区)

bash
# 正在 feature 分支开发,突然要修 main 上的 bug
git stash            # 暂存当前未提交的修改
git checkout main     # 切到 main 修 bug
git checkout feature  # 修完回来
git stash pop         # 恢复之前暂存的修改

# 多个 stash
git stash list        # 查看暂存列表
git stash apply stash@{1}  # 恢复指定 stash(不删除)
git stash drop stash@{0}   # 删除指定 stash
git stash clear       # 清空所有 stash
  • 场景:临时切换分支,不想 commit 半成品
  • 注意:stash 只暂存已 tracked 的文件,新文件需 git stash -u

git cherry-pick(摘樱桃)

bash
# 把某个 commit 的修改应用到当前分支
git cherry-pick <commit-hash>

# 把一段 commit 范围摘过来
git cherry-pick <start-commit>..<end-commit>

# 多个 commit
git cherry-pick <hash1> <hash2> <hash3>
  • 场景:在 release 分支上只合入某个 hotfix,不带其他 feature
  • 和 rebase 的区别:rebase 搬运整个分支,cherry-pick 只摘个别 commit

git tag(标签)

bash
# 轻量标签
git tag v1.0.0

# 附注标签(推荐,包含作者、日期、说明)
git tag -a v1.0.0 -m "正式发布 1.0.0 版本"

# 给历史 commit 打标签
git tag -a v0.9.0 <commit-hash> -m "回溯打标签"

# 推送标签到远程
git push origin v1.0.0     # 推单个标签
git push origin --tags     # 推所有标签

# 删除标签
git tag -d v1.0.0                  # 删本地
git push origin --delete v1.0.0    # 删远程
  • 场景:版本发布标记,CI/CD 触发条件
  • 轻量标签 vs 附注标签:附注标签是完整对象,有元数据,适合正式发布

git submodule(子模块)

bash
# 在主仓库中引入子仓库
git submodule add https://github.com/user/lib.git libs/lib

# 克隆含子模块的仓库
git clone --recursive <url>
# 或先 clone 再初始化
git submodule update --init --recursive

# 更新子模块
git submodule update --remote
  • 场景:多个项目共享一个公共库(如 SDK、配置)

追问延伸

  • stash 和 commit 到临时分支哪个更好?(临时分支更安全,stash 容易遗忘)
  • cherry-pick 冲突了怎么办?(git cherry-pick --continue--abort

Q20: Git 工作流模型有哪些?你们团队用的哪种? 「🔴 高级」

考察点:团队协作模式的理解和选型能力。

参考答案

主流工作流模型:

1. GitFlow(经典但较重)

         ┌───── hotfix ─────┐
         │                   │
main ────●───────────────────●────────
         \          ┌── release ──┐
          \        /               \
develop ───●──●──●●──────────────●●─
              \      ┌── feature ──┐
               ●──●●─●────────────●
  • 分支:main(生产)、develop(开发)、feature(功能)、release(预发布)、hotfix(紧急修复)
  • 优点:分支职责清晰,适合版本发布的传统产品
  • 缺点:分支多、合并复杂,不适合持续部署

2. GitHub Flow(轻量)

main ────●────────────────●────────
          \               /
feature ──●──●──●──●──PR──
  • 只有 main + feature 分支,通过 PR 合并
  • main 随时可部署,feature 合入前需 CI 通过
  • 优点:简单、适合持续部署
  • 缺点:没有预发布环节

3. Trunk-Based Development(主干开发)

main ──●──●──●──●──●──●──●──●──
         \ /          /
short    ●           ●
feature
  • 所有人直接往 main 提交(或极短生命周期的 feature 分支)
  • 依赖 feature flag + CI/CD + 快速回滚
  • 优点:合并冲突少、部署快、适合高频发布
  • 缺点:要求 CI 完善、代码质量高、feature flag 管理
  • 代表:Google、Facebook、Netflix

4. GitLab Flow(环境分支)

production ──●────●────●──
        ↑    ↑
pre-prod ──●────●────●──
        ↑    ↑
develop ──●────●────●──
  • develop → pre-prod → production 环境分支
  • 优点:环境和代码绑定,部署可追溯
  • 缺点:环境分支合并容易冲突

选型建议:

团队特征推荐工作流
小团队 + 持续部署GitHub Flow
大团队 + 持续部署 + CI 强Trunk-Based
版本发布型产品(如客户端)GitFlow
多环境部署需求GitLab Flow

Feature Flag(特性开关)的作用:

  • 不通过分支控制功能可见性,而通过运行时配置开关
  • 代码先合入 main,功能默认关闭,验证后打开开关
  • 是 Trunk-Based 的关键支撑技术

追问延伸

  • 为什么 Google 用 Trunk-Based 而不用 GitFlow?(数万人协作,分支模型必须极简)
  • Feature Flag 怎么管理?(配置中心统一管理,如 Apollo/Nacos)

Q21: top 命令输出每一行都代表什么意思? 「🟢 校招/初级」

考察点:对系统监控工具输出的理解。

参考答案

top 命令输出分两部分:上半部分是系统信息,下半部分是进程列表。

top - 14:23:45 up 10 days,  3:15,  2 users,  load average: 0.52, 0.45, 0.38
Tasks: 128 total,   1 running, 127 sleeping,   0 stopped,   0 zombie
%Cpu(s):  2.3 us,  1.2 sy,  0.0 ni, 96.3 id,  0.2 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   7864.0 total,   1234.5 free,   2100.0 used,   4529.5 buff/cache
MiB Swap:   2048.0 total,   2048.0 free,      0.0 used.   5300.0 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
 1234 root      20   0  2.5g   1.2g   5000 S   5.2  15.2   0:45.30 java

第一行(系统概况)

  • 14:23:45:当前系统时间
  • up 10 days:系统已运行 10 天
  • 2 users:当前 2 个用户登录
  • load average: 0.52, 0.45, 0.38:1/5/15 分钟平均负载

第二行(进程概况)

  • 128 total:总进程数
  • 1 running:运行中
  • 127 sleeping:睡眠中
  • 0 stopped:停止
  • 0 zombie:僵尸

第三行(CPU 使用率)

  • us:用户态 CPU(应用代码)
  • sy:内核态 CPU(系统调用)
  • ni:nice 值非 0 的进程占的 CPU
  • id:空闲 CPU
  • wa:IO 等待(磁盘慢会高)
  • hi:硬件中断
  • si:软中断(网络收包多会高)
  • st:被虚拟化偷走的 CPU(云主机常见)

第四、五行(内存和 Swap)

  • total / free / used / buff/cache:总量 / 空闲 / 已用 / 缓存
  • avail Mem:实际可用内存(含可回收的 buff/cache)

进程列表

含义
PID进程 ID
USER进程所有者
PR优先级(越小越高)
NInice 值(-20 到 19,负值优先级高)
VIRT虚拟内存总量
RES常驻物理内存(实际占用)
SHR共享内存
S进程状态(R/S/D/Z/T)
%CPUCPU 占用率
%MEM内存占用率
TIME+累计 CPU 时间
COMMAND启动命令

常用交互操作:

  • 1:展开每个 CPU 核心的使用率
  • P:按 CPU 排序
  • M:按内存排序
  • k:输入 PID 杀进程
  • q:退出

追问延伸

  • load average 等于多少算高?(超过 CPU 核数算高,如 4 核 > 4.0)
  • 按数字 1 后看到的每个 CPU 核心使用率有什么用?(判断是否单核打满)

Q22: 怎么查看线程状态?一个进程有多少个线程? 「🟡 中级」

考察点:线程级别的排查能力。

参考答案

方法一:top -H

bash
# 查看某进程内的线程
top -H -p <pid>

# 或先 top 再按 H 键,显示线程模式
top -H

# 输出会显示 TID(线程ID)而不是 PID

方法二:ps 命令

bash
# 查看某进程的所有线程
ps -T -p <pid>
# 或
ps -eLf | grep <进程>

# -T 显示线程,-L 显示轻量级进程(线程),-f 完整格式
# 输出包含 PID、LWP(线程ID)、TTY、STAT、TIME、COMMAND

方法三:/proc 文件系统

bash
# 查看进程的线程数
ls /proc/<pid>/task | wc -l

# 查看每个线程的详细信息
ls /proc/<pid>/task/
# 每个数字是一个 TID 目录,里面有 status、stat 等

方法四:Java 应用专用

bash
# jstack 查看线程栈
jstack <pid>

# jcmd 查看线程
jcmd <pid> Thread.print

# Arthas 查看线程
thread -n 3    # CPU 最高的 3 个线程
thread <tid>   # 查看某线程栈
thread --state BLOCKED  # 查看阻塞线程

线程状态对照:

  • R:运行/就绪
  • S:可中断睡眠
  • D:不可中断睡眠(等 IO)
  • Z:僵尸
  • T:暂停

Java 线程状态(jstack 输出):

  • RUNNABLE:运行/就绪
  • BLOCKED:等 synchronized 锁
  • WAITING:Object.wait() 等待
  • TIMED_WAITING:Thread.sleep(n) 等待
  • TERMINATED:已结束

追问延伸

  • 线程数过多会有什么问题?(上下文切换开销大、内存占用高、调度延迟)
  • 怎么排查线程死锁?(jstack 检测、Arthas thread --deadlock

Q23: sed 和 awk 有什么区别?各适合什么场景? 「🟡 中级」

考察点:文本三剑客的理解和使用。

参考答案

sed(Stream Editor):行级文本编辑器,逐行处理文本。

bash
# 替换字符串(最常用)
sed 's/old/new/g' file.txt           # 输出到终端
sed -i 's/old/new/g' file.txt        # 原地修改
sed -i.bak 's/old/new/g' file.txt    # 原地修改并备份

# 删除空行
sed '/^$/d' file.txt

# 删除第 2-5 行
sed '2,5d' file.txt

# 只打印包含 error 的行
sed -n '/error/p' file.txt

# 在某行前插入
sed '/pattern/i\new line' file.txt
  • 强项:替换、删除、插入、批量修改
  • 简单快速,适合单行操作

awk:列级文本处理工具,支持条件判断、循环、变量。

bash
# 打印第二列
awk '{print $2}' file.txt

# 带条件过滤(第一列 > 100 时打印第二列)
awk '$1 > 100 {print $2}' file.txt

# 统计第二列的总和
awk '{sum += $2} END {print sum}' file.txt

# 指定分隔符
awk -F: '{print $1, $7}' /etc/passwd    # 以冒号分隔

# 统计每个 IP 的访问次数(第1列),按次数排序
awk '{count[$1]++} END {for (ip in count) print count[ip], ip}' access.log | sort -rn | head -10

# 多条件判断
awk '$3 > 80 && $5 > 1000 {print $0}' data.txt
  • 强项:按列操作、统计聚合、条件过滤、复杂逻辑
  • 内置变量:$0(整行)、$1-$n(第n列)、NF(列数)、NR(行号)、FS(分隔符)
  • 支持 BEGIN/END 块、数组、函数

对比

维度sedawk
处理粒度行级列级
替换强(正则替换)可以但不如 sed 方便
统计/计算强(支持加减乘除、聚合)
条件判断有限完整(if/else/循环)
典型场景批量替换、删除行按列提取、统计、分析
性能较快稍慢(功能更多)

选择建议:

  • 只需替换/删除/插入 → sed
  • 需要按列处理/统计/计算 → awk
  • 两者经常配合使用:sed 预处理 + awk 分析

追问延伸

  • sed 的 -i 选项有什么风险?(直接改文件,没有撤销,建议先不加 -i 验证再执行)
  • awk 怎么实现多文件处理?(awk 'FNR==1{...} {...}' file1 file2

Q24: Shell 实战:查找日志中字符串长度、统计请求耗时 Top10 「🟡 中级」

考察点:实际场景的 Shell 编写能力。

参考答案

题目一:查找日志中某个字符串出现的长度

bash
# 统计 ERROR 在日志中出现的次数
grep "ERROR" app.log | wc -l

# 查找每行中匹配字符串的长度
grep "search_string" log_file | awk '{print length}'
# length 是 awk 内置函数,返回当前行的字符数

# 查找匹配字符串本身在每行中出现的长度
echo "hello world" | awk '{match($0, /world/); print RLENGTH}'
# RLENGTH 是 awk 内置变量,记录 match() 匹配到的长度

题目二:统计请求耗时最高的 10 条记录

日志格式示例:

2024-01-15 192.168.1.100 350
2024-01-15 10.0.0.5 1200
2024-01-15 172.16.0.3 80

第一列:日期,第二列:IP,第三列:耗时(ms)

bash
# 方法一:sort 排序
sort -k3 -nr app.log | head -n 10
# -k3:按第三列排序
# -n:数字排序(否则 9 > 100)
# -r:倒序(从大到小)
# head -n 10:取前 10 条

# 方法二:awk + sort
awk '{print $3, $0}' app.log | sort -nr | head -10 | cut -d' ' -f2-
# 先把第三列放前面排序,再去掉临时列

# 方法三:awk 一行搞定(利用数组)
awk '{a[NR]=$3; b[NR]=$0} END {
    # 简单冒泡取 Top10(小数据量适用)
    for (i=1; i<=NR; i++)
        for (j=i+1; j<=NR; j++)
            if (a[j] > a[i]) { tmp=a[i]; a[i]=a[j]; a[j]=tmp;
                               tmp=b[i]; b[i]=b[j]; b[j]=tmp }
    for (i=1; i<=10 && i<=NR; i++) print b[i]
}' app.log

题目三:统计访问量最高的 10 个 IP(经典题)

bash
awk '{print $2}' app.log | sort | uniq -c | sort -rn | head -10

题目四:找出 5xx 错误的请求

bash
# 假设是 Nginx access.log,第9列是状态码
awk '$9 ~ /^5/' access.log | head -20

# 统计各状态码数量
awk '{count[$9]++} END {for (code in count) print code, count[code]}' access.log | sort -rn

常用技巧总结:

  • sort -k N -nr:按第 N 列数字倒序排序
  • uniq -c:去重并计数(需先 sort)
  • awk '{count[$N]++}':用关联数组做分组统计
  • head -n N:取前 N 条
  • wc -l:统计行数

追问延伸

  • 日志文件特别大(几十 GB),怎么处理?(split 分片后并行处理,或用 awk 流式处理)
  • 怎么实时监控错误日志?(tail -f app.log | grep --line-buffered ERROR

Q25: 怎么分析系统负载?load average 到底怎么看? 「🟡 中级」

考察点:负载分析的综合能力。

参考答案

load average 是什么

  • 系统"平均负载",指单位时间内处于 R 状态(运行+就绪)和 D 状态(不可中断睡眠)的平均进程数
  • 三个数字分别是 1 分钟、5 分钟、15 分钟的平均值
  • 不一定等于 CPU 使用率:负载包含等 IO 的进程

怎么看 load average

bash
# 方法一:uptime
$ uptime
 14:23:45 up 10 days,  3:15,  2 users,  load average: 0.52, 0.45, 0.38

# 方法二:top 命令第一行
$ top -bn1 | head -1

# 方法三:cat /proc/loadavg
$ cat /proc/loadavg
0.52 0.45 0.38 2/128 12345
# 前三个数是 load average
# 第四个数:运行进程数/总进程数
# 第五个数:最近创建的 PID

判断负载是否高

  • load average <= CPU 核数 → 正常
  • load average > CPU 核数 → 有进程在排队,需要关注
  • load average > CPU 核数 × 2 → 明显过载
bash
# 查看 CPU 核数
nproc          # 输出核数
lscpu          # 详细 CPU 信息
# 在 top 中按数字 1 也能看到核数

例:4 核服务器,load average = 3.5 → 正常(接近满载但没超) 例:4 核服务器,load average = 8.0 → 过载(排队严重)

深入分析工具

bash
# 1. mpstat - 按核查看 CPU 各项指标
mpstat -P ALL 1
# %usr(用户态)、%sys(内核态)、%iowait(IO等待)、%idle(空闲)
# %iowait 高 → 磁盘慢;%sys 高 → 系统调用频繁

# 2. pidstat - 定位具体进程的 CPU 占用
pidstat -u 1          # 所有进程 CPU 使用率
pidstat -u -p <pid> 1 # 指定进程
pidstat -r 1          # 内存使用
pidstat -d 1          # 磁盘 IO

# 3. vmstat - 综合系统状态
vmstat 1
# r:就绪队列长度(持续 > 核数说明 CPU 瓶颈)
# b:D 状态进程数(等 IO)
# cs:上下文切换次数
# in:中断次数
# us/sy/wa/id:CPU 各项百分比

# 4. iostat - 磁盘 IO
iostat -x 1
# %util:磁盘使用率
# await:IO 等待时间

load average 高但 CPU 使用率不高的原因

  1. IO 瓶颈:大量 D 状态进程等磁盘 → %wa 高 → 升级磁盘或优化 IO
  2. 网络等待:等网络响应的进程也算负载
  3. 锁竞争:线程等锁也在 R 队列中排队

追问延伸

  • load average 和 CPU 使用率的区别?(负载包含 IO 等待,CPU 使用率只算计算时间)
  • vmstat 的 r 列和 load average 有什么关系?(r 是实时就绪队列,load average 是时间平均)

Q26: 怎么判断内存是否够用?怎么统计内存交换频率? 「🔴 高级」

考察点:内存问题的深度排查能力。

参考答案

判断内存是否够用

bash
# 1. free 命令
$ free -m
              total        used        free      shared  buff/cache   available
Mem:           7864        2100        1234         100        4529        5300
Swap:          2048           0        2048

判断标准:

  • available > 总量的 30% → 内存充足
  • available < 总量的 10% → 内存紧张,需要排查
  • available 接近 0 → 内存不足
  • Swap 在使用(used > 0)→ 内存不够,系统在用交换分区 → 性能严重下降
  • buff/cache 大是正常的(系统缓存可以回收)
bash
# 2. 检查是否发生过 OOM(Out of Memory)
dmesg | grep -i "out of memory"
dmesg | grep -i "oom-killer"
# 如果有 OOM 记录,说明内存严重不足,系统杀过进程

# 3. 找出占内存最多的进程
ps aux --sort=-%mem | head -10
# 或
top -bn1 -o %MEM | head -20

统计内存交换频率

bash
# 1. vmstat 看交换活动
$ vmstat 1
procs ---memory-- ---swap-- -----io---- -system-- -----cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id
 0  0      0 123400  45678 789000    0    0    10     5  100  200  2  1 97

关键列:

  • si(swap in):每秒从 swap 读入内存的量(KB),> 0 说明在换入
  • so(swap out):每秒从内存写入 swap 的量(KB),> 0 说明在换出
  • si/so 持续 > 0 → 内存紧张,系统在频繁交换 → 性能严重下降
bash
# 2. sar -B 查看页面回收统计
$ sar -B 1
# pgscank/s:kswapd 后台扫描的页面数
# pgscand/s:直接扫描的页面数(应用内存不足时触发,值大说明内存紧张)
# pgsteal/s:回收的页面数
# %vmeff:回收效率(低说明内存碎片严重)

# 3. sar -W 查看 swap 统计
$ sar -W 1
# pswpin/s:每秒换入页数
# pswpout/s:每秒换出页数
# 这两个值持续 > 0 说明内存严重不足

# 4. /proc/meminfo 查看详细信息
$ cat /proc/meminfo | grep -E "SwapTotal|SwapFree|Dirty|Writeback|AnonPages"
# SwapTotal/SwapFree:Swap 总量和空闲
# Dirty:待写入磁盘的脏页
# AnonPages:匿名页(应用内存,不能直接回收)

内存不足的排查流程:

  1. free -m 看 available 和 Swap 使用
  2. vmstat 1si/so 是否 > 0
  3. dmesg | grep oom 检查 OOM 历史
  4. ps aux --sort=-%mem | head 找内存大户
  5. sar -B 1 查看页面回收压力
  6. 对 Java 应用:jstat -gc <pid> 看 GC 情况,jmap -heap <pid> 看堆使用

追问延伸

  • 为什么系统还有 free 但 Swap 却在用?(内核的 swapiness 参数,默认 60,会主动把不活跃页换出)
  • 怎么临时关闭 swap?(swapoff -a,但生产环境谨慎,可能 OOM)

Q27: Git 的四个区域是什么?基础工作流是怎样的? 「🟢 校招/初级」

考察点:Git 基础概念。

参考答案

Git 的四个区域:

工作区         暂存区           本地仓库          远程仓库
(Workspace)   (Index/Stage)   (Repository)    (Remote)
  │               │                │               │
  │── git add ──→ │                │               │
  │               │── git commit →│               │
  │               │                │── git push ─→ │
  │← git checkout ───────────────│                │
  │←────────────── git pull ───────────────────────│

工作区(Workspace)

  • 你在文件系统中看到的目录和文件
  • 修改文件后,变更在工作区

暂存区(Index/Stage)

  • git add 后文件进入暂存区
  • 暂存区是下一次 commit 的快照
  • .git/index 文件记录

本地仓库(Repository)

  • git commit 后变更进入本地仓库
  • 存储在 .git 目录
  • 有完整的提交历史

远程仓库(Remote)

  • GitHub/GitLab/Gitee 上的仓库
  • git push 推送到远程,git pull 拉取远程

基础工作流:

bash
# 1. 克隆远程仓库
git clone https://github.com/user/repo.git

# 2. 修改文件(工作区变更)
vim file.txt

# 3. 查看变更状态
git status                # 查看工作区和暂存区状态
git diff                  # 查看工作区 vs 暂存区的差异

# 4. 暂存修改
git add file.txt          # 暂存单个文件
git add .                 # 暂存所有变更

# 5. 提交到本地仓库
git commit -m "修复bug描述"

# 6. 推送到远程仓库
git push origin main

# 7. 拉取远程最新代码
git pull                  # = git fetch + git merge
git pull --rebase         # = git fetch + git rebase

文件状态流转:

  • Untracked:新文件,未跟踪 → git addStaged
  • Modified:已跟踪文件被修改 → git addStaged
  • Staged:已暂存 → git commitUnmodified
  • Unmodified:已提交未修改 → 修改后 → Modified

常用命令对照:

操作命令
查看状态git status
查看差异git diff / git diff --staged
暂存git add <file> / git add .
提交git commit -m "msg"
推送git push origin <branch>
拉取git pull / git pull --rebase
查看日志git log --oneline --graph
查看远程git remote -v
切换分支git checkout <branch> / git switch <branch>
创建分支git checkout -b <branch> / git switch -c <branch>

追问延伸

  • git fetchgit pull 的区别?(fetch 只下载不合并,pull = fetch + merge)
  • git diffgit diff --stagedgit diff HEAD 分别比较什么?