高性能日志
1 数据肯定是批量写入的
如果数据单笔写入会造成 磁盘寻址、频繁用户态/内核态的切换 (耗时)
2 fwrite/write 的区别
fwrite()
是C标准库中的函数,而write()
是系统调用接口。fwrite
底层也是通过write来实现的。
二者均都有缓冲区,系统调用write的效率取决于你buf的大小和你要写入的总数量,如果buf太小,你进入内核空间的次数增大,效率就低下。而fwrite会替你做缓存,减少了实际出现的系统调用,所以效率比较高。
假如write按照调用者要求写入十个字节的数据在内核缓冲区,这中间会涉及到用户态与内核态之间的切换。(由于使用了系统调用)
而fwrite不一样,fwrite每次都会先把数据写入一个应用进程缓冲区,等到该缓冲区满了,系统会调用write一次性把相应数据写进内核缓冲区中。同样减少了系统调用(即write调用)。
因此当 buf_size 设置的比较大的时候,write 的性能比较优;当 buf_size 设置的比较小的时候,缓冲区会容易写满,频繁使用系统调用,OS会进行变态,增加耗时
3 log4cpp
3.1 设计理念
-
输出格式
-
c++风格
warn_log.warnStream() << "xxx = " << 100;
箭头运算符重载 -
C语言风格
warn_log.info("xxx = %d", 100);
-
-
输出位置
-
打印到本地文件
log4cpp::FileAppender
-
输出到控制台
log4cpp::OstreamAppender
-
3.2 日志性能的问题
支持回滚的日志(RollignFileAppender)
-
换了一个文件后,会在程序里面记录每次写入的数据大小,总计超过比如1M 再重写另外一个文件
此处log4cpp 错误的做法是:每次写日志都会去调用 lseek 函数去获取文件大小(耗时多),这也是影响效率的关键
FileAppender
每次日志都调用write
异步日志的高性能:支持批量写入,日志达到一定量的时候,才会调用write去写入文件
4 异步设计
4.1 多线程写入
多个线程将日志写入到缓存队列里,需要加锁
4.2 日志什么时候写入磁盘
引入双缓存机制:两个缓存队列。
日志写满一个缓存队列的话,才去notify 日志读取线程将日志从缓存队列中取出然后写入,然后下一条日志写到另外一个缓存队列中去。
而不是一个日志写到缓存队列中就通知线程进行日志读取。
实际做法原理:使用多个大数组缓冲区作为日志缓冲区,多个大数组缓冲区以双循环链表方式连接,并使用两个指针p1和p2指向链表两个节点,分别用以生成数据、与消费数据
生产者可以是多线程,共同持有p1来生产数据,消费者是一个后台线程,持有p2去消费数据
4.3 如果缓存队列快满了(还未满),导致日志长时间未写入
日志写线程:notify 唤醒 + 超时唤醒
4.4 程序崩溃了,日志没有及时写入
使用gdb,打开-g,可以产生coredump,通过coredump方式