【日至组件】日至后端

本节将研究日至组件的后端,并给出C++实现;

日志后端

说明几点:

(1)首先线程安全性,即多个线程可以并发的写日至到后端,而多个线程的日至不会出现交织,比如在多线程中使用cout时,经常会发现数据出现交叉的情况,这是因为cout << x  << y在输出cout << x 和cout << y并不是一个原子操作,可被其他的线程打断的;

(2)在如下示意图中,日至后端库维护3个缓冲池,一个是empty buffer缓冲池,前端可以将数据写入到该缓冲池中;另一个是full buffer缓冲池,后端负责要将该缓冲池的内容写入到日至文件中;当empty buffer的一个buffer满时,将会把它放入到full buffer中;最后一个缓冲池是swap buffers,它是空的,在进行写日至之前,首先和full buffers进行交换一下,这样full buffers就变为空,这样后端对日至的写就针对swap
buffers来了(耗时时间可能很长,如果使用full buffers将会阻塞current buffer放入到full buffers)
,而full buffers可以继续来放入current buffer,因此不会阻塞前端写日至的操作;

(3)当前端出现将大量的数据写入到empty buffer中时,empty buffer可能已经为空,此时empty buffer是可以动态增长的;万一前端出现死循环,将大量的数据写入到empty buffer,进而full buffers也会出现大量日至内容,此时程序仅仅是丢弃full buffers多余的日至内容;最后将swapBuffers的内容回收到empty buffer时,empty buffers可能会有较多的buffer内存,empty buffers也是会受限制的;

AsyncLogger

AsyncLogger的声明

class AsyncLogger final
{
public:
  AsyncLogger(const AsyncLogger&) = delete;
  AsyncLogger& operator=(const AsyncLogger&) = delete;

  explicit AsyncLogger(size_t maxEmptySmallBuffersSize = 10, size_t maxfullSmallBuffersSize = 20, size_t fflushInterval = 5);

  ~AsyncLogger();

  void start();

  void stop();

  void append(const char* buf, size_t len);

  bool running() const
  {
    return _running;
  }
public:
  void _takeEmptyBufferUnlock();

  void _asyncLogger();

  Base::Thread _thread;
  bool _running;

  std::vector<std::shared_ptr<SmallBuffer>> _emptyBuffers;
  std::vector<std::shared_ptr<SmallBuffer>> _fullBuffers;
  std::shared_ptr<SmallBuffer> _currentBuffer;

  Base::Mutex _mutex;
  Base::Condition _cond;

  const size_t  cMaxEmptySmallBuffersSize;
  const size_t  cMaxFullSmallBuffersSize;
  const size_t _fflushInterval;
};

说明几点:

(1)append(const char* buf, size_t len)是提供给日至前端写数据的接口,在前一篇博客描述日至前端的时候介绍过日至前端的默认输出是通往控制台的,而如今通过该接口,日至前端就可以将日至内容写入到该后端;

(2)void _asyncLogger()就是日至后端写出数据至日至文件的主要函数;

(3) const size_t  cMaxEmptySmallBuffersSize; 和const size_t  cMaxFullSmallBuffersSize;分别是empty buffers和full buffers维护的缓冲池的最大大小,而 const size_t _fflushInterval;表示当full buffers为空时,后端异步写入将等待的时间,超过该时间后端会将日至内容写入,即使当前的full buffers为空,也会将current buffer写入;

AsyncLogger其他

AsyncLogger::AsyncLogger(size_t maxEmptySmallBuffersSize, size_t maxFullSmallBuffersSize, size_t fflushInterval) :
    _thread(std::bind(&AsyncLogger::_asyncLogger, this)),
    _running(false),
    _currentBuffer(new SmallBuffer),
    _mutex(),
    _cond(_mutex),
    cMaxEmptySmallBuffersSize(maxEmptySmallBuffersSize),
    cMaxFullSmallBuffersSize(maxFullSmallBuffersSize),
    _fflushInterval(fflushInterval)
{
  _emptyBuffers.reserve(10);
}

AsyncLogger::~AsyncLogger()
{
  if (_running)
    stop();
}

void AsyncLogger::start()
{
  assert(!_running);
  _running = true;
  _emptyBuffers.push_back(shared_ptr<SmallBuffer>(new SmallBuffer));
  _emptyBuffers.push_back(shared_ptr<SmallBuffer>(new SmallBuffer));

  _thread.start();
}

void AsyncLogger::stop()
{
  assert(_running);
  _running = false;       //important
  _thread.join();
}

日至前端写入的接口

void AsyncLogger::append(const char* buf, size_t len)
{
  assert(_running);
  {
    MutexLockGuard lock(_mutex);
    if ( _currentBuffer->avail() < len)
      {
        _takeEmptyBufferUnlock();
      }

    _currentBuffer->append(buf, len);
  }

  _cond.wake();
}

void AsyncLogger::_takeEmptyBufferUnlock()
{
  _fullBuffers.push_back(_currentBuffer);

  if (_emptyBuffers.empty())
    {
      _currentBuffer.reset(new SmallBuffer);
    }
  else
    {
      _currentBuffer = _emptyBuffers.back();
      _emptyBuffers.pop_back();
    }
}

说明几点:

(1)设置后端的输出类型如下:

  AsyncLogger async;
  setDefualtFunc(std::bind(&AsyncLogger::append, &async, std::placeholders::_1, std::placeholders::_2));

(2)在append函数,如果发现current buffer的缓冲已经不足存放当前日至内容时,就需要重新获得新的空缓冲了,主要是通过_takeEmptyBufferUnlock()来获得;

后端写出数据至日至文件

void AsyncLogger::_asyncLogger()
{
  assert(_running);
  vector<shared_ptr<SmallBuffer>> swapBuffers;
  LogFile output;
  while (_running)
    {
      {
        MutexLockGuard lock(_mutex);
        if (_fullBuffers.empty())
          {
            _cond.waitForSeconds(_fflushInterval);    //important
          }

        _takeEmptyBufferUnlock();

        swapBuffers.swap(_fullBuffers);
        assert(_fullBuffers.empty());
      }

      if (swapBuffers.size() > cMaxFullSmallBuffersSize)
        {
          std::copy(swapBuffers.end() - cMaxFullSmallBuffersSize , swapBuffers.end() , swapBuffers.begin());
          swapBuffers.resize(cMaxFullSmallBuffersSize);
        }

for (auto& buffer : swapBuffers)
        {
          output.output(buffer->buffer(), buffer->used());    //刷入
          buffer->reset();      //impotant
        }

      {
        MutexLockGuard lock(_mutex);          //将full buffer回收
        std::copy(swapBuffers.begin(), swapBuffers.end(), back_inserter(_emptyBuffers));
        if (_emptyBuffers.size() > cMaxEmptySmallBuffersSize)
          _emptyBuffers.resize(cMaxEmptySmallBuffersSize);
      }

      swapBuffers.clear();
      output.fflush();
    }

  output.fflush();
}

说明几点:

(1)swapBuffers刚开始是空的,通过swapBuffers.swap(_fullBuffers);后_fullBuffers也就变为空了,后面针对swapBuffers的操作也就不需要加锁了;_cond.waitForSeconds(_fflushInterval); 即为后端写入线程等待的时间;

(2)如下代码就是将日至内容写入到日至文件,日至文件LogFile output将在下文介绍;

for (auto& buffer : swapBuffers)
        {
          output.output(buffer->buffer(), buffer->used());    //写入
          buffer->reset();      //impotant
        }

(3)if (swapBuffers.size() > cMaxFullSmallBuffersSize)和 if (_emptyBuffers.size() > cMaxEmptySmallBuffersSize)两个判断主要就是针对full buffers和empty buffers的最大大小的限制做出的处理,对于full buffers将会丢弃多余的日至内容,对于empty buffers是只要维持到一定的缓冲量,多余的内存也将会回收给系统;

日至文件

File的声明

class File final
{
public:
  File(const File&) = delete;
  File& operator=(const File&) = delete;

  explicit File(const std::string& name);

  ~File();

  void append(const char* buf, size_t len);

  std::string name() const
  {
    return _name;
  }

  void fflush();

private:
  typedef StreamBuffer<cLargeBufferSize> Buffer;

  std::string _name;
  FILE* _file;
  Buffer _buffer;
};

FIle的实现

File::File(const std::string& filename):
    _name(filename),
    _file(::fopen(_name.c_str(), "a+"))
{
  if (!_file)
    {
      LOG_SYSERR << "fopen error";
    }
  else
    {
      if (setvbuf(_file, const_cast<char *>(_buffer.buffer()), _IONBF, _buffer.avail()) != 0)
        LOG_SYSERR << "setvbuf error";
    }
}

File::~File()
{
  if (_file)
    ::fclose(_file);
}

void File::append(const char* buf, size_t len)
{
  if (::fwrite(buf, 1, len, _file) != len)
    {
      LOG_WARN << "fwrite complete error";
    }
}

void File::fflush()
{
  if (_file)
    ::fflush(_file);
}

说明几点:

(1)在File的构造函数中,将会设置File的缓冲大小,是一个StreamBuffer;

LogFile的声明

class File;

class LogFile final
{
public:
  LogFile(const LogFile&) = delete;
  LogFile& operator=(const LogFile&) = delete;

  explicit LogFile(size_t fflushInterval = 5 * 60 * 60);

  void output(const SmallBuffer& buffer);

  void output(const char* buf, size_t size);

  void fflush();

  size_t fflushInterval() const
  {
    return _fflushInterval;
  }
private:
  std::string _getLogFileName();

  std::unique_ptr<File> _file;
  const size_t _fflushInterval;
  size_t _totalWriteBytes;
  Base::TimeStamp _last;

  const size_t cLargestFileSize = 1000 *  1024 * 1024;   //C++ 11
};

说明几点:

(1)当日至文件超过cLargestFileSize大小时,将会更换日至文件;fflushInterval为日至内容写入到磁盘的时间间隔;

(2)_getLogFileName()为如何产生日至文件的文件名;

LogFile其他

LogFile::LogFile(size_t interval):
    _file(new File(_getLogFileName())),
    _fflushInterval(interval),
    _totalWriteBytes(0),
    _last(TimeStamp::now())
{

}

void LogFile::output(const SmallBuffer& buffer)
{
  output(buffer.buffer(), buffer.avail());
}

void LogFile::fflush()
{
  _file->fflush();
}

string LogFile::_getLogFileName()
{
  char buf[32];
  snprintf(buf, sizeof(buf), "-[pid:%d].log", ::getpid());

  return TimeStamp::now().toFormattedString() + buf;
}

日至文件写入

void LogFile::output(const char* buf, size_t size)
{
  if (!_file)
    {
      _file.reset(new File(_getLogFileName()));
      _last = TimeStamp::now();
      _totalWriteBytes = 0;
    }

  _file->append(buf, size);
  _totalWriteBytes += size;

  TimeStamp now = TimeStamp::now();
  if (static_cast<size_t>(now - _last) >= _fflushInterval)
    {
      _file->fflush();
      _last = now;
    }

  if (_totalWriteBytes > cLargestFileSize)
    {
      _file.reset(new File(_getLogFileName()));      //impotant, maybe not a new file (Time will influnce filename)
      _totalWriteBytes = 0;
    }
}

说明几点:

(1)当_file为空时,产生一个新的日至文件;_getLogFileName()为如何产生日至文件的文件名;

(2)当日至文件超过cLargestFileSize大小时,将会更换日至文件;fflushInterval为日至内容写入到磁盘的时间间隔;

(3)_file->append(buf, size);为具体的将日至内容写入到磁盘文件的过程,此时日至文件内容已经至少在在File的缓冲中;

测试

#include <Log/AsyncLogger.h>
#include <Log/LogStream.h>
#include <Log/Logger.h>
#include <Base/Thread.h>
#include <Base/Mutex.h>
#include <unistd.h>

using namespace Log;
using namespace Base;

int main(void)
{
  AsyncLogger async;
  setDefualtFunc(std::bind(&AsyncLogger::append, &async, std::placeholders::_1, std::placeholders::_2));
  Logger::setCurrentLogLevel(Logger::LogLevel::DEBUG);

  async.start();

  int count = 0;
  while (count++ < 1000)
    {
      LOG_SYSERR << ++count << "tid " << CurrentThread::tid();

      LOG_ERROR << ++count << "tid " << CurrentThread::tid();;

      LOG_WARN << ++count << "tid " << CurrentThread::tid();

      LOG_INFO << ++count << "tid " << CurrentThread::tid();

      LOG_DEBUG  << ++count << "tid " << CurrentThread::tid();

      LOG_TRACE  << ++count << "tid " << CurrentThread::tid();

    }

  return 0;
}

日至文件20150328 08:21:35.728767957-[pid:16918].log最后几行的输出:

0150328 08:21:35.743687102 16918 WARN   AsyncLoggerTest.cc:29 main  - 988tid 16918
20150328 08:21:35.743695832 16918 INFO   AsyncLoggerTest.cc:31 main  - 989tid 16918
20150328 08:21:35.743704563 16918 DEBUG  AsyncLoggerTest.cc:33 main  - 990tid 16918
20150328 08:21:35.743713363 16918 SYSERR AsyncLoggerTest.cc:25 main  - 992tid 16918
20150328 08:21:35.743722372 16918 ERROR  AsyncLoggerTest.cc:27 main  - 993tid 16918
20150328 08:21:35.743731382 16918 WARN   AsyncLoggerTest.cc:29 main  - 994tid 16918
20150328 08:21:35.743740321 16918 INFO   AsyncLoggerTest.cc:31 main  - 995tid 16918
20150328 08:21:35.743749401 16918 DEBUG  AsyncLoggerTest.cc:33 main  - 996tid 16918
20150328 08:21:35.743758340 16918 SYSERR AsyncLoggerTest.cc:25 main  - 998tid 16918
20150328 08:21:35.743775172 16918 ERROR  AsyncLoggerTest.cc:27 main  - 999tid 16918
20150328 08:21:35.743784251 16918 WARN   AsyncLoggerTest.cc:29 main  - 1000tid 16918
20150328 08:21:35.743793401 16918 INFO   AsyncLoggerTest.cc:31 main  - 1001tid 16918
20150328 08:21:35.743808207 16918 DEBUG  AsyncLoggerTest.cc:33 main  - 1002tid 16918

时间: 2024-10-10 05:10:42

【日至组件】日至后端的相关文章

西北政法大学8月28日至29日网上报名,1011日至12日参加考试

2014年西北政法大学8月28日至29日网上报名,10月11日至12日参加考试 新疆2015年成人高考报名时间.入口专题 新疆2015年成人高考报名 登录新疆成人高考报名网站

2015年9月13日和15日【shell、sed&awk 的使用(一)】-JY1506402-19+liuhui880818

目录: 一.shell程序的运行原理 二.shell常用技巧 1.命令历史 2.命令别名 3.命令引用 4.文件名通配 5.常用快捷键 6.补全功能 7.输入输出重定向和管道 8.与用户交互命令 9.脚本的规范建立与执行 10.bash常用选项 11.命令状态结果 持续发现中... 三.常用知识点说明 1.变量 1)变量类型 2)本地变量 3)环境变量 4)特殊变量 2.条件测试 1)整数测试 2)字符串测试 3)文件测试 4)测试表达式 3.条件判断(选择执行) 4.脚本参数(位置参数变量)

Linux学习笔记十三周二次课(5月3日、4日)

十三周二次课(5月3日.4日) 复习LNMP 扩展 nginx中的root和alias区别 http://blog.csdn.net/21aspnet/article/details/6583335 nginx的alias和root配置 http://www.ttlsa.com/nginx/nginx-root_alias-file-path-configuration/ http://www.iigrowing.cn/shi-yan-que-ren-nginx-root-alias-locat

输入两个日期(年 月 日 年 月 日), 输出这两个日期之间差多少天

假定输入日期合法,且第二个日期晚于第一个日期. 我的想法是首先判断是不是同一年,如果是,则用第二个日期在当年的天数减去第一个日期在当年的天数即可: 如果不是,刚把中间间隔的天数分成三部分,第一部分为第一个日期到当年末的天数,第二部分为第一个日期的第二年第一天到第二个日期的前一年最后一天的天数,第三个部分为第二个日期的当年第一天到第二个日期的天数.三个部分相当即得相差天数. 特别要注意的是闰年一定要记得判断. 1 #ifndef __FUN_H__ 2 #define __FUN_H__ 3 #i

JS计算从某年某月某日到某年某月某日的间隔天数差

直接贴代码了,你直接拷贝然后另存为html就可以用了,不多说,请看: <!DOCTYPE html> <html lang="en" xmlns="http://www.w3.org/1999/xhtml"><head> <meta charset="utf-8" /> <title></title> <script type="text/javascript

每周一书-2016年8月8日到14日获奖读者公布

今天是周日,本周的每周一书活动,为大家送出的书是 李笑来的<把时间当朋友>.活动过程中,感谢大家的积极响应.下面我们摘录一些留言,然后公布获奖者. KILE 我们的孤独,常常与单身无关. 七夕最有感触的一句话. 更多留言,请打开微信订阅号,到<单身狗,你并不孤单>这篇文章里看吧.本周的幸运读者,新鲜出炉,他就是: 接下来请“永梅”同学,微信订阅号回复你的联系方式吧,明天就给你寄书喽. 没有得到书的小伙伴,也不要忧伤,每周一书,每周都有新惊喜.下一周,也就是明天了.从8月15日到8月

17日至18日将上演“双星伴月” 全国各地均可见r

"为了隔绝油烟和气味,杨大姐家的窗户不管什么季节都关得死死的事发后,武安市委.市政府主要.主管领导带领相关单位负责人赶赴现场处置后续工作.面对工作基础相对薄弱的状况,要实现赶超和跨越,山西公安怎么办?神奇之处是,"束束"戴上它,在手机上发微信.玩游戏,操控自如."孩子家长不堪其扰准备搬家截至11月20日,离事发时间已过去5个多月,但双方就医疗费用由谁支付仍未达成一致意见.这句话的由来不得而知,但"回家吃饭"不断升级,从网络走入现实生活.华泰证券股

1月10日,11日工作情况

1月10日 在西区网自和毛一起讨论了一下前端的问题,我负责做团队页面的任务进度这一块的进度条和子项部分, 想把layui模板的时间轴修改为一个简洁的下拉列表,思路是通过onclick事件修改按钮对应内容的display属性实现显示和隐藏. 试图在class不变的情况下把标签修改为<button>,未成功. 试图把原标签放在<button>里面,未成功. 在网上查找错误原因无果后,写了一了一下任务进度的html,排了一下版. 1月11日 下午鼓捣github desktop(为了解决

通过批处理文件使用7zip执行备份,将1日和15日的备份再另外备份,定时清理过期备份

7z.exe是个好东东,N多年前,当时试用了各种命令行压缩软件,效果都不怎么样,那时7zip出来不久,发现它也有命令行模式,然后试用一下,发现可用性很好,压缩率.效率也很不错.当时试用过winrar和rar,效果都不太好. C:\7-Zip\7z.exe a -sdel "E:\Backup\Zip_%date:~0,4%_%date:~5,2%_%date:~8,2%.7z" "E:\Backup\%date:~0,4%_%date:~5,2%_%date:~8,2%.dm