Linux基础组件之muduo日志库分析

news/2024/5/17 22:42:32/文章来源:https://blog.csdn.net/Long_xu/article/details/127276155

muduo日志库分析

  • 异步日志机制
  • 双缓存机制
  • 前台日志写入栈
  • 后台日志(落盘)写入栈
  • 使用示例
  • 总结
  • 后言

异步日志机制

日志队列
写日志
取日志
日志出队列,
mutex+wait_timeout
日志入队列,
mutex+notify
线程1
线程2
线程3
线程...
日志落盘线程
磁盘

通过notify和超时方式唤醒日志落盘线程读取日志写入磁盘。
多线程间使用mutex互斥保证线程安全。
日志写入磁盘时采用批量写入方式。

注意:队列不是每一行日志,而是buffer缓冲区(比如4M)。

双缓存机制

日志写入过程(假设buffer为4M):
(1)加锁,判断当前的buffer是否超过4M。
(2)如果没有超过4M,把日志写入buffer;如果超出4M则把当前的buffer插入到队列中。此时,当前日志写到一个新的buffer(循环复用的buffer)中。

转换
B已满
交换A
日志文件
写入日志
buffer B
buffer A
A已满
交换B
日志文件
写入日志
buffer A
buffer B

log_buffer
日志notify问题:
(1)写满1个buffer才发一次notify唤醒日志落盘。
(2)超时通过wait_timeout唤醒日志落盘线程,buffer只要有数据就写入到磁盘。

双缓冲机制中循环使用buffer,避免buffer不断分配。

void AsyncLogging::append(const char* logline, int len)
{// if(cnt++ == 50000)abort();MutexLockGuard lock(mutex_);    // 多线程加锁if (currentBuffer_->avail() > len)    // 判断buffer还有没有空间写入这条日志{currentBuffer_->append(logline, len); // 直接写入}else{buffers_.push_back(std::move(currentBuffer_));    // buffers_是vector,把buffer入队列// printf("push_back append_cnt:%d, size:%d\n", ++append_cnt, buffers_.size());if (nextBuffer_)  // 用了双缓存{currentBuffer_ = std::move(nextBuffer_);    // 如果不为空则将buffer转移到currentBuffer_}else{// 重新分配buffercurrentBuffer_.reset(new Buffer); // Rarely happens如果后端写入线程没有及时读取数据,那要再分配buffer}currentBuffer_->append(logline, len);   // buffer写满了cond_.notify(); // 唤醒写入线程}
}void AsyncLogging::threadFunc()
{assert(running_ == true);latch_.countDown();LogFile output(basename_, rollSize_, false);BufferPtr newBuffer1(new Buffer); // 是给currentBuffer_BufferPtr newBuffer2(new Buffer); // 是给nextBuffer_newBuffer1->bzero();newBuffer2->bzero();BufferVector buffersToWrite;    // 保存要写入的日志buffersToWrite.reserve(16);while (running_){assert(newBuffer1 && newBuffer1->length() == 0);assert(newBuffer2 && newBuffer2->length() == 0);assert(buffersToWrite.empty());{ // 锁的作用域MutexLockGuard lock(mutex_);if (buffers_.empty())  // 没有数据可读取,休眠{// printf("waitForSeconds into\n");cond_.waitForSeconds(flushInterval_);   // 超时退出或者被唤醒(收到notify)// printf("waitForSeconds leave\n");}buffers_.push_back(std::move(currentBuffer_));  // currentBuffer_被锁住  currentBuffer_被置空// printf("push_back threadFunc:%d, size:%d\n", ++threadFunc_cnt, buffers_.size());currentBuffer_ = std::move(newBuffer1); // currentBuffer_ 需要内存空间buffersToWrite.swap(buffers_);          // 用了双队列,把前端日志的队列所有buffer都转移到buffersToWrite队列if (!nextBuffer_)     // newBuffer2是给nextBuffer_{nextBuffer_ = std::move(newBuffer2);  // 如果为空则使用newBuffer2的缓存空间}}// 从这里是没有锁,数据落盘的时候不要加锁assert(!buffersToWrite.empty());// fixme的操作 4M一个buffer *25 = 100Mif (buffersToWrite.size() > 25)  // 这里缓存的数据太多了,比如4M为一个buffer空间,25个buffer就是100M了。{printf("Dropped\n");char buf[256];snprintf(buf, sizeof buf, "Dropped log messages at %s, %zd larger buffers\n",Timestamp::now().toFormattedString().c_str(),buffersToWrite.size()-2);    // 只保留2个bufferfputs(buf, stderr);output.append(buf, static_cast<int>(strlen(buf)));buffersToWrite.erase(buffersToWrite.begin()+2, buffersToWrite.end());   // 只保留2个buffer(默认4M)}for (const auto& buffer : buffersToWrite)  // 遍历buffer{// FIXME: use unbuffered stdio FILE ? or use ::writev ?output.append(buffer->data(), buffer->length());    // 负责fwrite数据}output.flush();   // 保证数据落到磁盘了if (buffersToWrite.size() > 2){// drop non-bzero-ed buffers, avoid trashingbuffersToWrite.resize(2);   // 只保留2个buffer}if (!newBuffer1){assert(!buffersToWrite.empty());newBuffer1 = std::move(buffersToWrite.back());    // 复用buffer对象buffersToWrite.pop_back();newBuffer1->reset();    // 重置}if (!newBuffer2){assert(!buffersToWrite.empty());newBuffer2 = std::move(buffersToWrite.back());   // 复用buffer对象buffersToWrite.pop_back();newBuffer2->reset();   // 重置}buffersToWrite.clear(); }output.flush();
}

前台日志写入栈

流程图:

未满
已满
LOG INFO
Logger
output,同步或者异步的方式写入日志
AsyncLogging::append
多线程加锁,线程安全,MutexLockGuard lock ( mutex_ )
判断是否写满buffer
直接写入buffer
当前buffer插入到buffer队列
判断nextBuffer_是否为空
复用
重新分配
写入buffer
唤醒日志落盘线程,cond_.notify()

具体实现流程:
log_info

后台日志(落盘)写入栈

流程图:

遍历buffer
负责fwrite数据
fflush(),保证数据落到磁盘了
for (const auto& buffer : buffersToWrite)  // 遍历buffer
{// FIXME: use unbuffered stdio FILE ? or use ::writev ?output.append(buffer->data(), buffer->length());    // 负责fwrite数据
}
output.flush();   // 保证数据落到磁盘了

使用示例

#include "AsyncLogging.h"#include <stdio.h>
#include <sys/resource.h>
#include <unistd.h>
#include <sys/time.h>
#include <iostream>
#include "Logging.h"
#define LOG_NUM 5000000 // 总共的写入日志行数using namespace std;off_t kRollSize= 1 * 1000 * 1000;    // 只设置1Mstatic AsyncLogging *g_asyncLog = NULL;
static void asyncOutput(const char *msg, int len)
{g_asyncLog->append(msg, len);
}// 时间戳
static uint64_t get_tick_count()
{struct timeval tval;uint64_t ret_tick;gettimeofday(&tval, NULL);ret_tick = tval.tv_sec * 1000L + tval.tv_usec / 1000L;return ret_tick;
}int main(int argc,char*argv[])
{printf("PID = %d\n",getpid());char name[260] = { 0 };strncpy(name, argv[0], sizeof name - 1);// 设置 回滚大小kRollSize(1M), 最大1秒刷一次盘(flush)AsyncLogging log(::basename(name), kRollSize, 1);Logger::setOutput(asyncOutput);g_asyncLog = &log;// 启动日志写入线程log.start();uint64_t begin_time = get_tick_count();cout << "name: " << basename(name) << "\nbegin time: " << begin_time << endl;for (int i = 0; i < LOG_NUM; i++){LOG_INFO << "NO." << i << " Root Error Message!"; // 47个字节}log.stop();uint64_t end_time = get_tick_count();std::cout << "end_time: " << end_time << std::endl;int64_t ops = LOG_NUM;ops = ops * 1000 / (end_time - begin_time);std::cout << "need the time1: " << end_time << " " << begin_time << ", " << end_time - begin_time << "毫秒"<< ", ops = " << ops << "ops/s\n";return 0;
}

总结

(1)日志可以采用批量写入(以数据大小为判断为准)来做到高性能。
同步方式通过攒够数据(比如4M)或者时间超过一定阈值(比如1秒)触发写入。比如glog日志库。
异步方式(比如moduo日志库)采用append积攒数据,异步落盘线程负责数据写入磁盘。

什么时候触发? ------> notify+wait_timeout,即 通知唤醒+超时唤醒。

(2)为减少锁的粒度,减少刷新磁盘的时候日志接口阻塞,采用双队列方式(前台队列+后台刷新磁盘队列,后台队列刷新数据到磁盘)。

(3)内存分配通过move语义避免深拷贝。
(4)log4cpp的日志框架值得参考,但是它的性能不佳,要自己做完善、扩展。

后言

本专栏知识点是通过<零声教育>的系统学习,进行梳理总结写下文章,对c/c++linux系统提升感兴趣的读者,可以点击链接,详细查看详细的服务:C/C++服务器课程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_22402.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何做架构规划

文章目录架构师的职责WhyWhatHow架构活动生命周期环境搭建目标确认可行性探索架构规划统一语义需求确认任务边界划分确认规划完整性项目启动阶段性价值交付复盘经历过的典型案例参考架构师的职责 Why 互联网架构活动的挑战较多&#xff0c;如&#xff1a; 反射式的研发行为。…

Scratch软件编程等级考试四级——20200913

Scratch软件编程等级考试四级——20200913理论单选题判断题实操奇偶之和创意画图数字之和用逗号分隔列表数字反转理论 单选题 1、执行下面程序&#xff0c;输入4和7后&#xff0c;角色说出的内容是&#xff1f;&#xff08;&#xff09; A、4&#xff0c;7 B、7,7 C、7,4 D、…

为什么会发生云中断?如何防范?

IT 越依赖云服务&#xff0c;用户就越有可能因云中断而遭受停机和收入损失。由于云中断事件的发生&#xff0c;超过 60% 的使用公共云的组织在 2022 年报告了损失&#xff0c;因此云中断并不是公司不太可能面临的异常事件。 但是中断是否足以成为永远离开云的理由?还是应该坚持…

《安富莱嵌入式周报》第286期:8bit浮点数规范,VxWorks火星探测器故障原因修复,Matter V1.0智能家居规范,Wireshark 4.0发布

往期周报汇总地址&#xff1a;嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 目录 视频版&#xff1a; 1、SIA全球半导体行业协会统计显示全球芯片市场增长放缓&#xff0c;中国市场下跌10% …

程序员如何高效准备简历和面试03:诊断:简历为什么被忽视?

你好&#xff0c;欢迎学习课时3&#xff0c;我是你的职场导师吴文娟。 这节课主要为后面教你写简历做个铺垫&#xff0c;学习内容只有2个字&#xff1a;挑错。一个大家比较喜欢的事。我们来敲黑板看一些反面典型&#xff0c;案例都是我截取之前诊断过的简历&#xff0c;讲一讲为…

Mac电脑图片后期处理Lightroom Classic 2022(lrc2022)

Lightroom Classic 2022具有非常强大的图像处理功能&#xff0c;甚至对照片的一些修饰也可以完成&#xff0c;例如去除不要的物体、校正照片和增强照片颜色等。Lightroom Classic 2022 Mac版为用户提供了各种满足优秀摄影效果所需的编辑工具。让您能够轻松提亮颜色、使灰暗的摄…

C++ Builder XE TChart动态添加N个线条TLineSeries变化

// LARGE_INTEGER litmp; LONGLONG QPart1,QPart2; double dfMinus, dfFreq, dfTim; QueryPerformanceFrequency(&litmp); dfFreq (double)litmp.QuadPart;// 获得计数器的时钟频率 QueryPerformanceCounter(&litmp)…

STM32:外部中断控制旋转编码器并计次

1.主函数(main.c)代码部分&#xff1a; #include "stm32f10x.h" // Device header #include "Delay.h" #include "OLED.h" #include "Encoder.h" int16_t Num; int main(void) { OLED_Init(); OLED_Sh…

第十四届蓝桥杯备赛模板题——蓝桥部队 (带权并查集)

目录1.蓝桥部队1. 问题描述2.输入格式3.输入样例4.样例答案5.原题连接2.解题思路3.Ac_code1.蓝桥部队 1. 问题描述 小明是蓝桥部队的长官&#xff0c;他的班上有 NNN 名军人和 111 名军师。 这天&#xff0c;NNN 名军人在操场上站成一排&#xff0c;起初编号为 iii 的军人站…

JPA EntityManager 获取关联对象

今天尝试了几种方式&#xff0c;来获取关联对象。 关联对象&#xff0c;使用的 OneToMany(fetchFetchType.EAGER) 下面给一下总结&#xff1a; 一 Example 毫无疑问&#xff0c;很有信心&#xff0c;Example可以关联到对象。 事实也是这样。 但是Example好像只有and关系…

公众号网课题库系统-轻易获取查题接口

公众号网课题库系统-轻易获取查题接口 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 题库&#xff1a;题库后台&#xff08;点击…

Mindquantum实现变分量子奇异值分解

论文题目&#xff1a;Variational Quantum Singular Value Deposition(VQSVD) 项目介绍 复现过程 案例1&#xff1a;分解随机生成的8*8复数矩阵 先引入需要使用的包&#xff1a; import os os.environ[OMP_NUM_THREADS] 1​ import mindspore as ms from mindquantum impo…

Transformer解读之:Transformer 中的 Attention 机制

encoder 的 attention 场景&#xff1a;现在要训练的内容是 I love my dog -> 我喜欢我的狗那么在 encoder 端的输入是&#xff1a; I love my dog&#xff1b;假设经过 embedding 和位置编码后&#xff0c;I love my dog 这句话肯定已经变成了一个向量&#xff0c;但是在这…

一文彻底搞清Linux中块设备驱动的深层次原理和编写方法

【摘要】本文主要讲述了在Linux环境下的块设备驱动的常见数据结构和内核接口&#xff0c;并以一个实际例子讲述了块设备驱动的编写方法。 1.前提知识 一个块驱动提供对块存储设备&#xff08;比如 SD 卡、EMMC、NAND Flash、Nor Flash、SPI Flash、机械硬盘、固态硬盘等&…

Bed Bath Beyond EDI 856提前发货通知

自从1971年创业以来&#xff0c;Bed Bath&Beyond&#xff08;以下简称为BBB&#xff09;一直在为用户提供货真价实的卫浴用品&#xff0c;床上用品等家用商品。Bed Bath&Beyond 致力于成为一个勇于承担责任的公司团体&#xff0c;在市场建立起良好的信誉&#xff0c;提…

JavaSE 案例练习——精算师 double精度丢失解决思路

案例介绍 具体的内容是这样的&#xff1a; 编写一个程序&#xff0c;提示输入一个代表总钱数的双精度值&#xff0c;然后确定每种纸币和硬币需要的最少数量以达到输入的总钱数。 假设人民币种类如下&#xff1a;佰圆纸钞&#xff0c;伍拾圆纸钞&#xff0c;贰拾圆纸钞&#…

Asible最佳实践-进阶版-RHCA447 定义分组与变量

Asible最佳实践-进阶版-RHCA447 -------定义角组变量/主机变量/变量文件6.1 所有受管节点设置sudo免密[root@libin libin]# vim /etc/sudoers.d/devops libin ALL=(ALL) NOPASSWD:ALL [root@libin sudoers.d]# scp devops 192.168.124.134:`pwd` 6.2 自定义ansible目录[root@l…

学习使用jquery控制select下拉选项的字体样式

学习使用jquery控制select下拉选项的字体样式实现代码实现代码 <script src"../jquery-2.1.4.min.js"></script><style>div#container {padding: 30px;font-family: verdana, lucida;}a {color: #777;display: block;background-color: #ccc;widt…

向开发者开放免费注册!“远眺捷码”提供一站式软件快速开发平台

近日&#xff0c;远眺科技旗下具有自主知识产权的国产一站式软件快速开发平台——“远眺捷码”宣布正式开放免费注册&#xff0c;有各类软件应用开发等需求开发者、软件开发企业&#xff0c;可访问捷码官网https://www.gemcoder.com/ 操作步骤&#xff1a; Step1、打开捷码PC端…

客户成功 | 数据解码技能提升,Smartbi助力长沙烟草找到“新路子”

让数据会“说话”能“干活”&#xff0c;为客户挖掘出更深层的数据价值&#xff0c;是Smartbi一直以来助力企业数字化转型的目标和方向。大数据时代&#xff0c;每个科学的决策离不开数据的支撑&#xff0c;数字化精益管理是各行业提升自身运营管理的必然选择。数字化转型的成色…