【Linux多线程服务端编程】| 【05】高效的多线程日志
发布时间:2022-11-10 13:09:26 所属栏目:Linux 来源:
导读: 索引
1 简介
【C++模块实现】| 【01】日志系统实现
【日志类型】:
【日志功能】:
日志通常用来故障诊断和追踪、性能分析等;
【日志库】:可分为前、后端;
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都产生不同的文件; (编辑:拼字网 - 核心网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
站长推荐

