目录
在前文中分析了日志消息的存储和输出,不过并没有涉及到异步日志,下面就来分析一下异步日志是如何实现的。
什么是异步日志
在默认的情况下,日志消息都是直接打印到终端屏幕上,但是实际应用中,日志消息都应该写到本地文件,方便记录以及查询。
最简单的方式就是每产生一条日志消息,都将其写到相应的文件中,然而这种方式效率低下,如果很多线程在某一段时间内需要输出大量日志,那么显然日志输出的效率是很低的。之所以效率低,就是因为每条日志消息都需要通过write这类的函数写出到本地磁盘,这就导致频繁调用IO函数,而磁盘操作本身就比较费时,这样一来后面的代码就只能阻塞住,直到前一条日志写出成功。
为了优化上述问题,一个比较好的办法就是:当日志消息积累到一定量的时候再写到磁盘中,这样就可以显著减少IO操作的次数,从而提高效率。
换句话说,当日志消息需要输出时,并不会立即将其写出到磁盘上,而是先把日志消息存储,直到达到”写出时机“才会将存储的日志消息写出到磁盘,这样一来,当日志消息生成时,只需要将其进行存储而不需要写出,后续代码也不会被阻塞,相对于前面的那种阻塞式日志,这种就是非阻塞式日志。
muduo的异步日志核心思想正是如此。当需要输出日志的时候,会先将日志存下来,日志消息存储达到某个阈值时将这些日志消息全部写到磁盘。需要考虑的是,如果日志消息产生比较慢,可能很长一段时间都达不到这个阈值,那就相当于这些日志消息一直无法写出到磁盘,因此,还应当设置一个超时值如3s,每过3s不管日志消息存储量是否达到阈值,都会将已经存储的日志消息写出到磁盘中。即日志写出到磁盘的两个时机:1、日志消息存储量达到写出阈值;2、每过3秒自动将存储的日志消息全部写出。
这种非阻塞式日志也是异步的,因为产生日志的线程只负责产生日志,并不需要去管它产生的这条日志何时写出,写往何处...
异步日志的实现
muduo中通过AsyncLogging类来实现异步日志。
异步日志分为前端和后端两部分,前端负责存储生成的日志消息,而后端则负责将日志消息写出到磁盘,因此整个异步日志的过程可以看做如下所示:
先来看看前端和后端分别指的是什么。
前端与后端
class AsyncLogging : noncopyable
{
public:
AsyncLogging(const string& basename,
off_t rollSize,
int flushInterval = 3);
~AsyncLogging()
{
if (running_)
{
stop();
}
}
void append(const char* logline, int len);
void start()
{
running_ = true;
thread_.start();
latch_.wait(); //等待,直到异步日志线程启动,才能继续往下执行
}
void stop() NO_THREAD_SAFETY_ANALYSIS
{
running_ = false;
cond_.notify();
thread_.join();
}
private:
void threadFunc();
typedef muduo::detail::FixedBuffer<muduo::detail::kLargeBuffer> Buffer;
typedef std::vector<std::unique_ptr<Buffer>> BufferVector;
typedef BufferVector::value_type BufferPtr;
const int flushInterval_; //前端缓冲区定期向后端写入的时间(冲刷间隔)
std::atomic<bool> running_; //标识线程函数是否正在运行
const string basename_; //
const off_t rollSize_;
muduo::Thread thread_;
muduo::CountDownLatch latch_;
muduo::MutexLock mutex_;
muduo::Condition cond_ GUARDED_BY(mutex_); //条件变量,主要用于前端缓冲区队列中没有数据时的休眠和唤醒
BufferPtr currentBuffer_ GUARDED_BY(mutex_); //当前缓冲区 4M大小
BufferPtr nextBuffer_ GUARDED_BY(mutex_); //预备缓冲区,主要是在调用append向当前缓冲添加日志消息时,如果当前缓冲放不下,当前缓冲就会被移动到前端缓冲队列中国,此时预备缓冲区用来作为新的当前缓冲
BufferVector buffers_ GUARDED_BY(mutex_);//前端缓冲区队列
};
注意到这里typedef了一个新类型为Buffer类型,根据其定义可知,它就是前文所说的FixedBuffer缓冲区类型,而这个缓冲区大小由kLargeBuffer指定,大小为4M,因此,Buffer就是大小为4M的缓冲区类型。
这里定义了currentBuffer_和nextBuffer_,这两个缓冲区就是上面所说的”前端“,用来暂时存储生成的日志消息,只不过nextBuffer_用作预备缓冲区,当currentBuffer_不够用时用nextBuffer_来补充currentBuffer_。
然后就是buffers_,