webserver--多缓冲区实现日志系统

2024-03-29 21:44

本文主要是介绍webserver--多缓冲区实现日志系统,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

😎 作者介绍:我是程序员行者孙,一个热爱分享技术的制能工人。计算机本硕,人工制能研究生。公众号:AI Sun,视频号:AI-行者Sun
🎈 本文专栏:本文收录于《项目推荐》系列专栏,相信一份耕耘一份收获,我会继续分享更多优质项目,届时可以拳打字节,脚踢腾讯
🤓 欢迎大家关注其他专栏,我将分享Web前后端开发、人工智能、机器学习、深度学习从0到1系列文章。
🖥 随时欢迎您跟我沟通,一起交流,一起成长、进步!

⽇志系统

服务器的⽇志系统是⼀个多⽣产者,单消费者的任务场景:多⽣产者负责把⽇志写⼊缓冲区,单消费者负责把缓冲 区中数据写⼊⽂件。如果只⽤⼀个缓冲区,不光要同步各个⽣产者, 还要同步⽣产者和消费者。⽽且最重要的是需要保证⽣产者与消费者的并发,也就是前端不断写⽇志到缓冲区的同时,后端可以把缓冲区写⼊⽂件。

 所以我们这里采用双缓冲区,这样就可以高效的避免因为读写原因导致的阻塞。

 

双缓冲技术的基本思路:准备两块 buffer A B, 前端往 A 写数据,后端从 B ⾥⾯往硬盘写数据,当 A 写满 后,交换 A B ,如此反复。使⽤两个 buffer 的好处是在新建⽇志消息的时候不必等待磁盘⽂件操作,也避 免每条新⽇志消息都触发后端⽇志线程。换句话说,前端不是将⼀条条⽇志消息分别送给后端,⽽是将多条⽇ 志消息拼接成⼀个⼤的 buffer 传送给后端,相当于批处理,减少了线程唤醒的开销。
先看看大纲理解一下每个函数的作用。
各个函数的作用:
  • Log::Log() - 构造函数

    初始化Log类的实例。设置日志级别、异步写入标志、写入线程、队列、今日日期等成员变量的初始状态。
  • Log::~Log() - 析构函数

    清理Log类的实例。如果存在异步写入线程,则等待线程结束,关闭队列,加入主线程,并关闭文件指针。如果文件指针存在,则同步写入并关闭日志文件。
  • Log::GetLevel() - 获取当前日志级别

    通过互斥锁保护的成员变量level_,返回当前设置的日志级别。
  • Log::SetLevel(int level) - 设置日志级别

    通过互斥锁保护的成员变量level_,设置新的日志级别。
  • Log::init(int level, const char* path, const char* suffix, int maxQueueSize) - 初始化日志系统

    设置日志级别、日志文件路径、文件后缀和最大队列大小。根据队列大小决定是否开启异步写入模式,并初始化相关的成员变量。创建日志文件并打开用于写入。
  • Log::write(int level, const char *format, ...) - 写入日志

    根据指定的日志级别和格式化字符串写入日志。首先获取当前时间,然后根据日期和行数决定是否需要创建新文件。格式化日志内容并写入缓冲区,如果开启异步写入,则将日志内容推送到队列中,否则直接写入文件。
  • Log::AppendLogLevelTitle_(int level) - 添加日志级别标题

    根据传入的日志级别,向缓冲区追加相应的日志级别标题。
  • Log::flush() - 刷新日志缓冲区

    如果开启异步写入,则调用队列的flush()方法,否则调用fflush()刷新文件指针。
  • Log::AsyncWrite_() - 异步写入日志

    从队列中取出日志内容并写入文件。这是异步线程执行的函数,用于将日志缓冲区的内容异步写入磁盘。
  • Log::Instance() - 获取日志实例

    静态函数,返回Log类的一个实例。这里使用了单例模式,确保整个程序中只存在一个日志实例。
  • Log::FlushLogThread() - 异步日志写入线程函数

    这是异步线程的入口点,它会调用AsyncWrite_()函数来处理异步日志写入任务
具体详细注释代码(太不容易了,点个赞呗,可以关注一下公众号和视频号)
#include "log.h"using namespace std;// Log类的构造函数
Log::Log() {lineCount_ = 0; // 初始化日志行数为0isAsync_ = false; // 初始化异步写入标志为falsewriteThread_ = nullptr; // 初始化写入线程指针为nullptrdeque_ = nullptr; // 初始化日志队列指针为nullptrtoDay_ = 0; // 初始化今日日期标志为0fp_ = nullptr; // 初始化文件指针为nullptr
}// Log类的析构函数
Log::~Log() {// 如果存在写入线程并且可以加入,则等待线程结束if(writeThread_ && writeThread_->joinable()) {// 循环直到队列清空while(!deque_->empty()) {deque_->flush();};// 关闭队列deque_->Close();// 等待写入线程结束writeThread_->join();}// 如果文件指针存在if(fp_) {// 锁定互斥锁lock_guard<mutex> locker(mtx_);// 刷新缓冲区并关闭文件flush();fclose(fp_);}
}// 获取当前日志级别
int Log::GetLevel() {// 锁定互斥锁lock_guard<mutex> locker(mtx_);// 返回日志级别return level_;
}// 设置日志级别
void Log::SetLevel(int level) {// 锁定互斥锁lock_guard<mutex> locker(mtx_);// 设置新的日志级别level_ = level;
}// 初始化日志系统
void Log::init(int level, const char* path, const char* suffix,int maxQueueSize) {// 设置日志级别isOpen_ = true;level_ = level;// 如果队列大小大于0,则开启异步写入if(maxQueueSize > 0) {isAsync_ = true;// 如果队列不存在,则创建新队列if(!deque_) {unique_ptr<BlockDeque<string>> newDeque(new BlockDeque<string>);deque_ = move(newDeque);// 创建新的写入线程std::unique_ptr<std::thread> NewThread(new thread(FlushLogThread));writeThread_ = move(NewThread);}} else {// 否则关闭异步写入isAsync_ = false;}// 重置日志行数lineCount_ = 0;// 获取当前时间time_t timer = time(nullptr);struct tm *sysTime = localtime(&timer);struct tm t = *sysTime;// 设置日志文件路径和后缀path_ = path;suffix_ = suffix;// 构造日志文件名char fileName[LOG_NAME_LEN] = {0};snprintf(fileName, LOG_NAME_LEN - 1, "%s/%04d_%02d_%02d%s", path_, t.tm_year + 1900, t.tm_mon + 1, t.tm_mday, suffix_);// 设置今日日期toDay_ = t.tm_mday;// 锁定互斥锁lock_guard<mutex> locker(mtx_);// 清空缓冲区buff_.RetrieveAll();// 如果文件指针存在,则关闭旧文件if(fp_) { flush();fclose(fp_); }// 打开新文件fp_ = fopen(fileName, "a");// 如果文件打开失败,则创建目录后再次尝试if(fp_ == nullptr) {mkdir(path_, 0777);fp_ = fopen(fileName, "a");}// 断言文件指针不为空assert(fp_ != nullptr);
}// 写入日志
void Log::write(int level, const char *format, ...) {// 获取当前时间struct timeval now = {0, 0};gettimeofday(&now, nullptr);time_t tSec = now.tv_sec;struct tm *sysTime = localtime(&tSec);struct tm t = *sysTime;va_list vaList;// 如果日期变化或达到最大日志行数,则创建新文件if (toDay_ != t.tm_mday || (lineCount_ && (lineCount_  %  MAX_LINES == 0))) {// 锁定互斥锁unique_lock<mutex> locker(mtx_);locker.unlock();// 构造新文件名char newFile[LOG_NAME_LEN];char tail[36] = {0};snprintf(tail, 36, "%04d_%02d_%02d", t.tm_year + 1900, t.tm_mon + 1, t.tm_mday);// 如果日期变化if (toDay_ != t.tm_mday) {snprintf(newFile, LOG_NAME_LEN - 72, "%s/%s%s", path_, tail, suffix_);// 更新今日日期toDay_ = t.tm_mday;// 重置日志行数lineCount_ = 0;} else {// 否则更新文件名,包含行数信息snprintf(newFile, LOG_NAME_LEN - 72, "%s/%s-%d%s", path_, tail, (lineCount_  / MAX_LINES), suffix_);}// 重新加锁locker.lock();// 刷新缓冲区flush();// 关闭旧文件fclose(fp_);// 打开新文件fp_ = fopen(newFile, "a");// 断言文件指针不为空assert(fp_ != nullptr);}// 锁定互斥锁unique_lock<mutex> locker(mtx_);// 增加日志行数lineCount_++;// 格式化日志时间int n = snprintf(buff_.BeginWrite(), 128, "%d-%02d-%02d %02d:%02d:%02d.%06ld ",t.tm_year + 1900, t.tm_mon + 1, t.tm_mday,t.tm_hour, t.tm_min, t.tm_sec, now.tv_usec);// 写入时间到缓冲区buff_.HasWritten(n);// 添加日志级别标题AppendLogLevelTitle_(level);// 格式化日志内容va_start(vaList, format);int m = vsnprintf(buff_.BeginWrite(), buff_.WritableBytes(), format, vaList);va_end(vaList);// 写入日志内容到缓冲区buff_.HasWritten(m);// 添加换行符buff_.Append("\n\0", 2);// 如果开启异步写入且队列未满,则将日志内容推送到队列if(isAsync_ && deque_ && !deque_->full()) {deque_->push_back(buff_.RetrieveAllToStr());} else {// 否则直接写入文件fputs(buff_.Peek(), fp_);}// 清空缓冲区buff_.RetrieveAll();
}// 添加日志级别标题
void Log::AppendLogLevelTitle_(int level) {// 根据日志级别添加相应的标题switch(level) {case 0:buff_.Append("[debug]: ", 9);break;case 1:buff_.Append("[info] : ", 9);break;case 2:buff_.Append("[warn] : ", 9);break;case 3:buff_.Append("[error]: ", 9);break;default:buff_.Append("[info] : ", 9);break;}
}// 刷新缓冲区,将内容写入文件
void Log::flush() {// 如果开启异步写入,则调用队列的flush方法if(isAsync_) { deque_->flush(); }// 刷新文件指针fflush(fp_);
}// 异步写入日志
void Log::AsyncWrite_() {// 从队列中取出日志内容并写入文件string str = "";while(deque_->pop(str)) {// 锁定互斥锁lock_guard<mutex> locker(mtx_);// 将日志内容写入文件fputs(str.c_str(), fp_);}
}// 获取日志实例
Log* Log::Instance() {// 创建并返回Log类的单例实例static Log inst;return &inst;
}// 异步日志写入线程函数
void Log::FlushLogThread() {// 调用实例的异步写入函数Log::Instance()->AsyncWrite_();
}

这篇关于webserver--多缓冲区实现日志系统的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/859723

相关文章

Nginx 配置跨域的实现及常见问题解决

《Nginx配置跨域的实现及常见问题解决》本文主要介绍了Nginx配置跨域的实现及常见问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来... 目录1. 跨域1.1 同源策略1.2 跨域资源共享(CORS)2. Nginx 配置跨域的场景2.1

Python中提取文件名扩展名的多种方法实现

《Python中提取文件名扩展名的多种方法实现》在Python编程中,经常会遇到需要从文件名中提取扩展名的场景,Python提供了多种方法来实现这一功能,不同方法适用于不同的场景和需求,包括os.pa... 目录技术背景实现步骤方法一:使用os.path.splitext方法二:使用pathlib模块方法三

CSS实现元素撑满剩余空间的五种方法

《CSS实现元素撑满剩余空间的五种方法》在日常开发中,我们经常需要让某个元素占据容器的剩余空间,本文将介绍5种不同的方法来实现这个需求,并分析各种方法的优缺点,感兴趣的朋友一起看看吧... css实现元素撑满剩余空间的5种方法 在日常开发中,我们经常需要让某个元素占据容器的剩余空间。这是一个常见的布局需求

HTML5 getUserMedia API网页录音实现指南示例小结

《HTML5getUserMediaAPI网页录音实现指南示例小结》本教程将指导你如何利用这一API,结合WebAudioAPI,实现网页录音功能,从获取音频流到处理和保存录音,整个过程将逐步... 目录1. html5 getUserMedia API简介1.1 API概念与历史1.2 功能与优势1.3

Java实现删除文件中的指定内容

《Java实现删除文件中的指定内容》在日常开发中,经常需要对文本文件进行批量处理,其中,删除文件中指定内容是最常见的需求之一,下面我们就来看看如何使用java实现删除文件中的指定内容吧... 目录1. 项目背景详细介绍2. 项目需求详细介绍2.1 功能需求2.2 非功能需求3. 相关技术详细介绍3.1 Ja

使用Python和OpenCV库实现实时颜色识别系统

《使用Python和OpenCV库实现实时颜色识别系统》:本文主要介绍使用Python和OpenCV库实现的实时颜色识别系统,这个系统能够通过摄像头捕捉视频流,并在视频中指定区域内识别主要颜色(红... 目录一、引言二、系统概述三、代码解析1. 导入库2. 颜色识别函数3. 主程序循环四、HSV色彩空间详解

PostgreSQL中MVCC 机制的实现

《PostgreSQL中MVCC机制的实现》本文主要介绍了PostgreSQL中MVCC机制的实现,通过多版本数据存储、快照隔离和事务ID管理实现高并发读写,具有一定的参考价值,感兴趣的可以了解一下... 目录一 MVCC 基本原理python1.1 MVCC 核心概念1.2 与传统锁机制对比二 Postg

SpringBoot整合Flowable实现工作流的详细流程

《SpringBoot整合Flowable实现工作流的详细流程》Flowable是一个使用Java编写的轻量级业务流程引擎,Flowable流程引擎可用于部署BPMN2.0流程定义,创建这些流程定义的... 目录1、流程引擎介绍2、创建项目3、画流程图4、开发接口4.1 Java 类梳理4.2 查看流程图4

C++中零拷贝的多种实现方式

《C++中零拷贝的多种实现方式》本文主要介绍了C++中零拷贝的实现示例,旨在在减少数据在内存中的不必要复制,从而提高程序性能、降低内存使用并减少CPU消耗,零拷贝技术通过多种方式实现,下面就来了解一下... 目录一、C++中零拷贝技术的核心概念二、std::string_view 简介三、std::stri

C++高效内存池实现减少动态分配开销的解决方案

《C++高效内存池实现减少动态分配开销的解决方案》C++动态内存分配存在系统调用开销、碎片化和锁竞争等性能问题,内存池通过预分配、分块管理和缓存复用解决这些问题,下面就来了解一下... 目录一、C++内存分配的性能挑战二、内存池技术的核心原理三、主流内存池实现:TCMalloc与Jemalloc1. TCM