【Linux C++ 后端实战】异步日志系统 AsyncLogging 完整设计与源码解析

张开发
2026/4/11 22:19:06 15 分钟阅读

分享文章

【Linux C++ 后端实战】异步日志系统 AsyncLogging 完整设计与源码解析
前言在高并发、高性能后端服务中同步日志会严重拖慢业务线程。为了解决这个问题工业级主流方案都是业务线程 → 写内存 → 后台线程异步落盘这就是异步日志Async Logging。一、异步日志核心设计思想1. 为什么要用异步日志同步日志业务线程直接写文件阻塞 IO高并发下性能暴跌异步日志业务线程只写内存后台线程异步刷盘业务无阻塞2. 核心架构你代码的设计CountDownLatch线程同步门栓AsyncLogging异步日志核心双缓冲、生产者 - 消费者LogFile / AppendFile文件落地Logger / LogMessage日志格式化与宏封装多线程安全mutex condition_variable二、核心组件讲解CountDownLatch 线程同步门栓作用让主线程等待后台线程启动完成再继续执行。CountDownLatch.hpp#include mutex #include condition_variable using namespace std; #ifndef COUNT_DOWN_LATCH_HPP #define COUNT_DOWN_LATCH_HPP namespace tulun { class CountDownLatch { private: int count_; mutable std::mutex mutex_; std::condition_variable cond_; public: CountDownLatch(int count); ~CountDownLatch() default; void wait(); void countDown(); int getCount() const; }; } // namespace tulun #endifCountDownLatch.cpp#include CountDownLatch.hpp namespace tulun { // class CountDownLatch // int count_; // std::mutex mutex_; // std::condition_variable cond_; CountDownLatch::CountDownLatch(int count) : count_(count) { } void CountDownLatch::wait() { std::unique_lockstd::mutex locker(mutex_); while (count_ 0) { cond_.wait(locker); } } void CountDownLatch::countDown() { std::unique_lockstd::mutex locker(mutex_); count_-1; if(count_ 0) { cond_.notify_all(); } } int CountDownLatch::getCount() const { std::unique_lockstd::mutex locker(mutex_); return count_; } } // namespace tulun三、异步日志核心AsyncLogging这是整个日志系统的心脏。核心设计双缓冲机制Double BuffercurrentBuffer_当前写入缓冲区buffers_装满的缓冲区队列后台线程批量将缓冲区写入文件核心流程业务线程调用append()→ 写入 currentBuffer缓冲区满 → 放入 buffers 队列条件变量通知后台线程后台线程批量写入文件四、AsyncLogging 完整代码解析AsyncLogging.hpp#include AppendFile.hpp #include LogFile.hpp #include CountDownLatch.hpp #include thread #include atomic #include string #include memory #include mutex #include condition_variable #include deque #include vector using namespace std; #ifndef ASYN_LOGGING_HPP #define ASYN_LOGGING_HPP namespace tulun { class AsynLogging { private: void workthreadfunc(); // 【后端线程函数】真正写文件的人 private: const int flushInterval_; // 多久自动刷盘3秒 std::atomicbool running_; // 日志是否在运行控制线程退出 const std::string basename_; // 日志文件名前缀如syrou const size_t rollSize_; // 日志多大滚动默认2M tulun::CountDownLatch latch_; std::unique_ptrstd::thread pthread_; // 后端线程写文件的线程 std::mutex mutex_; // 锁保护队列和缓冲区 std::condition_variable cond_; // 条件变量通知后端“有数据啦快来写” std::string currentBuffer_; // 当前正在写的缓冲4K //std::dequestd::string buffers_; // 满了的缓冲队列多个4K std::vectorstd::string buffers_; tulun::LogFile output_; // 真正写文件的对象你之前写的LogFile public: AsynLogging(const std::string basename, const size_t rollsize 1024 * 1024 * 2, int flushInterval 3); ~AsynLogging(); AsynLogging(const AsynLogging ) delete; AsynLogging operator(const AsynLogging ) delete; void append(const std::string msg); void append(const char *msg, const size_t len);// 前端打日志调用 void start();// 启动后端线程 void stop(); void flush(); }; } #endifAsyncLogging.cpp 核心方法解析#include AsynLogging.hpp namespace tulun { const size_t BufMaxLen 1024 * 8; // 4k const size_t BufQueueSize 32; // class AsynLogging // const int flushInterval_; // 3s; // std::atomicbool running_; // const std::string basename_; // const size_t rollSize_; // 10M; // std::unique_ptrstd::thread pthread_; // std::mutex mutex_; // std::condition_variable cond_; // std::string currentBuffer_; // 4K // std::dequestd::string buffers_; // std::vectorstd::string buffers_; // tulun::LogFile output_; void AsynLogging::workthreadfunc() { //std::dequestd::string buffersToWrite; std::vectorstd::string buffersToWrite; // 准备写入文件的缓冲 latch_.countDown(); while (running_) // 一直跑 { { std::unique_lockstd::mutex locker(mutex_); // 加锁 // 没有数据 还在运行 → 等待1秒 while (buffers_.empty() running_) { cond_.wait_for(locker, std::chrono::seconds(1)); } // 把当前缓冲也加入队列 buffers_.push_back(std::move(currentBuffer_)); currentBuffer_.reserve(BufMaxLen); // 交换队列【最关键一步】 buffersToWrite.swap(buffers_); buffers_.reserve(BufQueueSize); } // 解锁后才写文件不阻塞前端 if(buffersToWrite.size() 50) { fprintf(stderr,Dropped log message at larger buffers \n); buffersToWrite.erase(buffersToWrite.begin()2,buffersToWrite.end()); } // 把所有缓冲写入文件 for (const auto buf : buffersToWrite) { output_.append(buf); } buffersToWrite.clear(); } output_.flush(); } // 参数列表里只放【需要从外面传进来的值】 AsynLogging::AsynLogging(const std::string basename, const size_t rollsize, int flushInterval) : basename_(basename), flushInterval_(flushInterval), rollSize_(rollsize), running_(false), pthread_(nullptr), mutex_{}, cond_{}, output_(basename, rollsize, flushInterval), latch_{1} { currentBuffer_.reserve(BufMaxLen); // } AsynLogging::~AsynLogging() { if (running_) { stop(); } } void AsynLogging::append(const std::string msg) { append(msg.c_str(), msg.size()); } void AsynLogging::append(const char *msg, const size_t len) { std::unique_lockstd::mutex locker(mutex_); // 加锁 // 如果当前缓冲满了 || 剩余空间不够放下这条日志 if (currentBuffer_.size() BufMaxLen || currentBuffer_.capacity() - currentBuffer_.size() len) { // 把当前缓冲 移动 到队列 buffers_.push_back(std::move(currentBuffer_)); currentBuffer_.reserve(BufMaxLen); // 开新缓冲 } currentBuffer_.append(msg, len); // 把日志写进缓冲 cond_.notify_all(); // 唤醒后端线程来活啦 } void AsynLogging::start() { running_ true; pthread_.reset(new std::thread(AsynLogging::workthreadfunc, this)); latch_.wait(); } void AsynLogging::stop() { running_ false; cond_.notify_all(); pthread_-join(); } void AsynLogging::flush() { //std::dequestd::string bufferToWriter; std::vectorstd::string bufferToWriter; std::unique_lockstd::mutex locker(mutex_); buffers_.push_back(std::move(currentBuffer_)); currentBuffer_.reserve(BufMaxLen); bufferToWriter.swap(buffers_); buffers_.reserve(BufQueueSize); for (const auto buf : bufferToWriter) { output_.append(buf); } output_.flush(); bufferToWriter.clear(); } }五、使用示例Test04_11_Asyn.cpp#include iostream #include string using namespace std; #include LogCommon.hpp #include Logger.hpp #include AsynLogging.hpp //tulun::LogFile asynfile(hm); const int n 10000; void funa() { for (int i 0; i n; i) { //std::this_thread::sleep_for(std::chrono::milliseconds(10)); LOG_DEBUG syrou funa i; } } void funb() { for (int i 0; i n; i) { //std::this_thread::sleep_for(std::chrono::milliseconds(10)); LOG_TRACE syr funb i; } } void func() { LOG_INFO func; } // 创建一个全局异步日志对象 tulun::AsynLogging asynfile(syrou); void asynFile(const std::string msg) { asynfile.append(msg); } void asynFlush() { asynfile.flush(); } int main() { // 1. 启动异步日志的【后台写文件线程】 asynfile.start(); // 2. 设置日志级别 tulun::Logger::setLogLevel(tulun::LOG_LEVEL::TRACE); // 3. 把日志输出 → 绑定到异步日志 tulun::Logger::setOutput(asynFile); // 4. 把刷新 → 绑定到异步日志 tulun::Logger::setFlush(asynFlush); tulun::Timestamp start,end; start tulun::Timestamp::Now(); std::thread tha(funa); std::thread thb(funb); tha.join(); thb.join(); end tulun::Timestamp::Now(); cout tulun::diffMicro(end,start)endl; return 0; }六、高性能关键点总结重点1. 双缓冲机制零拷贝交换buffersToWrite.swap(buffers_);不拷贝数据只交换指针速度极快。2. 前端只写内存无 IO 阻塞业务线程完全不参与文件 IO不影响业务性能。3. 无锁后端写入后台线程写文件时不加锁极大提升吞吐量。4. 条件变量 wait_for即使没有日志也会1 秒自动唤醒防止日志滞留内存。5. CountDownLatch 保证线程安全启动确保后台线程完全启动后主线程才继续执行。6. 多线程安全所有共享变量都由mutex保护。七、异步日志 VS 同步日志模式性能阻塞业务适用场景同步日志低是小工具、测试程序异步日志极高否高并发服务器、网关、核心服务

更多文章