Rpc异步日志模块

Rpc异步日志模块作用

在一个大型分布式系统中,任何部署的分布式节点都可能发生崩溃,试想如果用普通的办法,即先排查哪个节点down掉了,找到down掉的节点后采取调试工具gdb调试该节点,进而排查宕机的原因。这中排查方法对于人力物力都是无法接受的。

那么由此记录日志就变得至关重要,分布式RPC框架必定存在一个异步日志模块,用于记录所有分布式站点的调试信息。通过日志分析就能很容易排查出哪个节点出了问题

Rpc异步日志模块实现思路

一个日志模块必须是异步的,不能影响主程序的运行,例如在RPC框架中显然不能阻塞了RPCProvider(服务提供者)和RPCConsumer(服务调用者)的运行

RPCProvider是一个能接受高并发rpc请求的高性能服务器(epoll+多线程),那么存在多个线程同时写日志的情况,这里的"写日志"并不是真正意义上的写磁盘文件的操作,因为磁盘IO会严重拖累该线程原本执行的其他任务。所以这里的写日志 只是多个线程将日志写入一个异步缓冲队列(这个操作是在内存进行的非常快),并且这个队列必须是线程安全的。

此外,应该另起一个线程来读取队列里面的日志数据进行真正的磁盘写文件操作,这样写日志线程是单独工作的,它只是依赖于异步缓冲队列里面的数据,不会影响RPC服务线程和其他IO线程。

Rpc异步日志模块实现

下面提供一个简单版本的实现
异步缓冲队列类:

java 复制代码
#pragma once

#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>

//异步写日志的日志队列
template<typename T>
class LockQueue
{

public:
    //多个work线程都会写日志queue
    void Push(const T& data)
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        m_queue.push(data);
        m_condvariable.notify_one();
    }
    //一个线程读日志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;
    }
private:
    std::queue<T> m_queue;
    std::mutex m_mutex;
    std::condition_variable m_condvariable;

};

Logger.h类:

java 复制代码
#pragma once

#include "lockqueue.h"
#include <utility>

//日志级别
enum LogLevel{
    INFO = 1, //普通信息
    ERROR  //错误信息
};

//Mprpc框架提供的日志系统
class Logger
{
public:
    //获取日志的单例
    static Logger& GetInstance();
    //写日志
    void Log(std::pair<LogLevel,std::string> msg);

private:
    LockQueue<std::pair<LogLevel,std::string>> m_lckQue; //日志缓冲队列

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

//定义宏  LOG_XXX("xxx %d %s", 20, "sdasd");
#define LOG_INFO(logmsgformat, ...) \
    do \
    { \
        Logger &logger = Logger::GetInstance(); \
        char c[1024] = {0};           \
        snprintf(c, 1024, logmsgformat, ##__VA_ARGS__); \
        std::pair<LogLevel,std::string> pr = std::make_pair(INFO, std::string(c)); \
        logger.Log(pr); \
    } while (0);

#define LOG_ERR(logmsgformat, ...) \
do \
{ \
    Logger &logger = Logger::GetInstance(); \
    char c[1024] = {0};           \
    snprintf(c, 1024, logmsgformat, ##__VA_ARGS__); \
    std::pair<LogLevel,std::string> pr = std::make_pair(ERROR, std::string(c)); \
    logger.Log(pr); \
} while (0);
    

Logger.cc

java 复制代码
#include "logger.h"
#include <time.h>
#include <iostream>

//获取日志的单例
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::pair<LogLevel,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,
                                    (msg.first == INFO ? "info" : "error"));
            msg.second.insert(0, time_buf);
            msg.second.append("\n");
            fputs(msg.second.c_str(), pf);
            fclose(pf); 
        }
    });

    //设置分离线程, 守护线程
    writeLogTask.detach();

}

//写日志,把日志信息写入到lockqueue缓冲区当中
void Logger::Log(std::pair<LogLevel,std::string> msg)
{
    m_lckQue.Push(msg);
}

使用:

java 复制代码
...
LOG_INFO("NotifyService UserService success");
LOG_ERR("eeeeeerrror%d", 9999999);
LOG_INFO("NotifyService GetFriendListService success");
...

查看日志文件:cat 2023-8-2-log.txt 
21-35-26 => [info]NotifyService UserService success
21-35-26 => [error]eeeeeerrror9999999
21-35-26 => [info]NotifyService GetFriendListService success
相关推荐
anew___5 小时前
蜂鸣器播放音乐——让 Arduino 唱出旋律
c++·stm32·单片机·嵌入式硬件·c
stolentime6 小时前
洛谷P10515 转圈题解
c++·算法·数学建模·贪心算法
隐擎fox6 小时前
高性能网络爬虫架构设计:基于 Python 的长连接复用与分布式会话池调度实践
分布式·python·网络协议·tcp/ip·高并发·网络爬虫、
tdtsmt7 小时前
穿戴硬件量产工艺实录:天地通电子智能手表主板 SMT 贴片案
c++
m0_7345717610 小时前
深入理解C++ 析构函数<一>基本概念
开发语言·c++
t-think11 小时前
C++ string 类(上)—— string类初识与常用接口详解
开发语言·c++
cpp_learners12 小时前
C++ 实现责任链模式(Chain of Responsibility):从一堆 if-else 到可插拔的处理管道
开发语言·c++·责任链模式
郝学胜_神的一滴12 小时前
C++11 工程级应用 12:编译期类型魔法,干掉重复与臃肿的代码
c++·visual studio
闲云自留地12 小时前
云平台存储管理员:Cinder 创建挂载卷 + Swift 分布式存储原理
分布式·wpf·swift
David猪大卫13 小时前
【C++修炼】哈希表的实现
数据结构·c++·笔记·学习·算法·哈希算法·散列表