分布式日志聚集系统开发总结

项目功能

主要功能:接收来自多个节点的日志,按节点名存入文件。

分析功能:设定多个Tag,将日志与Tag匹配。最终可以让用户指定Tag,将所有匹配日志存入指定文件。

对外接口类

LogService

主要对外接口。

启动后持续运行,接收节点日志,交给Handler类处理。

用户可以设定并行线程数。

TagManager

管理Tag。

单例。

Analyst

分析数据。

生成Tag文件。 

内部实现类

 FileWriter

打开文件写入。

 Handler类

处理日志,包括存入文件、匹配Tag存入Cache。

 Cache类

保存与Tag匹配成功的日志。

数据类型

来自节点的任务
struct LogTask {
    std::string host; // 发送者
    std::string msg;  // 消息
};

存入Cache的缓存信息
struct LogInfo {
    std::string file; // 文件名
    size_t line;      // 行号
};

共享队列,在多线程中保证队列安全
class SharedQueue;

工作流程

用户调用TagManager接口,生成Tag。

用户构造LogService,设定线程数。

LogService拥有成员Handler [ ],每个Handler用一个线程。
LogService拥有成员Cache,将Cache传给Handler [ ]。

用户调用LogService接口添加节点。

LogService拥有成员FileWriter [ ],LogService为每一个节点生成FileWriter,
并将FileWriter [ ] 传给Handler [ ]。

用户启动LogService。

用户调用LogService的接口添加任务,LogService直接把任务平均分配到Handler的任务队列,每个Handler拥有一个任务队列。

Handler用LogService传入的FileWriter,将日志写入节点文件,写入函数会返回行号。
Handler从TagManager获得Tags,做匹配,匹配成功则将<节点文件名:行号>存入Cache。

直到用户调用LogService接口停止服务,停止服务后LogService接口无法再接收任务,所有Handler完成任务队列。

用户调用Analyst的接口,指定Tag,指定文件名。

Analyst类调用LogService接口获取Cache。调用Cache接口,从Cache中得到与Tag匹配的所有日志在文件中的储存信息。Analyst从文件中读取与Tag匹配的所有日志,生成到指定文件名的文件中。

流程图 (点击放大)

 从本次开发中获得的经验

C++开发经验之谈1_zidian666的博客-CSDN博客

 改正/优化

void TagManager::remove(const std::string &tag)
{
    std::unique_lock<std::mutex> ulock(mtx_tags_);
    tags_.erase(tag);
}

Tag被romove后,应从cache中删除相关缓存 

void TagManager::remove(const std::string &tag)
{
    cache_->remove(tag);
    std::unique_lock<std::mutex> ulock(mtx_tags_);
    tags_.erase(tag);
}


void TagManager::remove(const std::string &tag)
{
    cache_->remove(tag);
    std::unique_lock<std::mutex> ulock(mtx_tags_);
    tags_.erase(tag);
}

在借用作用域释放锁的地方,用lock_guard而不是unique_lock。

用{}可以轻松限制作用域。

void TagManager::remove(const std::string &tag)
{
    cache_->remove(tag);
    std::lock_guard<std::mutex> ulock(mtx_tags_);
    tags_.erase(tag);
}


class TagManager
{
public:
    /**
     * @brief 获取当前所有生效的条目
     */
    std::map<std::string, bool> &get_all_tags();
    {
        return tags_;
    }

private:
    std::map<std::string, bool> tags_; // string: tag, bool: enabled
    std::mutex mtx_tags_;
    Cache *cache_ = nullptr;
};

返回类成员的引用可能会导致:

外部类读取类成员同时,类成员被类对象修改,未同步;

外部类修改类成员;

std::vector<std::string> TagManager::get_all_tags()
{
    std::lock_guard<std::mutex> ulock(mtx_tags_);
    std::vector<std::string> ret;
    ret.reserve(tags_.size());
    for (auto &each : tags_)
    {
        if (each.second)
            ret.push_back(each.first);
    }
    return ret;
}


class LogServcie{
public:
    void addHandler()
    {
        task_queues_.push_back(SharedQueue<Task>());
        handlers.emplace_back(Handler(task_queues_.back()));
    }
    void addTask(Task task)
    {
        task_queues_[total_task_num_++ % task_queues.size()].push(task);
    }
private:
    size_t total_task_num_ = 0;
    std::vector<Handler> handlers_;
    std::vector<SharedQueue<Task>> task_queues_;
};

class Handler{
public:
    Handler(SharedQueue &task_queue) : task_queue_(task_queue) {}
    void handleTask();
private:
    SharedQueue<Task> &task_queue_;
}

某些成员可以作为处理它的类的成员,而不是以传参的形式将成员传给处理它的类

此例中的task_queue。

class LogServcie{
public:
    void addHandler()
    {
        handlers.emplace_back(Handler());
    }
    void addTask(Task task)
    {
        handlers_[total_taks_num_++ % handlers_.size()].addTask(task);
    }
private:
    size_t total_task_num_ = 0;
    std::vector<Handler> handlers_;
};

class Handler{
public:
    Handler() = default;
    void addTask(Task task)
    {
        task_queue_.push(task);
    }
    void handleTask();
private:
    SharedQueue<Task> task_queue_;
}


class SharedQueue{
    void push(Task);
    void pop();
    Task front();
    //各操作保证同步
};

class Handler{
    void handle()
    {
        Task = task_queue_.front();
        task_queue_.pop();
    }
private:
    SharedQueue<Task> task_queue_;
};

即使类中操作保证同步,但多线程调用仍可能导致异步。

如果两个Handler各一个线程对同一个SharedQueue操作,本期望:

thread1.front();
thread1.pop();
thread2.front();
thread2.pop();

实际可能:

thread1.front();
thread2.front();
thread1.pop();
thread2.pop();

在SharedQueue中增加功能frontAndPop()可解决这个问题。

template <typename T>
class SharedQueue
{
public:
    T SharedQueue<T>::frontAndPop();
private:
    std::deque<T> queue_;
    std::mutex mutex_;
    std::condition_variable cond_;
};

template <typename T>
T SharedQueue<T>::frontAndPop()
{
    std::unique_lock<std::mutex> mlock(mutex_);
    while (queue_.empty())
    {
        cond_.wait(mlock);
    }
    T temp = queue_.front();
    queue_.pop_front();
    return temp;
}


class Handler{
public:
    Handler() = default;
    void addTask(Task *task)
    {
        task_queue_.push(*task);
        delete task;
    }
    void handleTask();
private:
    SharedQueue<Task> task_queue_;
}

某些时候,存指针所指值,不如直接存指针。 

class Handler{
public:
    Handler() = default;
    void addTask(Task *task)
    {
        task_queue_.push(task);
        delete task;
    }
    void handleTask()
    {
        Task *tmp = task_queue_.frontAndPop();
        do something with tmp;
        delete tmp;
    }
private:
    SharedQueue<Task*> task_queue_;
}


std::transform(src.begin(),src.end(),dist.begin(),(int (*)(int))std::tolower);

官方文档中不建议这样使用std::toupper或std::tolower;

由此得到的收获是:第一次使用的东西去查官方文档。

std::transform(lower_tag.begin(), lower_tag.end(), lower_tag.begin(),
                                                   [](unsigned char c)
                                                   {
                                                       return std::tolower(c);
                                                   });


void LogService::addTask(Task *task)
{
    if (!stop_flag && !task)
    {
        handlers_[total_task_num % handlers_.size()].addTask(task);
        ++total_task_num;
    }
}

两个条件检测,或许可以把更重要的放在前面。

void LogService::addTask(LogTask *task)
{
    if (!task)
        return;
    if (!stop_flag)
    {
        handlers_[total_task_num % handlers_.size()].addTask(task);
        ++total_task_num;
    }
}


std::map<std::string, bool> tags;
std::string tag;
if (tags.find(tag) != tags.end())
{
    if (tags[tag].second)
        do something with tags[tag].first;
}

多次用到查找,不如用一个迭代器解决。

std::map<std::string, bool> tags;
std::string tag;
auto where = tags.find(tag);

if (where != tags.end())
{
    if (where->second)
        do something with where.first;
}


FILE* file_ = fopen(file.c_str(), "w+");
std::string content;
fputs(content.c_str(), file_);

fputs函数会将原本写入文件的内容放在缓冲区,直到调用fclose()或fflush()才能将缓冲区内容写入文件。

通过setvbuf()可以设置缓冲模式。

建议使用更广泛使用的fread()和fwrite()。

FILE* file_ = fopen(file.c_str(), "w+");
std::string content;
fputs(content.c_str(), file_);
// _IOLBF, 行缓冲模式, 遇到'\n'或缓冲区满时写入文件
setvbuf(file_, NULL, _IOLBF, 1024);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值