多维度架构之日志
传统做法是文件型日志,分布在各个服务器上。在大规模部署服务器后代来很多不便,增加很多管理成本,所有我们需要集中管理服务器产生的所有日志,我们叫他日志中心服务器
一次切割日志引发的血案
很多应用程序会产生日志,有些程序已经实现了日志切割,一般是每天一个文件。但有时这个切割并不能满足我们的需求,例如我们需要颗粒度更细的切割。
切割日志的目的是什么?
- 日志尺寸过大
- 便于分析
- 切割后归档,或者导入日志平台
切割日志基本两种方法:
- 手工
- shell
- 工具,例如logrotate,传统的cronolog
日志切割方案网上有很多,很多运维也是参考这些方案进行配置,网上的例子不完全都是对的,可能你用了很多年配置方案是错误的。 没有出现故障是侥幸,因为笔者15年前就在此处栽过,由于日志太大,我便清空了日志,以为程序仍然会继续写入,最后直到服务器崩溃。 最近发现很多新手再谈cronolog,我便想起当年发生的故障,有必要跟大家分享。
首先日志是可以切割的,网上的例子理论上也是可行,但我们不能不求甚解,稀里糊涂的用下去。
我们首先了解一下日志是怎么产生的,那种日志可以切割,那些日志不能切割,为什么不能切割,如果需要切割日志怎么处理?
日志是怎么产生的
日志生命周期,创建/打开日志文件,追加日志记录,关闭日志文件。请看下面伪代码。
main (){ f = open(/tmp/prog.log) ... ... f.append('DEBUG .............') ... f.append('INFO .............') ... f.append('WARN .............') f.close() }
这个程序是顺序运行,每次运行都会经历,打开日志文件,追加日志记录,关闭日志文件,一个日志生命周期结束。 在完成日志生命周后,你就可以切割日志了。因为f.close()后日志文件已经被释放。
再看下面的程序
main (){ f = open(/tmp/prog.log) loop{ ... ... f.append('DEBUG .............') ... f.append('INFO .............') ... f.append('WARN .............') if(quit){ break } } f.close() }
这个程序就不同了,程序运行,打开日志文件,然后进入无穷循环,期间不断写入日志,接到退出命令才会关闭日志。 那么这个程序你就不能随便切割日志。你一旦修改了日志文件,程序将不能在写入日志到文件中。 这个程序切割日志的过程是这样的
split loop { prog run prog quit && mv /tmp/prog.log /tmp/prog.2016-05-05.log }
再看下面的程序
main (){ loop{ f = open(/tmp/prog.log) loop{ ... ... f.append('DEBUG .............') ... f.append('INFO .............') ... f.append('WARN .............') if(reload){ break } } f.close() } }
这个程序多了一层循环,并加入了重载功能。这个程序怎样切割日志呢:
split loop { prog run mv /tmp/prog.log /tmp/prog.YYYY-MM-DD.log prog reload }
main (){ loop{ f = open(/tmp/prog.YYYY-MM-DD.log) loop{ ... ... f.append('DEBUG .............') ... f.append('INFO .............') ... f.append('WARN .............') if(reload){ break } } f.close() } }
如果你是程序猿,这个程序可以优化一下,日志文件名自动产生日期 /tmp/prog.YYYY-MM-DD.log 在reload时候重新创建或打开日志。
最操蛋写法,很多初学者会这么干,
log(type, msg){ f = open(/tmp/prog.YYYY-MM-DD.log) f.append(type, msg) f.close() } main(){ ... ... log('INFO','..............') ... ... log('DEBUG','..............') ... ... }
这种代码的适应性非常强,写一个日志函数,但牺牲了IO性能,如果频繁打开/关闭文件同时进行写IO操作,这样的程序很难实现高并发。 所以很多高并发的程序,只会打开一次日志文件(追加模式),不会再运行期间关闭日志文件,直到进程发出退出信号。
让我们看个究竟
我们手工模拟一次日志分割的过程,首先开启三个Shell终端。
第一种情况,日志文件被重命名
终端一,模拟打开日志文件 [root@www.netkiller.cn ~]# cat > /tmp/test.log 终端二,重命名文件 [root@www.netkiller.cn ~]# mv /tmp/test.log /tmp/test.2016.05.05.log 终端一,输入一些内容然后按下Ctrl+D 保存文件 [root@www.netkiller.cn ~]# cat > /tmp/test.log Helloworld Ctrl + D[root@www.netkiller.cn ~]# cat /tmp/test.log cat: /tmp/test.log: No such file or directory
第二种情况,日志文件被删除
终端一,模拟打开日志文件 [root@www.netkiller.cn ~]# cat > /tmp/test.log 终端二,使用lsof查看文件的打开情况 [root@www.netkiller.cn ~]# lsof | grep /tmp/test.log cat 20032 root 1w REG 253,1 0 288466 /tmp/test.log 终端三,删除日志文件 [root@www.netkiller.cn ~]# rm -rf /tmp/test.log 终端二,查看日志的状态,你能看到 deleted [root@www.netkiller.cn ~]# lsof | grep /tmp/test.log cat 5269 root 1w REG 253,1 0 277445 /tmp/test.log (deleted) 终端一,回到终端一种,继续写入一些内容并保存,然后查看日志文件是否有日志记录被写入 [root@www.netkiller.cn ~]# cat > /tmp/test.log Helloworld ^D[root@www.netkiller.cn ~]# cat /tmp/test.log cat: /tmp/test.log: No such file or directory 经过上面两个实验,你应该明白了在日志打开期间对日志文件做重命名或者删除都会造成日志记录的写入失败。
第三种情况,日志没有被删除,也没有被重命名,而是被其他程序做了修改
第一步,终端窗口一中创建一个文件,文件写入一些字符串,这里写入 “one”,然后查看是否成功写入。 [root@www.netkiller.cn ~]# echo one > /tmp/test.log[root@www.netkiller.cn ~]# cat /tmp/test.log one