Muduo网络库包含了一个轻量级的日志库,可达到超过100MB/s的本地文件写入速度。这个基本上已经达到SATA磁盘线速,满足实际应用中的需求。最近在SOLID系统的开发中,想借用Muduo的日志库来进行元数据(metadata)的输出,但是发现Muduo日志库并不支持同时向多文件进行写入:输出函数是全局的,所以输出行为(包括位置)是共享的;在异步日志类中也是共享了一个mutex。这些都导致Muduo日志库原生不支持多文件的输出。而在SOLID系统中,每种协议或者应用的输出格式是不一样的,放在一个文件显得杂乱而且数据库端也不好处理。 我首先尝试了修改Muduo本身:将输出函数变成Muduo::Logger类的成员而非全局,这样每次在使用LOG宏的时候就可以指定输出函数。然后将异步类中的mutex也搞成私有。后来发现核心问题在域LOG_INFO/LOG_ERROR这些宏实际上是生成了一个匿名的临时对象,这些对象的生命周期就在这条语句之后结束。通过析构函数的调用顺便可以进行文件flush等操作。这就要求修改后每次LOG_INFO时都要对输出函数进行重新赋值,这个开销还是有点大。同时在异步日志类中对mutex的依赖相当多,需要大量修改,还是决定重新造个轮子吧。 正好与Muduo网络库搭配的书到了,仔细研读了一下日志库的代码和相应章节,总结了Muduo可以做到磁盘线速写入的主要原因。 1. double-buffer结构,其实就是ping-pong结构。即前端(接收新日志)将日志写入BufferA中,后端(将日志输出到文件)不断的将BufferB中的日志写入文件,同时前端对BufferA做个判断,超过阈值大小就swap两个buffer,这样的好处是锁粒度相当小,前端只在swap的时候加锁,后端每次把BufferB转移到另一个临时变量后也可以释放锁。两个临界区的操作都是常数时间。这是Muduo高效的最主要原因。 2. 小细节。如时间格式化函数首先比较struct tm.tv_sec,相等的话就只修改tv_usec部分就好了,减少了很多格式化工作。时间部分的长度也是固定的,可以预先格式化等等。 按照以上设计概要,我简单实现了一个提供异步写入的日志类。这个类的对象生命周期可认为是永久,通过类型内部的线程进行持续的写入。简单结构如下。 class Log { private: FILE* file_; vstring name_; uint64_t now_size_; thread* thread_; void FormatTime(char t_time[]); void RollFile(); public: Log(vstring name) : name_(name), now_size_(0), now_buf_(), running_(false)...

More