【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 同步日志

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

相关推荐
zy happy9 小时前
VMware虚拟机添加新的硬盘
java·linux·jvm·docker
LCG元9 小时前
嵌入式开发踩坑排查实战:从 STM32 外设初始化到 Linux SPI 驱动的 15 类硬件疑难故障深度复盘
linux·stm32·单片机
代码AC不AC9 小时前
【Linux】Socket编程TCP
linux·tcp·socket编程
扛枪的书生9 小时前
SeaweedFS 学习总结
linux
minglie19 小时前
香橙派zero3配个iic屏显示ip
linux·学习
我不是懒洋洋9 小时前
从零实现一个分布式日志平台:ELK的核心设计
c++
edwarddamon9 小时前
SSH 指定端口建立隧道:打通内网访问完整教程
linux·命令行
神仙别闹10 小时前
基于QT(C++)实现Windows 自启动项查看和分析
c++·windows·qt
ShirleyWang01210 小时前
Day02 K3s NGF(Nginx Gateway Fabric)单 Worker 环境网关更新与故障处置 SOP
linux·服务器·python·k8s·k3s
酷可达拉斯10 小时前
Linux操作系统-shell编程(1)
linux·运维·服务器