加入收藏 | 设为首页 | 会员中心 | 我要投稿 拼字网 - 核心网 (https://www.hexinwang.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 服务器 > 搭建环境 > Linux > 正文

【Linux多线程服务端编程】| 【05】高效的多线程日志

发布时间:2022-11-10 13:09:26 所属栏目:Linux 来源:
导读:  索引

  1 简介

  【C++模块实现】| 【01】日志系统实现

  【日志类型】:

  【日志功能】:

  日志通常用来故障诊断和追踪、性能分析等;

  【日志库】:可分为前、后端;
  索引
 
  1 简介
 
  【C++模块实现】| 【01】日志系统实现
 
  【日志类型】:
 
  【日志功能】:
 
  日志通常用来故障诊断和追踪、性能分析等;
 
  【日志库】:可分为前、后端;
 
  【如何将数据高效地传输到后端】
 
  【前端API风格】:
 
  2 功能需求 日志消息格式可配置;可设置运行时过滤器,控制不同组件的日志消息的级别的目的地;上述中,第一项必须,其余……;
 
  日志文件压缩与归档
 
  如何解决崩溃,日志丢失
 
  日志格式
 
  建议
 
  运行时的日志过滤器,控制不同的部件的输出日志级别,但可用放到编译器做,让整个程序有一个整体的输出级别即可;
 
  3 性能需求
 
  【优化】
 
  4 多线程异步日志
 
  【直接写日志】
 
  muduo双缓冲技术
 
  【接受方】:
 
  在这里插入图片描述
 
  re-fill newBufer1和newBuffer2会被buffer重新填充,便于替换前端的当前缓冲和预备缓冲,四个缓冲会在启动时填为0,避免出现page fault;
 
  4.1 前后端交互情况
 
  前后端各有两个缓冲区以及一个缓冲区数组;
 
  【前端写日志频度不高,后端3s后超时写入文件】
 
  在这里插入图片描述
 
  【3s超时前写满了缓冲区,唤醒后端线程开始写入文件】
 
  在这里插入图片描述
 
  【前端需要分配新buffer的情况】
 
  【文件写入速度慢,导致前端耗尽了两个缓冲区,并分配新的缓冲区】
 
  在这里插入图片描述
 
  4.2 改进措施
 
  在这里插入图片描述
 
  4.3 若日志消息堆积,该如何处理
 
  即前端陷入死循环,发送的日志,超过后端处理;
 
  4.4 部分源码
 
  // 往队列增加数据
  void AsyncLogging::append(const char* logline, int len)
  {
    muduo::MutexLockGuard lock(mutex_);
    // 若缓冲区的长度满足加入数据的长度,则直接添加
    if (currentBuffer_->avail() > len)
    {
      currentBuffer_->append(logline, len);
    }
    
    else
    {
     // 将当前缓冲区添加到写入队列中
      buffers_.push_back(std::move(currentBuffer_));
   // 判断下一个缓冲区是否有效,若有效,则将该缓冲区分配给当前缓冲区
      if (nextBuffer_)
      {
        currentBuffer_ = std::move(nextBuffer_);
      }
      else
      {
       // 若没有空间,则重新申请
        currentBuffer_.reset(new Buffer); // Rarely happens
      }
   // 将添加的数据追加的当前缓冲区中
      currentBuffer_->append(logline, len);
   // 提醒线程写入
      cond_.notify();
    }
  }
  // 执行线程函数:写入磁盘
  void AsyncLogging::threadFunc()
  {
    assert(running_ == true);
    latch_.countDown(); // 数量减一
    LogFile output(basename_, rollSize_, false); // 打开logFile
    BufferPtr newBuffer1(new Buffer); // 缓冲区1
    BufferPtr newBuffer2(new Buffer); // 缓冲区2
    newBuffer1->bzero();
    newBuffer2->bzero(); // 初始化为0
    BufferVector buffersToWrite; // 写入队列
    buffersToWrite.reserve(16); // 设置大小为16
    
    while (running_)
    {
      assert(newBuffer1 && newBuffer1->length() == 0);
      assert(newBuffer2 && newBuffer2->length() == 0);
      assert(buffersToWrite.empty());
      {
        muduo::MutexLockGuard lock(mutex_);
     // 等待秒数到达
        if (buffers_.empty())  // unusual usage!
        {
          cond_.waitForSeconds(flushInterval_);
        }
     // 将当前缓冲区移动到所有数据都添加到写入队列中
        buffers_.push_back(std::move(currentBuffer_));
     // 将buf1的缓冲区交给当当前缓冲区
        currentBuffer_ = std::move(newBuffer1);
     // 将队列转移到写入队列
        buffersToWrite.swap(buffers_);
   // 若该缓冲区为空,则将buf2给它
     if (!nextBuffer_)
        {
          nextBuffer_ = std::move(newBuffer2);
        }
      }
      assert(!buffersToWrite.empty());
   // 限制大小,若数据堆积太多,则直接删除
      if (buffersToWrite.size() > 25)
      {
        char buf[256];
        snprintf(buf, sizeof buf, "Dropped log messages at %s, %zd larger buffers\n",
                 Timestamp::now().toFormattedString().c_str(),
                 buffersToWrite.size()-2);
        fputs(buf, stderr);
        output.append(buf, static_cast<int>(strlen(buf))); // 追加
        buffersToWrite.erase(buffersToWrite.begin()+2, buffersToWrite.end());
      }
   // 遍历队列
      for (const auto& buffer : buffersToWrite)
      {
        // FIXME: use unbuffered stdio FILE ? or use ::writev ?
        output.append(buffer->data(), buffer->length());
      }
   // 删除其他无关的数据,只保留2个
      if (buffersToWrite.size() > 2)
      {
        // drop non-bzero-ed buffers, avoid trashing
        buffersToWrite.resize(2);
      }
   // 若buf1为空
      if (!newBuffer1)
      {
        assert(!buffersToWrite.empty());
   // 将最后一个队列给它
     newBuffer1 = std::move(buffersToWrite.back());
        buffersToWrite.pop_back();
        newBuffer1->reset();
      }
   // 若buf2为空
      if (!newBuffer2)
      {
        assert(!buffersToWrite.empty());
     // 将最后一个队列给它
        newBuffer2 = std::move(buffersToWrite.back());
        buffersToWrite.pop_back();
        newBuffer2->reset();
      }
      buffersToWrite.clear(); // 清空
      output.flush(); //
    }
    output.flush();
  }
  5 其他方案
 
  【使用队列】
 
  高效的前后端消息传递可使用BlockingQueue/BoundedBlockingQueue线程池linux,但需要每条日志都分配内存,后端线程需要将其释放;
 
  【更改core dump文件名】
 
  通过sysctl设置kernel.core_pattern参数或修改/proc/sys/kernel/core_pattern,让core dump都产生不同的文件;
 

(编辑:拼字网 - 核心网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!