手撸高性能日志系统(一):百万日志,秒秒落盘(小试牛刀篇)

news2024/11/26 20:27:49

一、需求一丢,谁累成狗

最近由于某些需要,计划手撸一个高性能的日志系统。需求很简单:
1、 不允许丢一条日志信息(很重要很重要)
2、支持多线程,必须线程安全
3、性能要越优越好,尽量百万可秒级

二、手拿把掐?

掐指一算,也只是掐指,这会我老婆拎着我耳朵你敢信。诶,有了。
我们看好这几个关键词:1、高性能 2、不丢数据

高性能的保障是什么,我们先不要想具体的方案,来一场头脑风暴。
对,多线程、内存预分配,减少I/O次数,零拷贝…
保证数据有序,不丢数据该怎么搞? 按序处理,对对,可以一条条的写,哦哦哦,好像不行,生产日志同时进行I/O太慢了,特别是是当前实时性要求很高的业务中,日志只能异步处理;对对对,可以用队列…
(一万年以后~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~)在这里插入图片描述

灵光闪现,于是乎一个基于多线程的生产者与消费者的经典方案出现在脑中。
在这里插入图片描述
好像还不错哦,思路有理有据。队列本来就是先进先出的,在日志push进队列和在pop出队列时,加上锁,不就okk啦,拿捏~ 那么,按照这个思路,我们浅写一个日志框架,并且来测试下按照这个思路能达到的性能。

三、诶诶诶,我要从后面进入啦~

// tinylog.hpp (hpp是有故事的, 好像hit pp)

#ifndef TINYLOG_HPP
#define TINYLOG_HPP

#include <iostream>
#include <fstream>
#include <string>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <vector>
#include <atomic>
#include <sstream>
#include <ctime>
#include <iomanip>

#ifdef _WIN32 || _WIN64
#include <Windows.h>
#include <time.h>
#endif


class LogQueue {
public:
    LogQueue(){}
    LogQueue(size_t max_size) : max_size_(max_size) {}

    void push(const std::string& log) {
        std::unique_lock<std::mutex> lock(mutex_);
        cond_var_.wait(lock, [this] { return log_queue_.size() < max_size_; });
        log_queue_.push(log);
        cond_var_.notify_one();
    }

    std::string pop() {
        std::unique_lock<std::mutex> lock(mutex_);
        cond_var_.wait(lock, [this] { return !log_queue_.empty(); });
        std::string log = log_queue_.front();
        log_queue_.pop();
        cond_var_.notify_one();
        return log;
    }

    bool empty(){ return log_queue_.empty();}
    void setMax_size(const size_t &max_size)
    {
        max_size_ = max_size;
    }


private:
    size_t max_size_;
    std::queue<std::string> log_queue_;
    std::mutex mutex_;
    std::condition_variable cond_var_;
};


class TinyLog {
public:
    using LogCallback = std::function<void(std::ofstream&,const std::string&)>;

    TinyLog(size_t queue_size = 1024,size_t thread_count = 1)
        : queue_size_(queue_size),stop_(false), log_callback_(defaultLogCallback)
    {
        log_file.open("log.txt",std::ios::app);
        log_queue_.setMax_size(queue_size_);

        for (size_t i = 0; i < thread_count; ++i) {
            thread_pool_.emplace_back(&TinyLog::processLogs, this);
        }
    }

    ~TinyLog() { stop();}

    void log(const std::string& message) {
        log_queue_.push(message);
        cond_var_.notify_one();
    }

    void setLogCallback(LogCallback callback) {log_callback_ = callback;}
    bool isStopped(){return stop_.load();}
    void setStopped(){stop_.store(true);}

private:

    static std::string getcurrentAscTime(){
        auto now = std::chrono::system_clock::now();
        auto in_time_t = std::chrono::system_clock::to_time_t(now);
        auto microseconds = std::chrono::duration_cast<std::chrono::microseconds>(now.time_since_epoch()) % 1000000;
        auto milliseconds = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;

        std::ostringstream oss;
        oss << std::put_time(std::localtime(&in_time_t), "%Y-%m-%d %H:%M:%S")
            << " " << std::setw(3) << std::setfill('0') << milliseconds.count()
            << " " << std::setw(6) << std::setfill('0') << microseconds.count();
        return oss.str();
    }

    static void defaultLogCallback(std::ofstream& stream, const std::string& message) {

#ifdef TEST_DEMO
        static long times = 0;
        static long long epleased = 0;

        auto start = std::chrono::high_resolution_clock::now(); // Start time
#endif
        {
            stream << message << std::endl;
        }

#ifdef TEST_DEMO
        auto end = std::chrono::high_resolution_clock::now(); // End time
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();

        epleased += duration;

        if(++times >= 999900){

            std::cout << "[thread id] : " << std::this_thread::get_id() << " times :" << times
                      << "  total write time :" << (epleased/1000.00) << " milliseconds" << std::endl;
        }
#endif
    }

    void stop() {
        if(log_file.is_open()){
            log_file.close();
        }

        {
            std::unique_lock<std::mutex> lock(mutex_);
            stop_ = true;
        }

        cond_var_.notify_all();
        for (auto& thread : thread_pool_) {
            if (thread.joinable()) {
                thread.join();
            }
        }
    }


    void processLogs() {

        while (true) {

            std::unique_lock<std::mutex> lock(mutex_);
            cond_var_.wait(lock, [this] { return !log_queue_.empty() || stop_; });

            if (stop_ && log_queue_.empty()) {
                std::cout << "process thread was stopped ,and logs all has been dealed." << std::endl;
                return;
            }

            if(log_queue_.empty()){
                std::this_thread::sleep_for(std::chrono::seconds(1));
                std::cout << "wait for comming logs ..." << std::endl;
                continue;
            }

            std::string& message = log_queue_.pop();
            log_callback_(log_file,message);

            cond_var_.notify_one();
        }
    }


private:
    size_t queue_size_;
    LogQueue log_queue_;
    std::vector<std::thread> thread_pool_;
    std::mutex mutex_;
    std::condition_variable cond_var_;
    std::atomic<bool> stop_;
    LogCallback log_callback_;

    std::ofstream log_file;
};



#endif // TINYLOG_HPP


#endif // TINYLOG_HPP

解读一下,笔者实现了一个日志队列,日志队列中出入站都加锁,哦哦,保证有序,暂时没问题。但是咱们追求的是高性能啊,都说锁会降低性能的,不行,带上“贞操锁”只会影响我进出的速度啊。这里咱们先忍一下。

然后,实现在TinyLog 中实现日志 输出, 多线程日志处理接口,这里特别设置成了回调函数,因为,谁TMD的能保证就是直接在本机存盘啊,没准需要已改,变成要做成可分布式部署的,那没准回调就变成了 基于SOCKET的各种通讯协议的消息分发了。这个必须要给自己的机智点赞!在这里插入图片描述

接下来,我们得实测一下了,没有数据也不敢高声讲话啊。

四、请上我的专属测试:右手哥

#include <iostream>
#include <fstream>
#include <string>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <vector>
#include <atomic>
#include <sstream>
#include <ctime>
#include <iomanip>
#include "tinylog.hpp"

#ifdef _WIN32 || _WIN64
#include <Windows.h>
#include <time.h>
#endif

#define TEST_DEMO


std::unique_ptr<TinyLog> _log = nullptr;

BOOL WINAPI ConsoleCtrlHandler(DWORD ctrlType) {
    if (ctrlType == CTRL_C_EVENT) {
        std::cout << "CTRL+C received. Exiting..." << std::endl;

        if(_log){
            _log->setStopped();
        }

        return TRUE;
    }
    return FALSE;
}

int main(int argc,char* argv[])
{
    _log = std::make_unique<TinyLog>(102400,2);

    if (!SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE)) {
        std::cerr << "Error setting control handler" << std::endl;
        return 1;
    }

    std::thread thrd([](){

#ifdef TEST_DEMO
        auto start = std::chrono::high_resolution_clock::now(); // Start time
#endif
        char message[255]  = {};
        for(size_t time = 0; time < 1000000; ++time){
            memset(message,0,255);
            snprintf(message,255,"Log message %d",time);
            _log->log(message);
        }
#ifdef TEST_DEMO
        auto end = std::chrono::high_resolution_clock::now(); // End time
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start).count();
        double secs = duration / 1000000.00 ;
        std::cout << "produce time: " << secs << " seconds.\n" << std::endl;
#endif
    });

    if(thrd.joinable()){
        thrd.join();
    }

    while(!_log->isStopped()){
          std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
    return 0;
}

测试不说硬件环境好像你们也会喷,别CHAO喷。
笔者:11th Gen Intel(R) Core(TM) i5-1155G7 @ 2.50GHz 2.50 GHz HDD(我甚至没有SSD,哭晕)


笔者测试过了,百万条数据综合落盘写入日志文件,约5s;太酷了吧(有点拉胯)。能用么,绝大多数的场景基本都够用了,但是本文的标题是什么,“百万级”“高性能”,这显然达不到要求,那么,该怎么优化呢?

五、如何C出残影

思路肯定是一大把,毕竟C和被C了这么多年,一个优质的PIAO客还是有很多技巧的。首先我们定位耗时影响性能的点,那肯定不用想,必抓 消息队列的出入速度了,不够快,怎会有激情。
大致说说改善的点,锁这笔玩意,坚决不能要,我们要自己撸一个无锁的高性能环形队列;其次,队列的内存布局要优化下,必须要能预分配,有需要可以上个内存池;再次有必要我们可以上一个双缓冲;再再次…

下篇继续,下篇继续。“它山之石哦,可以攻玉兮”~

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【逗老师的无线电】QRZ快速得到Incoming请求的准确QSO时间

各位友台&#xff0c;有没有遇到过别人从QRZ发过来了Incoming的QSO请求&#xff0c;但是我完全不记得QSO的时间和波段&#xff0c;盲猜要猜好久。尤其是下面这种&#xff0c;8月份发来的6月份的通联记录&#xff0c;这我天天FT8&#xff0c;上哪翻当天的记录啊&#xff08;大概…

第6章>>实验6:PS(ARM)端Linux RT与PL端FPGA之间(通过Reg寄存器进行通信和交互)-《LabVIEW ZYNQ FPGA宝典》

1、实验内容 前面第五章入门实验和上一个实验5里面我们向大家展示通过了布尔类型的Reg寄存器通道实现了ZYNQ PS端ARM和PL端FPGA二者之间的开关量交互&#xff0c;抛砖引玉。 从本节实验开始&#xff0c;接下来4个实验我们将着重向大家讲解更为通用和更为全面的4种交互方式&…

研0 冲刺算法竞赛 day27 P1090 [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G

P1090 [NOIP2004 提高组] 合并果子 / [USACO06NOV] Fence Repair G - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 考点&#xff1a;哈夫曼树 思路&#xff1a;建优先队列&#xff0c;自动排序&#xff0c;然后每次取出最小两个即可。本来思路是数组的&#xff0c;但是一直写…

这才是你需要的C语言、C++学习路线!

大家好&#xff0c;我已经整理好了关于学习 C 语言和 C 的路径图。 接下来&#xff0c;让我们先聊一些有趣且常见的话题。 这些问题是我经常在私信中收到的&#xff0c;同时也是我在学习过程中曾经感到困惑的地方。 粉丝福利&#xff0c; 免费领取C/C 开发学习资料包、技术视…

Jenkins保姆笔记(2)——基于Java8的Jenkins插件安装

前面我们介绍过&#xff1a; Jenkins保姆笔记&#xff08;1&#xff09;——基于Java8的Jenkins安装部署 本篇主要介绍下基于Java8的Jenkins插件安装。为什么要单独讲一个插件安装&#xff1f;因为一些原因&#xff0c;Jenkins自带的插件源下载几乎都会失败&#xff0c;如图…

小怡分享之Java的String类

前言&#xff1a; &#x1f308;✨之前小怡给大家分享了图书管理系统这个项目&#xff0c;今天小怡给大家分享Java的String类。 1.String类的重要性 String是字符串类型&#xff0c;C语言中没有字符串类型。 Java当中没有说字符串的结尾是 \0这样的说法。C语言中要表示字符串只…

【爬虫实战】利用代理爬取Temu电商数据

引言 在行业竞争激烈、市场变化快速的跨境电商领域&#xff0c;数据采集可以帮助企业深入了解客户需求和行为&#xff0c;分析市场趋势和竞争情况&#xff0c;从而优化产品和服务&#xff0c;提高客户满意度和忠诚度。同时&#xff0c;数据采集可以实时跟踪库存水平和销售情况&…

Windows10上安装SQL Server 2022 Express

Microsoft SQL Server 2022 Express是一个功能强大且可靠的免费数据管理系统&#xff0c;可为轻量级网站和桌面应用程序提供丰富可靠的数据存储&#xff0c;为关系数据库&#xff1a; (1).LocalDB(SqlLocalDB)&#xff1a;是Express的一种轻型版本&#xff0c;该版本具备所有可…

常见中间件漏洞复现之【Tomcat】!

Tomcat介绍 tomcat是⼀个开源⽽且免费的jsp服务器&#xff0c;默认端⼝ : 8080&#xff0c;属于轻量级应⽤服务器。它可以实现 JavaWeb程序的装载&#xff0c;是配置JSP&#xff08;Java Server Page&#xff09;和JAVA系统必备的⼀款环境。 在历史上也披露出来了很多的漏洞 …

Python爬虫实战:利用代理IP爬取百度翻译

文章目录 一、爬取目标二、环境准备三、代理IP获取3.1 爬虫和代理IP的关系3.2 巨量IP介绍3.3 超值企业极速池推荐3.4 IP领取3.5 代码获取IP 四、爬虫代码实战4.1分析网页4.2 寻找接口4.3 参数构建4.4 完整代码 一、爬取目标 本次目标网站&#xff1a;百度翻译&#xff08;http…

Java I/O (Input/Output)——文件字节流

博客主页&#xff1a;誓则盟约系列专栏&#xff1a;Java SE 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ Java I/O 简介 Java I/O&#xff08;输入/输出&#xff09;是 Java 程序中…

抖音ip地址怎么换到别的地方

在数字化时代&#xff0c;抖音作为一款风靡全球的短视频社交平台&#xff0c;让我们的生活充满了无限乐趣与创意。然而&#xff0c;有时我们可能希望自己的抖音能够显示一个不同于当前所在地的IP地址&#xff0c;无论是出于隐私保护、还是其他个性化需求。那么&#xff0c;如何…

Linux学习记录(三)-----文件io和标准io的区别

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言文件IO和标准IO的区别1.\r和\n的区别2.缓冲2.1缓冲区的概念2.2.缓冲区的分类 3.文件IO和标准IO的区别 前言 文件IO和标准IO的区别 1.\r和\n的区别 \r 回车操作…

无人机之植保机篇

一、什么是植保无人机 植保无人机是用于农林植物保护作业的无人驾驶飞机&#xff0c;该型无人飞机由飞行平台、导航飞控、喷洒机构三部分组成&#xff0c;通过地面遥控器或导航飞控&#xff0c;来实现喷洒作业&#xff0c;可以喷洒药剂、种子、粉剂等。目前国内销售的植保无人机…

【已解决】VSCode连接Linux云服务器,代码写着写着服务器突然挂了是怎么回事?

文章目录 1. 问题描述2. 问题原因3. 解决方法 1. 问题描述 在使用 VSCode 连接远程 Ubuntu 云服务器写代码的时候&#xff0c;感觉越写越卡&#xff0c;代码提示半天出不来&#xff0c;最后更是直接断开连接了&#xff1a; 即使把 VSCode 关了&#xff0c;再重启也没用&#x…

五种IO模型与阻塞IO

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 IO本质 我们常说IO就是input&#xff0c;output&#xff0c;也就是输入和输出&#xff0c;但是&#xff0c;他的本质是什么&#xff1f;站在OS角度&#xff0c;站在进程的角度&#xff0c;IO是什么&#xff1f;我们想&#…

申请专利需要准备哪些材料?

申请专利需要准备哪些材料&#xff1f;

代码之外的生存指南——自我营销

你是否有去过酒吧、夜店看过驻场乐队的演出&#xff1f; 你到了那里面听过之后你会发现那些乐队的演唱水平丝毫不亚于原唱的艺术家们&#xff0c;都很有才华&#xff1b; 你有没有想过【为什么这些驻场乐队就只能在那小小的夜店里做驻唱演出&#xff0c;每天疲于奔命&#xff0…

图综述-GGNN详解

A Survey of Geometric Graph Neural Networks:Data Structures, Models and Applications 本文主要介绍了在化学领域的分子设计和预测任务中&#xff0c;如何利用几何图神经网络&#xff08;Geometric Graph Neural Networks&#xff0c;简称GGNN&#xff09;来处理具有几何信…

怎样可以撰写出一篇优质软文呢?

现在这个互联网飞速发展的时代&#xff0c;软文推广已经逐渐变成了现在很多企业和品牌的推广宣传方式了&#xff0c;虽然软文推广操作起来很简单&#xff0c;但是想要做好没那么简单&#xff0c;软文稿件的质量和推广的流量更是息息相关。 好的软文不止可以让转化更高&#xff…