一个日志问题,和一个沟通问题


[摸不着头脑.JPG]


== 1 ==

说来也巧,7月6号在这里发了一篇 Linux 下删点日志也能搞死人,讲了下删文件可能存在的一些坑,到了第二周、12号,一个 B 厂(此 B 厂非鄙厂)的朋友问了我个问题:

“用 sed 处理文本文件,想要只保留最后 1k 行,怎么写比较合适?”

[黑人问号.JPG]

这问题让我觉得有点想吐槽,但我还是忍住了,我说这用 tail 不就好么?

但他的要求是,得直接修改原文件。

所以他想到了 sed 的 -i 参数(--in-place,即原地修改)。

还有一个原因是,之前用过类似这种方式来输出第 6 ~ 8 行:

$ sed -ne "6,8p" 

虽然我们可以用 $ 来替换 8 ,表示输出到文件末尾,但 sed 又不支持 "$-1000,$" 这种写法,有点尴尬。

当然,变通的办法不是没有,比如我们可以这样计算出起始行的行号:

echo `wc -l $filename | awk '{print $1}'` - 1000 | bc

只不过看起来一点都不优雅。

注:stackoverflow上某位大佬竟然有个只用 sed 就能解决的写法,但我没看懂,就不写了,反正一样也不优雅。


== 2 ==

于是就像很多pen友吐槽我那篇里的面试题一样,我说:

[你这需求很不合理啊.JPG]

他解释说,线上部署的 EMR 服务托管了很多开源组件,各个组件的日志都不是很规范,需要用一些外围脚本来做清理,日常是用 logrotate 做清理;但偶发的异常,可能会导致某个组件在短时间内输出大量日志、打爆磁盘。

—— 所以其实原始需求是清理过度膨胀的日志。

根据前文所述,线上清理日志是需要注意的 —— 否则可能会导致文件虽然删了,但是磁盘空间并不释放的情况。

但 sed -i 是否会导致这种情况呢?


== 3 ==

由于文件在 fs 里的存储类似数组(或者很多数组串成的链表,类似C++的 deque),而 sed 的操作可能会需要修改任意位置,因此从逻辑上来说,直接修改原文件并不合适;不过稳妥起见还是要确认下。

打开 sed 的源码,我们可以看到:

static void open_next_file (const char *name, struct input *) {
  ...
  if (in_place_extension) {
    ...
    output_file.fp = ck_mkstemp (&input->out_file_name, tmpdir, "sed", write_mode);
    ...
}


static void closedown (struct input *) {
  if (in_place_extension && output_file.fp != NULL) {
    ...
    ck_rename (target_name, backup_file_name, input->out_file_name); 
    ...
}

大概意思是,当开启了 -i 选项时,会新建一个临时文件,并在处理结束的时候将它重命名为原文件,这和前面的推测一致。

ck_rename 函数里调用了 glibc 的 rename,然鹅 rename 的文档没有明确提及被替换的文件会怎么样。

文档不够,实操来凑:

  • 在终端 1 打开一个文件

$ echo 123456 > 1.txt
$ python
>>> f = open("1.txt", "w")
  • ‍在终端 2 用 sed 原地修改该文件(不做备份)

# @Terminal#2
$ sed -i'*' -e 's/345/666/' 1.txt
$ cat 1.txt
126666
  • 观察 python 打开的文件:

$ readlink /proc/723/fd/3
/tmp/1.txt (deleted)

‍注:723 是 python 的 pid;进程打开的第一个文件 fd 通常是 3(0/1/2对应stdin/out/err)。

[果然是坑.JPG]


== 4 ==

既然此路不通,只能改道。

解决方案倒是很简单粗暴:线上既然已经部署了 logrotate ,还是考虑用它做日志切分。

虽然 logrotate 并不支持按日志尺寸切割,不过加个外挂实现这一点并不难,定时检测日志大小,超过阈值后主动调用 logrotate --force 就好了。

大概像这样:

size=`stat -c%s $logfile`
if [ $size -gt $threshold ]; then
  logrotate --force $CONFIG_FILE
fi

注:这几句只是示例,没有实际测试过。


== 5 ==

技术问题倒是解决了,但值得注意的是,这有个日常出现得很频繁的沟通问题,其模式是:

  • 小 A 遇到了问题 X。

  • 为了解决 X,他想到了方案 S。

  • 在实现 S 的过程中,他发现困惑 P。

  • 于是他问小 B :“P是什么?” 或 “要如何处理 P?”

然鹅接下来的沟通常常是这样

  • B 问:为什么要处理P?

  • A 答:因为想实现 S。

  • B 问:为什么要实现S?

  • A 答:因为遇到了问题 X。

[熊猫头捂脸.JPG]

我在 Lark 上搜索“背景是什么”,平均每个月都能看到好几条,其中很多都是类似的场景。

这种模式显著增加了沟通成本,尤其是在 IM 这种 RTT 比较长的场景。

更糟的场景是,有时候 S 并不是解决 A 的合适方案、甚至是错误方案,本文所述例子就是一个典型。

如果在提问时,能够带上问题的背景(视实际场合简述或详述),就能有效降低沟通成本。


== 6 ==

本篇就到这里,小结一下:

  • 删除被打开的文件不会释放对应磁盘空间;

  • sed 的 in-place 其实是新建临时文件,再用 rename 替换原文件;

  • rename 操作相当于删了目标文件;

  • 日志的切割还是适合用 logrotate ,--force 参数有时会很有用;

  • 沟通的时候注意描述问题的背景,可以降低沟通成本。

 

感谢阅读以及对表情包的脑补,本文完。


推荐阅读:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值