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

前言

在高并发、高性能后端服务中,同步日志会严重拖慢业务线程

为了解决这个问题,工业级主流方案都是:业务线程 → 写内存 → 后台线程异步落盘 这就是 异步日志(Async Logging)


一、异步日志核心设计思想

1. 为什么要用异步日志?

  • 同步日志:业务线程直接写文件,阻塞 IO,高并发下性能暴跌

  • 异步日志 :业务线程只写内存,后台线程异步刷盘,业务无阻塞

2. 核心架构(你代码的设计)

  1. CountDownLatch:线程同步门栓

  2. AsyncLogging:异步日志核心(双缓冲、生产者 - 消费者)

  3. LogFile / AppendFile:文件落地

  4. Logger / LogMessage:日志格式化与宏封装

  5. 多线程安全:mutex + condition_variable


二、核心组件讲解

CountDownLatch 线程同步门栓

作用:让主线程等待后台线程启动完成再继续执行

CountDownLatch.hpp

cpp 复制代码
#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

#endif

CountDownLatch.cpp

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_lock<std::mutex> locker(mutex_);
        while (count_ > 0)
        {
            cond_.wait(locker);
        }
    }
    void CountDownLatch::countDown()
    {
        std::unique_lock<std::mutex> locker(mutex_);
        count_-=1;
        if(count_ == 0)
        {
            cond_.notify_all();
        }
    }
    int CountDownLatch::getCount() const
    {
        std::unique_lock<std::mutex> locker(mutex_);
        return count_;
    }

} // namespace tulun

三、异步日志核心:AsyncLogging

这是整个日志系统的心脏

核心设计:双缓冲机制(Double Buffer)

  • currentBuffer_:当前写入缓冲区

  • buffers_:装满的缓冲区队列

  • 后台线程:批量将缓冲区写入文件

核心流程

  1. 业务线程调用 append() → 写入 currentBuffer

  2. 缓冲区满 → 放入 buffers 队列

  3. 条件变量通知后台线程

  4. 后台线程批量写入文件


四、AsyncLogging 完整代码解析

AsyncLogging.hpp

cpp 复制代码
#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::atomic<bool> running_;  // 日志是否在运行(控制线程退出)
        const std::string basename_; // 日志文件名前缀(如:syrou)
        const size_t rollSize_;      // 日志多大滚动(默认2M)
        tulun::CountDownLatch latch_;
        std::unique_ptr<std::thread> pthread_; // 后端线程(写文件的线程)

        std::mutex mutex_;             // 锁:保护队列和缓冲区
        std::condition_variable cond_; // 条件变量:通知后端"有数据啦快来写"

        std::string currentBuffer_;       // 当前正在写的缓冲(4K)
        //std::deque<std::string> buffers_; // 满了的缓冲队列(多个4K)
        std::vector<std::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();
    };
}
#endif

AsyncLogging.cpp 核心方法解析

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::atomic<bool> running_;
    // const std::string basename_;
    // const size_t rollSize_; // 10M;
    // std::unique_ptr<std::thread> pthread_;

    // std::mutex mutex_;
    // std::condition_variable cond_;

    // std::string currentBuffer_; // 4K
    // std::deque<std::string> buffers_;
    // std::vector<std::string> buffers_;
    // tulun::LogFile output_;

    void AsynLogging::workthreadfunc()
    {
        //std::deque<std::string> buffersToWrite;
        std::vector<std::string> buffersToWrite; // 准备写入文件的缓冲
        latch_.countDown();
        while (running_) // 一直跑
        {
            {
                std::unique_lock<std::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_lock<std::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::deque<std::string> bufferToWriter;
        std::vector<std::string> bufferToWriter;
        std::unique_lock<std::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)

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. 双缓冲机制(零拷贝交换)

cpp 复制代码
buffersToWrite.swap(buffers_);

不拷贝数据,只交换指针,速度极快。

2. 前端只写内存,无 IO 阻塞

业务线程完全不参与文件 IO,不影响业务性能

3. 无锁后端写入

后台线程写文件时不加锁,极大提升吞吐量。

4. 条件变量 wait_for

即使没有日志,也会1 秒自动唤醒,防止日志滞留内存。

5. CountDownLatch 保证线程安全启动

确保后台线程完全启动后,主线程才继续执行。

6. 多线程安全

所有共享变量都由 mutex 保护。


七、异步日志 VS 同步日志

模式 性能 阻塞业务 适用场景
同步日志 小工具、测试程序
异步日志 极高 高并发服务器、网关、核心服务

相关推荐
梓䈑2 小时前
gtest实战入门:从安装到TEST宏的单元测试指南
c++·单元测试
2301_旺仔2 小时前
【prometheus】监控linux/windows
linux·windows·prometheus
郝学胜-神的一滴2 小时前
墨韵技术|CMake:现代项目构建的「行云流水」之道
c++·程序人生·软件工程·软件构建·cmake
雪域迷影2 小时前
Hazel游戏引擎结构分析
c++·游戏引擎·hazel
“愿你如星辰如月”2 小时前
从零构建高性能 Reactor 服务器:
linux·服务器·c++·websocket·tcp/ip
努力努力再努力wz2 小时前
【C++高阶系列】外存查找的极致艺术:数据库偏爱的B+树底层架构剖析与C++完整实现!(附B+树实现的源码)
linux·运维·服务器·数据结构·数据库·c++·b树
Yungoal2 小时前
c++迭代器
开发语言·c++
踏着七彩祥云的小丑2 小时前
Linux命令——开机自启配置
linux·运维·网络
clear sky .2 小时前
[linux]buildroot什么用途
linux·运维·数据库