c++ 高性能日志庫(muduo_AsyncLogging)


c++ 高性能日志庫(muduo_AsyncLogging)

實現一個高效的網絡日志庫要解決那些問題?
首先明確一下問題的模型,這是一個典型的多生產者 單消費者問題,對於前端的日志庫使用者來說,應該做到非阻塞添加,作為后端的文件寫入,應該注意磁盤IO的瓶頸。

功能需求

  1. 日志的級別分級
  2. 發生時間和具體線程信息
  3. 線程安全

實現思路

多個線程共有一個前端,通過后端寫入磁盤文件
異步日志是必須的,所以需要一個緩沖區,在這里我們使用的是多緩沖技術,基本思路是准備多塊Buffer,前端負責向Buffer中填數據,后端負責將Buffer中數據取出來寫入文件,這種實現的好處在於在新建日志消息的時候不必等待磁盤IO操作,前端寫的時候也不會阻塞。

實現結構

LogStream 負責寫入消息的格式化
LogFile 負責文件寫入
AsyncLogging 負責實現 多緩沖技術 協調前后端

LogFile

如果有必要就給日至文件加鎖

void LogFile::append(const char* logline, int len)
{
  if (mutex_)
  {
    MutexLockGuard lock(*mutex_);
    append_unlocked(logline, len);
  }
  else
  {
    append_unlocked(logline, len);
  }
}


LogStream

重寫流操作符
構造一個格式轉化類,給日志中消息提供一個統一的格式

AsyncLogging

是及實現采用了四個緩沖區,這樣可以進一步減少前端等待,數據結構

typedef boost::ptr_vector<LargeBuffer> BufferVector;
typedef BufferVector::auto_type BufferPtr;
MutexLock lock;
Condition cond;
BufferPtr nextBuffer;
BufferVector buffers_;

append的具體實現
在當前的緩沖區和備用緩沖區中選擇一個足夠使用的進行寫入。

void AsynLogging::append(const char* logline, int len){
	LockGuard(mutex);
	if(curbuf->avail() > len){//當前緩沖區足夠
		curbuf->append(logline,len);
	}
	else{
		buffers.push_back(curbuf->release());
		if(nextbuf){
			curbuf = std::move(nextbuf);
		}
		else{
			curbuf.reset(new LargeBuffer);	
		}
		curbuf->append(logline, len);
		cond.notify();
	}
}
  • 接收方的后端實現
    首先准備好兩塊空閑的buffer,已備在臨界區內交換,等待條件標量出發的條件又兩個,超時或者是前端寫滿了一個或者多個Buffer,當條件滿足時,先將當前緩沖移入buffer,並且立刻將空閑的newBuffer1作為當前緩沖,接下來將buffers和buffersToWrite交換,隨后將buffersToWrite寫入文件,重新設計設置Buffer。
void AsyncLogging::threadFunc(){
	BufferPtr nweBuffer1(new LargeBuffer);
	BufferPtr newBuffer2(new LargeBuffer);
	BufferVector bufferToWrite;
	while(running_){
	{
		MutexLockGuard lock(mutex);
		if(buffers.empty()){
			cond.wait_for(muted,flushInterval_);
		}
		buffers.push_back(currentBuffer_.release());
		currentBuffer = move(newBuffer1);
		buffersTowrite.swap(buffers_);
		if(!nextBuf){
			nextBuf = std::move(newBuffer2);
		}
	}
	}
}

交給后端去寫入,以及重新設置兩個緩沖區

	   for (size_t i = 0; i < buffersToWrite.size(); ++i)
      {
        // FIXME: use unbuffered stdio FILE ? or use ::writev ?
        output.append(buffersToWrite[i].data(), buffersToWrite[i].length());
      }

      if (buffersToWrite.size() > 2)
      {
        // drop non-bzero-ed buffers, avoid trashing
        buffersToWrite.resize(2);
      }

      if (!newBuffer1)
      {
        assert(!buffersToWrite.empty());
        newBuffer1 = buffersToWrite.pop_back();
        newBuffer1->reset();
      }

      if (!newBuffer2)
      {
        assert(!buffersToWrite.empty());
        newBuffer2 = buffersToWrite.pop_back();
        newBuffer2->reset();
      }

      buffersToWrite.clear();
      output.flush();


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM