前言
最近在做一个新零售的收银app,对于app稳定性要求比较高,但是难免会出现一些难以复现的问题,针对这些问题,分析日志有时候是解决问题的必要手段。下面我们主要分析下日志写入方案的实现。详细代码可参考AwesomeLog,如果能够帮到你,希望给个star,感谢。
常规方案的缺陷
- 性能问题:一开始日志的写入就是通过标准I/O直接写文件,当有一条日志要写入的时候,首先,打开文件,然后写入日志,最后关闭文件。但是写文件是 IO 操作,随着日志量的增加,更多的IO操作,一定会造成性能瓶颈。为什么这么说呢?因为数据从程序写入到磁盘的过程中,其实牵涉到两次数据拷贝:一次是用户空间内存拷贝到内核空间的缓存,一次是回写时内核空间的缓存到硬盘的拷贝。当发生回写时也涉及到了内核空间和用户空间频繁切换。
- 丢日志:为了解决性能问题,直接想到就是减少I/O操作,我们可以先把日志缓存到内存中,当达到一定数量或者在合适的时机将内存里的日志写入磁盘中。这样似乎可以减少I/O操作,但是在将内存里的日志写入磁盘的过程中,app被强杀了或者Crash了的话,这样会造成更严重的问题,日志丢失。
看到这里,难道真的就没有高性能又能保证日志完整性的方案了吗?答案是mmap。mmap是个什么鬼?我们接着往下看。
什么是mmap
mmap是一种内存映射文件的方法,即将一个文件或者其他对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对应关系;实现这样的映射关系后,进程就可以采用指针的方式读写操作这一块内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必调用read,write等系统调用函数,相反,内核空间堆这段区域的修改也直接反应到用户空间,从而可以实现不同进程间的文件共享。
网上很多文章都说 mmap 完全绕开了页缓存机制,其实这并不正确。我们最终映射的物理内存依然在页缓存中,它可以带来的好处有:
- 减少系统调用。我们只需要一次 mmap() 系统调用,后续所有的调用像操作内存一样,而不会出现大量的 read/write 系统调用。
- 减少数据拷贝。普通的 read() 调用,数据需要经过两次拷贝;而 mmap 只需要从磁盘拷贝一次就可以了,并且由于做过内存映射,也不需要再拷贝回用户空间。
- 可靠性高。mmap 把数据写入页缓存后,跟缓存 I/O 的延迟写机制一样&#x