news 2026/10/11 6:27:03

Linux 日志增量统计:inode + offset 方案(不丢不重)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux 日志增量统计:inode + offset 方案(不丢不重)

背景
我给 Nginx 缓存命中率写了个统计脚本,每 5 分钟跑一次,读 /var/log/nginx/dashboard_cache.log,统计 HIT/MISS 数量写进 MariaDB。

第一版逻辑很简单:

tail-n100/var/log/nginx/dashboard_cache.log|awk'{...}'

跑了两天发现数字不对:

命中率忽高忽低,有时候一小时内 HIT 数突然翻三倍

明明只 curl 了几次,统计里却有上百条

想做到:每次只统计「上次跑完之后新增的日志」,不重复、不遗漏。

就像你每天记账,不能把昨天的账再抄一遍,也不能漏掉今天新花的钱。
原因
第一版:tail -n 100

tail-n100$LOG|awk...

问题:

两次运行之间如果有超过 100 条新日志,会漏

两次运行之间有重叠的 100 条,会重复统计
第二版:记录行号

# 读上次行号 LAST=$(cat /tmp/last_line) # 从上次行号之后开始读 tail -n +$((LAST+1)) $LOG | awk ... # 写本次行号 wc -l < $LOG > /tmp/last_line

问题:logrotate 轮转后,行号从头开始。行号 500 指向的文件变了。
第三版:记录文件名

tail-n+$((LAST+1))$LOG

还是不对:如果文件被删除重建(rm 后 touch),文件名没变,但内容全变了。
真正需要的是
两个东西的组合:

inode:文件的唯一编号,用来判断「是不是同一个文件」

offset:字节偏移量,用来判断「读到哪了」
解决
核心思路

每次运行: 1. 读上次记录的 inode + offset 2. 对比当前文件的 inode 和大小 3. 如果 inode 变了 → 从头读 如果 offset > 当前大小 → 文件被截断 → 从头读 否则 → 从 offset 开始读 4. 处理完,记录新的 inode + 文件大小

完整脚本

#!/usr/bin/env bashset-uopipefailLOG="/var/log/nginx/dashboard_cache.log"STATE_DIR="/home/user/metrics"OFFSET_FILE="$STATE_DIR/cache.offset"mkdir-p"$STATE_DIR"# 当前文件状态CUR_INODE=$(stat-c%i"$LOG")CUR_SIZE=$(stat-c%s"$LOG")# 上次状态(默认 0 0)PREV_INODE=0PREV_OFFSET=0if[-f"$OFFSET_FILE"];thenread-rPREV_INODE PREV_OFFSET<"$OFFSET_FILE"||truefi# 判断是否需要从头读if["$CUR_INODE"!="$PREV_INODE"]||["$CUR_SIZE"-lt"$PREV_OFFSET"];thenPREV_OFFSET=0fi# 从 offset 之后读新内容TMP=$(mktemp)trap'rm -f "$TMP"'EXITtail-c+$((PREV_OFFSET+1))"$LOG">"$TMP"# 处理awk-F'\t''$10!="" && $10!="-" { c[$10]++ } END { for (s in c) print s, c[s] }'"$TMP"# 记录新的 offset(下次接着读)NEW_SIZE=$(stat-c%s"$LOG")echo"$CUR_INODE$NEW_SIZE">"$OFFSET_FILE"

三个关键命令

# 1. 拿 inode 和文件大小 CUR_INODE=$(stat -c %i "$LOG") # 例如 1234567 CUR_SIZE=$(stat -c %s "$LOG") # 例如 829456 字节 # 2. 从 offset 之后读(注意 +1,tail -c 从 1 开始计数) tail -c +$((PREV_OFFSET+1)) "$LOG" # 3. 记录状态到文件 echo "$CUR_INODE $NEW_SIZE" > "$OFFSET_FILE"

验证效果
正常情况

$catcache.offset1234567829456$sudo/path/to/script.sh HIT90MISS10$catcache.offset1234567829702# 文件涨了 246 字节,offset 同步更新

模拟 logrotate

# 手动模拟:把日志挪走,新文件进来 sudo mv /var/log/nginx/dashboard_cache.log /var/log/nginx/dashboard_cache.log.1 sudo touch /var/log/nginx/dashboard_cache.log sudo chown www-data:adm /var/log/nginx/dashboard_cache.log sudo systemctl reload nginx # 跑脚本 $ sudo /path/to/script.sh (新文件没内容,输出空) # offset 变了,因为 inode 变了 $ cat cache.offset 1234568 0 # inode 变了,offset 归 0

模拟截断

# 假装日志被截断 sudo truncate -s 100 /var/log/nginx/dashboard_cache.log # 跑脚本 $ sudo /path/to/script.sh (从 0 开始读,读到 100 字节的内容) $ cat cache.offset 1234567 100 # offset 变成当前实际大小

不丢不重的验证

# 记录当前 HIT 总数$sudogrep-c'HIT'/var/log/nginx/dashboard_cache.log1000# 跑脚本,记下本次新增$sudo/path/to/script.sh HIT50MISS5# 再跑一次,应该没有新增$sudo/path/to/script.sh (无输出,因为没有新日志)# 制造新流量$foriin{1..10};docurl-k-s-o/dev/null https://example.com:8443/;done# 再跑一次$sudo/path/to/script.sh HIT10MISS1

两次统计加起来正好等于实际新增,不重不漏。
三个坑
坑 1:用行号而不是字节偏移

# 错误 wc -l < $LOG > offset tail -n +$((LAST+1)) $LOG

为什么不行:

logrotate 后行号从头开始,会重复读

文件被截断后行号可能变少,逻辑就乱了

多字节字符(中文、UTF-8)可能导致行号与内容对应不准

正确:用 stat -c %s 拿字节数,tail -c +N 按字节读。
坑 2:忘了判断 inode 变化

# 错误:只看大小 if [ "$CUR_SIZE" -lt "$PREV_OFFSET" ]; then PREV_OFFSET=0 fi

问题:logrotate 后新文件很小,如果新文件恰好比旧 offset 大,就不会触发「从头读」,会从中间截断读。

正确:inode 变化和文件变小,任意一个满足就从头读。

if [ "$CUR_INODE" != "$PREV_INODE" ] || [ "$CUR_SIZE" -lt "$PREV_OFFSET" ]; then PREV_OFFSET=0 fi

坑 3:tail -c +N 的 +1

# 错误:tail -c +$PREV_OFFSET # 这会重复读最后一个字节

原因:tail -c +N 从第 N 个字节开始(1 起始)。而 offset 记录的是「已经读完的字节数」,等价于「下一个未读字节的前一个位置」。

所以要用 +$((PREV_OFFSET+1))。

例子:

文件 abcdef,offset = 3(已读完 abc)

下次应该从第 4 个字节 d 开始读

tail -c +4 正好从 d 开始 ✅

tail -c +3 会从 c 开始 ❌(重复读)

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/11 6:26:56

200Gb/s以上速率的通道信号完整性设计和优化经验(一)

摘要:人工智能驱动的互联网与数据中心基础设施快速扩张,对下一代通信系统的物理层提出了前所未有的要求。带宽需求持续增长、互连密度不断提升,加之系统架构日趋复杂,催生了单通道速率超 200 Gb/s 的信号传输技术。随着数据速率持续提升,系统裕量缩减、信道损耗增大、封装…

作者头像 李华
网站建设 2026/10/11 6:26:08

丝杆模组精度下降的预警信号:从声音温度到振动电流的排查清单

干设备维护这些年&#xff0c;我接过不少“精度莫名其妙的就没了”的投诉。最典型的一次&#xff0c;一台加工单元的定位偏差已经跑到0.12mm&#xff0c;现场先怀疑工艺参数、刀具磨损&#xff0c;折腾了两天&#xff0c;最后拆开一看&#xff0c;滚珠丝杆螺母的滚道里已经出现…

作者头像 李华
网站建设 2026/10/11 6:21:04

Claude Code插件:JetBrains IDE语义级AI编程工作流

1. 这不是又一个“AI写代码”插件&#xff1a;它专为JetBrains生态重构工作流JetBrains党——这个在IDE界自带信仰标签的群体&#xff0c;对工具的挑剔程度远超普通开发者。我们不是不接受AI辅助&#xff0c;而是拒绝把AI塞进一个不匹配的壳子里。当看到“Claude Code插件”这个…

作者头像 李华
网站建设 2026/10/11 6:19:45

GeekezBrowser:把浏览器变成开发者生产力工具

前阵子折腾 GeekezBrowser&#xff0c;本来只是想找个轻量浏览器当备用&#xff0c;结果越用越发现它和 Chrome、Edge 走的是完全不同的路线。简单说&#xff0c;GeekezBrowser 的核心目标不是“日常上网”&#xff0c;而是“把浏览器变成生产力工具”。它把开发者平时用得上的…

作者头像 李华
网站建设 2026/10/11 6:19:42

文档统一转Markdown:提升AI理解与RAG检索效果的实践指南

把 PDF、Word、Excel、图片交给 AI 之前&#xff0c;先统一成 Markdown&#xff0c;这个思路我是在做一批文档问答项目时彻底想通的。当时团队接到的需求是把几百份混合格式的资料喂给大模型做检索问答&#xff0c;一开始图省事&#xff0c;直接调各种解析库把文本抠出来塞给模…

作者头像 李华
网站建设 2026/10/11 6:19:16

用Python爬虫+WebSocket抓取B站直播弹幕:协议拆解与CSV落盘实战

直播间的弹幕&#xff0c;看起来只是一串串飞快滚过的文字&#xff0c;但如果把它当成数据&#xff0c;能挖掘的信息量远比想象中大。弹幕发送频率反映直播间的活跃曲线&#xff0c;弹幕用词反映观众的情绪和关注点&#xff0c;甚至能提前感知到内容节奏的变化。这篇文章分享的…

作者头像 李华