RPC分布式网络通信框架(四)—— 异步日志模块设计

news2024/10/7 18:25:06

文章目录

  • 异步日志模块
    • Logger类实现
    • 线程安全LockQueue类实现


异步日志模块

问题:由于RPC服务器端采用了epoll+多线程 ,并发处理来自客户端的请求,所以有可能造成多线程同时写日志信息。

将日志信息写入一个queue中,然后新建日志线程。但是需要注意的是,由于rpcprovider类中是epoll+多线程,所以进而会创建多个日志线程。这样必须要保证线程安全
在这里插入图片描述
于是需要自定义线程安全的queue。思路:如果queue为空,该线程就不抢锁了。
日志生成的命名格式为:年-月-日-log.txt;内容为时-分-秒+log信息

Logger类实现


enum LogLevel
{
	INFO,    // 普通信息
	ENTER, // 错误信息
}
class Logger
{
public:
    // 获取日志的单例
    static Logger& GetInstance();
    // 设置日志级别 
    void SetLogLevel(LogLevel level);
    // 写日志
    void Log(std::string msg);
private:
    int m_loglevel; // 记录日志级别
    LockQueue<std::string>  m_lckQue; // 日志缓冲队列

    Logger();
    Logger(const Logger&) = delete;
    Logger(Logger&&) = delete;
}

同样,单例模式记得去除拷贝和移动构造函数。

获取日志的懒汉单例模式:

Logger& Logger::GetInstance()
{
    static Logger logger;
    return logger;
}

因为要用宏调用,所以将无参构造函数作为写日志的线程,代码如下:

Logger::Logger()
{
    // 启动专门的写日志线程
    std::thread writeLogTask([&](){
        for (;;)
        {
            // 获取当前的日期,然后取日志信息,写入相应的日志文件当中 a+
            time_t now = time(nullptr);
            tm *nowtm = localtime(&now);

            char file_name[128];
            sprintf(file_name, "%d-%d-%d-log.txt", nowtm->tm_year+1900, nowtm->tm_mon+1, nowtm->tm_mday);

            FILE *pf = fopen(file_name, "a+");
            if (pf == nullptr)
            {
                std::cout << "logger file : " << file_name << " open error!" << std::endl;
                exit(EXIT_FAILURE);
            }

            std::string msg = m_lckQue.Pop();

            char time_buf[128] = {0};
            sprintf(time_buf, "%d:%d:%d =>[%s] ", 
                    nowtm->tm_hour, 
                    nowtm->tm_min, 
                    nowtm->tm_sec,
                    (m_loglevel == INFO ? "info" : "error"));
            msg.insert(0, time_buf);
            msg.append("\n");

            fputs(msg.c_str(), pf);
            fclose(pf);
        }
    });
    // 设置分离线程,守护线程
    writeLogTask.detach();
}

主要功能是将m_lckQue的内容全部写入文件中。
注意:
线程在等待期间不会一直进行写入操作,而是会暂停等待,直到有新的日志信息被添加到队列中。这是由于队列操作使用了一些同步机制(条件变量或互斥锁),来确保写入线程在队列为空时等待新的日志信息的到来。

而因为日志为单例模式,多次调用宏定义时,只会有一个Logger对象实例存在。而写日志线程是在第一次调用宏定义时创建并启动的,之后的调用并不会创建新的写日志线程。

定义日志宏,让用户不用去实例化Logger类就能用可变参的形式写日志。

#define LOG_INFO(logmsgformat, ...) \
    do \
    {  \
        Logger &logger = Logger::GetInstance(); \
        logger.SetLogLevel(INFO); \
        char c[1024] = {0}; \
        snprintf(c, 1024, logmsgformat, ##__VA_ARGS__); \
        logger.Log(c); \
    } while(0) \

#define LOG_ERR(logmsgformat, ...) \
    do \
    {  \
        Logger &logger = Logger::GetInstance(); \
        logger.SetLogLevel(ERROR); \
        char c[1024] = {0}; \
        snprintf(c, 1024, logmsgformat, ##__VA_ARGS__); \
        logger.Log(c); \
    } while(0) \

其中

void Logger::Log(std::string msg)
{
    m_lckQue.Push(msg);
}

宏定义体中的具体操作如下:

  1. 通过调用 Logger::GetInstance() 来获取 Logger 类的单例实例
  2. 设置日志级别为 INFO/ERROR,通过调用 logger.SetLogLevel(INFO/ERROR)。
  3. 创建一个大小为 1024 字节的字符数组 c,并将其初始化为全零。
  4. 使用 snprintf 函数将格式化的日志消息和可变数量的参数写入字符数组 c 中,最多写入 1024 字节。
  5. 调用 logger.Log(c) 将字符数组中消息塞入日志系统中的lockqueue中。
  6. 整个宏定义通过 do-while 语句实现了一个代码块,目的是确保宏定义可以像普通语句一样使用,而不会受限于语法结构。while(0) 是为了确保该宏只能作为单个语句使用,并且在使用时不会引入额外的控制流。

调用方法

LOG_INFO("This is an info message: %s", message);

线程安全LockQueue类实现

  1. 首先需要封装一个日志队列的自定义的Push和Pop的api接口,通过mutex和conditional_variable来保证线程安全。
  2. 他的原理类似一个生产者消费者模型,队列Push函数处理的是rpc服务器端的多个worker线程向队列里写数据,写之前加上一把互斥锁,然后push数据,结束以后notify阻塞等待写日志线程向磁盘写数据。
  3. Pop接口 首先会检测队列是否为空,为空代表没有数据 就会进入阻塞wait状态 然后释放锁 ,有数据来了返回数据。

带锁的LockQueue类是一个模板类,他的定义不能写到lockqueue.cc中。

// 多个worker线程都会写日志queue (宏定义)
void Push(const T &data)
{
    std::lock_guard<std::mutex> lock(m_mutex);
    m_queue.push(data);
    m_condvariable.notify_one();
}

rpc框架调用宏定义,将日志信息Push进入lockqueue。

Logger类写线程负责Pop:

// 一个线程读日志queue,写日志文件
T Pop()
{
    std::unique_lock<std::mutex> lock(m_mutex);
    while (m_queue.empty())
    {
        // 日志队列为空,线程进入wait状态
        m_condvariable.wait(lock);
    }

    T data = m_queue.front();
    m_queue.pop();
    return data;
}

条件变量的意义:
条件变量通常与互斥锁配合使用,用于实现线程之间的等待和唤醒机制。当多个线程需要等待某个条件满足时,它们会调用条件变量的wait()方法进入等待状态,同时释放互斥锁。当条件满足时,另一个线程会调用条件变量的notify_one()或notify_all()方法来唤醒等待的线程。

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

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

相关文章

MySQL select查询练习

一、创表并插入数据 创表&#xff1a; CREATE TABLE worker (部门号 int NOT NULL,职工号 int NOT NULL,工作时间 date NOT NULL,工资 float(8,2) NOT NULL,政治面貌 varchar(10) NOT NULL DEFAULT 群众,姓名 varchar(20) NOT NULL,出生日期 date NOT NULL,性别 char(1) DEFAU…

【ElasticSearch】ES与MySQL数据同步方案及Java实现

文章目录 一、同步实现思路1、方案一&#xff1a;同步调用2、方案二&#xff1a;异步通知3、方案三&#xff1a;监听binlog 二、实现ES与MySQL数据同步1、导入hotel-admin工程2、项目分析3、SpringAMQP整合4、声明队列和交换机5、发送消息MQ6、监听MQ消息7、测试同步功能 一、同…

一百二十九、Kettle——从MySQL增量导入到GreenPlum

一、目标 用Kettle从MySQL增量导入数据到GreePlum 二、前提准备 &#xff08;一&#xff09;kettle已连上MySQL &#xff08;二&#xff09;kettle已连上GreenPlum 三、实施步骤 &#xff08;一&#xff09;打开kettle&#xff0c;新建转换任务。拖拽2个表输入、替换NULL…

【Doris的安装和使用】

1.准备工作 1.1集群规划 1.2 操作系统安装要求 设置系统最大打开文件句柄数(注意这里的*不要去掉) sudo vim /etc/security/limits.conf * soft nofile 65536 * hard nproc 65536设置最大虚拟块的大小 sudo vim /etc/sysctl.conf vm.max_map_count2000000将修改的配置分发给…

麒麟-飞腾Kylin-V4桌面arm64系统静态编译QT

1.系统具体版本&#xff1a; 2. 因为此版本的源很老了&#xff0c;需要修改版本的源&#xff0c;才能正常更新各种软件&#xff0c;否则&#xff0c;你连麒麟商店都打不开。 sudo vi /etc/apt/sources.list 选择你系统对应版本的源地址&#xff1a; #4.0.2桌面版本: deb ht…

k8s 中的卷

前面的文章我们分享了 pod &#xff0c;RC&#xff0c;RS&#xff0c;DaemonSet&#xff0c;CJ&#xff0c;Service 等各种资源 今天我们来分享一波如何将磁盘挂载到容器中&#xff0c;在 docker 里面这种技术叫做 数据卷&#xff0c;感兴趣的小伙伴可以查看一下文章&#xff…

JDK环境配置、且运行一个简单程序

目录 JDK环境配置命令行运行java文件 JDK环境配置 下载好jdk,打开jdk下的bin&#xff0c;复制路径。 右击我的电脑&#xff0c;点击属性&#xff0c;找到高级系统设置&#xff0c;点击环境变量。 双击path&#xff0c;新建把路径粘贴进去即可。 打开cmd输入javac -version…

不会编程也可以制作ERP、CRM系统?

在以往的编程开发中&#xff0c;如果想要个人开发一款简单的ERP等流程系统&#xff0c;肯定是需要有编程代码的功底的&#xff0c;再学习编程语言和框架&#xff0c;又得花费大量的时间&#xff0c;而且不能完全确保可以做出来&#xff0c;毕竟编程开发有一定的门槛&#xff0c…

3d Max中的Arnold渲染为黑色,这样处理!

使用Arnold渲染视图(ARV)时&#xff0c;图像保持黑色。 快照功能和常规3ds Max渲染设置可按预期生成图像。 解决方案&#xff1a; 解决方案可能需要执行下面的一项或多项操作&#xff1a; 添加光源 检查场景文件是否包含光源。如果场景中没有光源&#xff0c;渲染结果为黑色…

通信算法之178: 通信信道模型及循环/线性卷积2

上一篇见 通信算法之159: 通信信道模型和循环/线性卷积 一. 衰减 二.多径效应--时延扩展--相干带宽 三. 时变性--多普勒扩展--相干时间 四. 频率选择性衰落&#xff0c;时间选择性衰落 小尺度衰落&#xff0c;小 五.瑞丽和莱斯信道 六循环卷积与线性卷积 线性卷积定义及计算…

C# PaddleInference OCR 表格识别

效果 项目 VS2022.net4.8OpenCvSharp4Sdcb.PaddleInferenceSdcb.PaddleOCR 测试图片 代码 using OpenCvSharp.Extensions; using OpenCvSharp; using Sdcb.PaddleInference; using Sdcb.PaddleOCR; using Sdcb.PaddleOCR.Models; using Sdcb.PaddleOCR.Models.Details; using…

netwox伪造ARP响应【网络工程】(保姆级图文)

目录 伪造ARP响应1) 在模拟之前&#xff0c;验证局域网中是否存在主机 192.168.43.97。在主机 B 上使用 arping 命令 ping 该主机。执行命令如下&#xff1a;2) 在主机 A 上伪造 ARP 响应&#xff0c;创建虚拟主机 192.168.43.97&#xff0c;设置其 MAC 地址为 A1&#xff1a;B…

react 初学(1)

1.安装环境 需要Node.js 自行下载安装 然后全局安装create-react-app npm install -g create-react-app 如果出现报错请参考 create-react-app -V 报错无法将“create-react-app”项识别为 cmdlet、函数、脚本文件或可运行程序的名称_Java-请多指教的博客-CSDN博客 2.创建…

尚医通04:Axios Node Npm bable webpack+前端工程改造

目录 本日学习 内容介绍 Axios Node NPM包管理器 本日学习 1. 了解Axios :他是异步请求用的&#xff0c;前后端。 用于在浏览器和 Node.js 中发送 HTTP 请求。它支持从服务器获取数据、上传数据以及执行其他与 HTTP 相关的操作。 2.Node:它允许你在服务器端运行 JavaScrip…

stm32(adc数模转换)

ADC介绍 ADC是什么&#xff1f; 全称&#xff1a;Analog-to-Digital Converter&#xff0c;指模拟/数字转换器 ADC的性能指标 量程&#xff1a;能测量的电压范围分辨率&#xff1a;ADC能辨别的最小模拟量&#xff0c;通常以输出二进制数的位数表示&#xff0c;比如&#xff1…

Leetcode每日一题:931. 下降路径最小和(2023.7.13 C++)

目录 931. 下降路径最小和 题目描述&#xff1a; 实现代码与解析&#xff1a; 动态规划 原理思路&#xff1a; 931. 下降路径最小和 题目描述&#xff1a; 给你一个 n x n 的 方形 整数数组 matrix &#xff0c;请你找出并返回通过 matrix 的下降路径 的 最小和 。 下降…

mongdb实战

概述 前言 这几天轮播图想用mongdb开发&#xff0c;然后就有了一下代码 效果图 源码如下 package jkw.pojo;import lombok.Data; import org.springframework.data.annotation.Id; import org.springframework.data.mongodb.core.index.Indexed; import org.springframewo…

【JAVA】穷词——基于嵌入式的数据库derby+BeautyEye的单词字典应用

文章目录 1. 题目2. 项目结构层次3. 环境以及技术栈说明4. 项目报告4.1软件功能描述4.2项目类图4.2.1 src层级下的类图4.2.2 data层级下的类图4.2.3 gui层级下的类图4.2.4 resource层级下的类图4.2.5 view层级下的类图4.2.6 DelWord的类图4.2.7 CustomMessageDialog的类图4.2.8…

Antd List组件增加gutter属性后出现横向滚动,如何解决

第一次使用ant design的List列表组件&#xff0c;设置gutter间隔属性后&#xff0c;页面出现了横向滚动条&#xff0c;查阅文档发现是由于加间隔后导致容器宽度被撑开&#xff0c;ant design官方默认给外层容器加了margin-left和margin-right 解决方法是在外层容器预留一定的pa…

stringstream的使用

写到290题使用stringstream简化步骤&#xff0c;学习一下使用 目录 小问题&#xff1f; 成员函数clear() 那么问题来了&#xff1f;clear在啥时候用呢&#xff1f; 数据类型转换 <sstream>库定义了三种类&#xff1a;istringstream、ostringstream、stringstream &l…