8.7-8日每日一讲
8月7日技术每日一讲:
如何实时查看系统日志?如何跟踪一个特定进程的日志输出?
实时查看系统日志
核心命令是 tail -f,配合 journalctl 按需选择:
# 实时跟踪所有系统日志(systemd 系统)
journalctl -f
# 只跟踪某个服务
journalctl -fu nginx
# 传统 syslog 文件
tail -f /var/log/messages
tail -f /var/log/syslog
常用辅助选项:
-n 100先显示最近 100 行再跟踪--since "5 min ago"只看某时间段grep过滤关键词,例如journalctl -f | grep -i error
跟踪特定进程的日志输出
分两种情况:
- 进程通过 systemd 管理(最常见):
journalctl -fu nginx.service
tail -f /var/log/nginx/error.log
- 进程直接运行在前台/后台,没有日志文件,可用
strace或重定向:
# 用 strace 跟踪进程的所有系统调用和输出
strace -p <PID>
# 如果进程已启动,直接把它的输出重定向到文件
/path/to/program >> /var/log/app.log 2>&1 &
什么是 inode?什么情况下会导致磁盘空间充足但无法创建文件?
inode 是 Linux 文件系统里的索引节点,用来保存文件的元数据,比如文件大小、所有者、权限、时间戳,以及数据块的位置。每个文件(或目录)都对应一个 inode。
关键点:
- 文件名和 inode 是靠目录项(dentry)关联的,真正存数据的是 inode。
- 所以文件系统有两个独立的"容量":数据块空间和inode 数量。
磁盘空间充足但无法创建文件,常见原因:
-
inode 耗尽
文件系统创建时分配了固定数量的 inode,格了之后不能动态增加(除非用支持动态 inode 的如 Btrfs)。大量小文件会快速吃光 inode。df -i查看。
检查:df -i -
进程打开文件句柄数(fd)达上限
报错通常是too many open files,受ulimit -n或/proc/sys/fs/file-max限制。
检查:ulimit -n、cat /proc/sys/fs/file-max -
目录权限/只读挂载
目录没有写权限,或分区被ro重新挂载。
检查:mount | grep ro、ls -ld 目录 -
磁盘配额(quota)超限,针对用户或项目。
-
文件系统损坏,块已满但 inode 显示有剩余。
排查顺序: 先 df -h 看空间,再 df -i 看 inode,最后看 ulimit 和挂载权限,基本能定位。最典型就是 inode 耗尽。
服务器磁盘 I/O 使用率100%,请描述你的排查思路和步骤。
一、先分清是哪一具体 100%
iostat的%util接近 100%,表示磁盘请求持续排队,磁盘确实满负荷top里iowait高,说明 CPU 在等待磁盘,通常伴随%util高- 云平台监控显示 IOPS 或吞吐打满,属于云盘性能上限被限流
二、排查步骤
- 定位是哪个盘、什么负载:用
iostat -x 1看%util、await、r/s、w/s。重点看await,很高说明请求在排队,是真的满了;很低则可能是瞬时抖动 - 找出读写进程:用
iotop -oP,没有就用pidstat -d 1按进程看读写速率 - 判断偶发还是持续:偶发多为备份、日志切割、定时任务、索引重建;持续则多为业务压力大或磁盘降级、RAID 重建、坏道
三、按现象对症
- 大量随机写:多为数据库、日志 fsync,可调刷盘参数、优化落盘策略
- 大量顺序读:多为数据仓库、日志分析,可上 SSD、加缓存、限并发
- 日志疯狂写:应用刷屏打日志,降低日志级别、做轮转
- 云盘限流:看云监控的 IOPS 上限,升配或开突发
结论:排查不能停在"看到 100%",要落到"下一步动作"。核心是看 await 区分排队型和限流型,再用 iotop 揪出元凶进程,最后针对负载类型做优化。
服务器 CPU 使用率100%,请描述你的排查思路和步骤。
第一步:确认现象和范围
先用 top、uptime 看整体,重点看 load average 的三档趋势(1/5/15 分钟),判断是持续性问题还是瞬时尖峰。用 mpstat -P ALL 确认是单核打满还是多核打满,这决定了是单线程瓶颈还是资源整体耗尽。
第二步:定位进程
用 top -c 或 ps aux --sort=-%cpu 找到 CPU 占用最高的进程,判断占用来源:
- 业务进程(应用服务、数据库)
- 系统组件(内核线程、IO 进程)
- 异常进程(挖矿、僵死、恶意脚本,需要结合
last、crontab排查安全)
第三步:区分用户态和系统态
通过 top 的 CPU 占比(us/sy/wa/st)判断瓶颈性质:
- us 高:应用逻辑问题,需要深入代码层
- sy 高:系统调用或上下文切换频繁,常见于锁竞争、大量短连接
- wa 高:表面是 CPU 100%,实则磁盘 IO 瓶颈,属于误判
- st 高:云主机被宿主机抢占,属虚拟化层面
这一步非常关键,因为很多"CPU 100%"实际是 IO 或虚拟化问题,方向错了就白查。
第四步:线程级下钻
对目标进程用 top -Hp <pid> 找到最忙的线程,再根据技术栈深入:
- Java:
jstack抓线程 dump,找 RUNNABLE 状态堆栈,配合 GC 日志判断是否 GC 频繁 - 数据库:
show processlist定位慢 SQL,用explain看执行计划 - 通用:
perf top或strace看热点函数和系统调用
第五步:定位根因并给出结论
最终落到具体原因,比如慢 SQL 缺索引、代码死循环、JVM 参数不合理、连接数过高导致上下文切换爆炸等,并说明对应的优化手段。
8月8日技术每日一讲:
服务器内存不足,OOM Killer 被触发,如何查看它杀掉了哪些进程?
内核在触发 OOM Killer 时,会把被杀进程的信息写进内核日志。这块记录不依赖任何应用,是内核自己的输出,所以只要找到内核日志就能看到。
日志里的核心记录包含三个关键要素:被杀进程的 PID、进程名,以及它的 oom_score_adj 值。oom_score_adj 很关键,它代表这个进程被杀的优先级,数值越大越容易被选为牺牲对象。
定位日志有两条路径。一是看实时和最近的记录,用 dmesg 就能拿到当前内核环形缓冲区里的内容,过滤 "killed process" 关键词即可,适合刚出问题立刻查的场景。二是查历史记录,走系统日志,从 journald 里过滤关键词,或者看 /var/log/messages、/var/log/syslog、/var/log/kern.log 这几个传统日志文件,适合事件过去之后复盘。
除了被杀进程本身,还能挖出更多信息。把 "out of memory" 关键词出现处附近的日志展开,能看到 OOM 触发那一刻系统里内存占用最高的进程清单。这能帮你判断两件事:是被杀进程自己吃满内存,还是另一个进程把内存耗尽导致它被误杀。
如何统计 /var/log/nginx/access.log 中访问量最高的前10个IP?
统计访问量最高的前10个IP,用 awk 提取 IP 字段再排序取前10即可。
awk '{print $1}' /var/log/nginx/access.log \
| sort \
| uniq -c \
| sort -rn \
| head -10
命令拆解:
awk '{print $1}':默认按空格分隔,Nginx 日志第一个字段是客户端 IP。sort:排序,让相同 IP 相邻(uniq -c依赖排序)。uniq -c:去重并统计每个 IP 出现次数。sort -rn:按出现次数倒序(-n数值比较,-r反序)。head -10:取前 10 行。
输出第一列是访问次数,第二列是 IP。
补充:
- 如果 Nginx 前面有代理(如负载均衡),
$1会是代理 IP,需改用$NF(最后一个字段,\转义后的真实 IP)。 - 只统计今天或某个时间段,可以用
grep过滤日期后再统计。 - 想按 IP 维度看访问量,直接
awk '{print $1}' | sort | uniq -c即可看到全部 IP 分布。
如何查找当前目录下7天以前大于100M的日志文件并删除?
查找 7 天前、大于 100M 的日志文件:
find /your/path -type f -name "*.log" -mtime +7 -size +100M
-type f只匹配普通文件-name "*.log"限定日志(可按实际情况调整或去掉)-mtime +7修改时间 7 天以前(严格超过 7×24h)-size +100M大于 100MB
确认列表无误后删除:
# 方式一:find 直接删(注意 -delete 在最后)
find /your/path -type f -name "*.log" -mtime +7 -size +100M -delete
# 方式二:配合 xargs(更直观,可加日志)
find /your/path -type f -name "*.log" -mtime +7 -size +100M -print0 | xargs -0 rm -f
注意事项:
-delete必须在所有条件之后,顺序不能乱- 删除前强烈建议先跑第一条命令看结果,或用
-ls预览 - 如果要按访问时间用
-atime,按创建时间(Linux 无直接选项,可用-newermt反向表达)
如何用一个命令查看指定进程的线程数?
ps -T -p <PID>
直接看进程 PID 的线程数,输出最后一列 SPID 就是线程列表,配合 wc -l 数个数:
ps -T -p 1234 | wc -l
注意 wc -l 会把表头也算进去,所以实际线程数是结果减 1。
更精确的只数线程(不计表头)用:
ps -T -p 1234 --no-headers | wc -l
命令拆解:
-T显示该进程下的所有线程(含子线程)-p <PID>指定进程--no-headers去掉表头行
其他常用变体:
- 按进程名查:
ps -T -C nginx - 实时监控线程数:
top -H -p 1234 - 更权威的线程统计用
/proc:ls /proc/1234/task | wc -l
三者都可以,ps -T 最直观,/proc/<pid>/task 最准确(直接就是内核线程目录数)。
描述ssh免密登录的配置过程及其原理(公私钥认证)。
SSH 免密登录核心是基于公私钥的非对称认证,替代每次都输密码的方式。
一、原理
SSH 认证本质是证明"我持有与公钥匹配的私钥",全程不传输私钥本体。
- 客户端生成一对密钥:私钥(自己保留,权限 600)和公钥(可公开)。
- 把公钥上传到服务器,追加到目标用户的
~/.ssh/authorized_keys。 - 连接时服务器生成随机挑战数,用该用户的公钥加密后发给客户端。
- 客户端用私钥解密挑战数,回传摘要(签名)。
- 服务器用公钥验证签名匹配,验证通过即放行,无需密码。
关键点:私钥永不离开客户端,攻击者拿到公钥也无法反推私钥,这是核心安全基础。
二、配置步骤(A 机免密登录 B 机)
假设从 A 机免密 ssh 到 B 机的 buser 用户:
# 1. 在 A 机生成密钥对(已有可跳过,会询问是否覆盖)
ssh-keygen -t rsa -b 4096
# 一路回车,默认存到 ~/.ssh/id_rsa(私钥)和 id_rsa.pub(公钥)
# 安全起见建议设置 passphrase(私钥口令)
# 2. 把公钥传到 B 机(会要求输入一次 B 机密码)
ssh-copy-id buser@B机IP
# 等价于手动操作:
# cat ~/.ssh/id_rsa.pub | ssh buser@B机IP "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
# 3. 验证免密登录
ssh buser@B机IP
# 若设置了 passphrase,可配合 ssh-agent 避免每次输入
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_rsa
三、权限要求
- 服务器端:
~/.ssh权限 700,authorized_keys权限 600。 - 客户端:
~/.ssh权限 700,私钥id_rsa权限 600。 - 权限过宽会被 SSH 拒绝加载密钥,是免密失效最常见原因。
如何用tcpdump抓取到达主机192.168.1.1端口80的流量并保存到文件?
用 tcpdump 抓取到达 192.168.1.1 端口 80 的流量并保存到文件,命令如下:
sudo tcpdump -i eth0 'dst host 192.168.1.1 and tcp dst port 80' -w /tmp/http.pcap
-i eth0:指定监听的网卡,换成你实际的网卡名(可用ip a或ifconfig查看)。- 过滤表达式:
dst host 192.168.1.1:抓取目的地址是该主机的包。and tcp dst port 80:同时满足 TCP 目的端口为 80(HTTP)。- 两个条件用
and组合,只抓"发往该主机 80 端口"的流量。
-w /tmp/http.pcap:把原始数据包写入文件(二进制格式,供wireshark分析)。
抓完按 Ctrl+C 停止。之后可用 -r 读取:
sudo tcpdump -r /tmp/http.pcap
- 不加
-w时默认在终端打印包内容;-w存的是完整报文,不丢细节。 - 如果只想看交互过程,可加
-A(ASCII)或-X(十六进制)打印内容。 - 文件是 pcap 格式,直接拷贝到本机用 Wireshark 打开即可分析。
如何用sed命令将文本中所有的foo替换为bar,但忽略包含baz的行?
用 sed 加地址范围(! 取反)实现,在模式空间里先做匹配,非 baz 行才执行替换。
sed '/baz/!s/foo/bar/g' 文件
/baz/:正则地址,匹配包含baz的行!:取反,即不匹配baz的行才执行后面的命令s/foo/bar/g:把 foo 替换为 bar,g表示一行内全部替换而非只替换首个
逻辑就是巴az 行原样保留,其余行里的 foo 全部变成 bar。
若只想整行不处理但允许行内出现 foo 时不改,如上即可。若希望跳过的是全局大小写无关的 baz,加 I 修饰符:
sed '/baz/I!s/foo/bar/g' 文件
复杂度换行则用 { } 分组,但单条命令以上写法最简洁。