日志系统扩展一:日志落地数据库:MySQL、SQLite3

日志系统扩展一:日志落地数据库:MySQL、SQLite3

  • 一、设计
    • 1.怎么落地
    • 2.落地的具体设计
    • 3.表的设计
      • 1.MySQL
      • 2.SQLite3
  • 二、数据库访问Helper的实现
    • 1.需要事务,但是无需回滚,如何理解?
      • 1.需要事务
      • 2.无需回滚
    • 2.SqliteHelper
      • 1.SQLite3常用接口介绍
      • 2.实现
    • 3.MySQLHelper
      • 1.MySQL常用接口介绍
      • 2.实现
    • 4.FileHelper补充
  • 三、数据库系列LogSink实现
    • 1.DBLogSink实现
    • 2.SqliteSink实现
    • 3.MySQLSink实现
    • 4.LogSinkFactory完善
  • 四、同步日志器的修改
    • 1.Logger抽象类的修改
    • 2.SyncLogger的修改
    • 3.LoggerBuilder的修改
    • 4.LocalLoggerBuilder和GlobalLoggerBuilder的修改
  • 五、数据库日志缓冲区的实现
    • 1.底层容器的选择
    • 2.原初的vector?
    • 3.具体代码
  • 六、AsyncLooper的完善
  • 七、异步日志器的完善
  • 八、测试
    • 1.前置工作
    • 2.SQLite3测试
      • 1.单线程
        • 1.同步
        • 2.异步安全
        • 3.异步不安全
    • 2.多线程
      • 1.同步
      • 2.异步安全
      • 3.异步不安全
    • 3.MySQL测试
      • 1.单线程
        • 1.同步
        • 2.异步安全
        • 3.异步不安全
      • 2.多线程
        • 1.同步
        • 2.异步安全
        • 3.异步不安全

一、设计

1.怎么落地

将日志落地到数据库,首先肯定是要建表的,而日志落地其实就是向对应表当中插入数据

那么怎么落地呢?
文件落地方式:virtual void log_fs(const char *data, size_t len) = 0;
因为文件是面向字节流的,所以这么落地日志是完全OK的

而数据库落地是向表当中插入数据,因此他的落地应该是这样的:

virtual void log_db(const LogMessage &message) = 0;

将一条LogMessage插入表当中

因此我们的LogSink日志落地基类无法满足数据库的落地需求,要不然就给他增加一个log_db函数
让子类选择性的对其中一个进行重写,另一个进行空实现,这是一种方式

不过不便于扩展,为了遵循高内聚,低耦合的程序设计原则,我们将LogSink分为两个日志基类:
FSLogSink和DBLogSink

新增加的MySQLSink和SqliteSink都继承于DBLogSink

2.落地的具体设计

我们的日志落地方式是允许用户灵活的进行格式控制的,这是为了让用户能够选择性的只记录自己想要的数据

而将日志落地到数据库,是非字符串形式的,因此格式化的意义不大

所以我们的日志落地到数据库当中是无需进行格式化的,这也印证了log_db的参数为何只需要一个LogMessage即可

virtual void log_db(const LogMessage &message) = 0;

所以我们再将日志落地到数据库时,是直接将所有字段全部都进行记录的

3.表的设计

1.MySQL

在这里插入图片描述

2.SQLite3

同样的,SQLite3当中LigData的创建也是如此,只不过具体细节要求不同:
在这里插入图片描述

二、数据库访问Helper的实现

1.需要事务,但是无需回滚,如何理解?

1.需要事务

在这里插入图片描述

2.无需回滚

在这里插入图片描述
不过我们贴心的给了大家savepoint和rollback的使用,大家可以使用

2.SqliteHelper

1.SQLite3常用接口介绍

SQLite3 官方文档
在这里插入图片描述
在这里插入图片描述

2.实现

class SqliteHelper
{
public:using SqliteCallback = int (*)(void *, int, char **, char **);SqliteHelper(const std::string &dbfile): _dbfile(dbfile), _handler(nullptr) {}~SqliteHelper(){close();}bool open(){if (sqlite3_open_v2(_dbfile.c_str(), &_handler, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, nullptr) != SQLITE_OK){std::cout << "打开数据库失败 " << errmsg() << " _dbfile:" << _dbfile << "\n";return false;}return true;}void close(){if (_handler != nullptr){sqlite3_close_v2(_handler);_handler = nullptr;}}bool begin(){if (sqlite3_exec(_handler, "begin;", nullptr, nullptr, nullptr) != SQLITE_OK){std::cout << "SQLite3开启事务失败," << errmsg() << "\n";return false;}return true;}bool exec(const std::string &sql, SqliteCallback cb, void *arg){if (sqlite3_exec(_handler, sql.c_str(), cb, arg, nullptr) != SQLITE_OK){std::cout << "执行sql语句:" << sql << " 失败," << errmsg() << std::endl;return false;}return true;}bool commit(){if (sqlite3_exec(_handler, "commit;", nullptr, nullptr, nullptr) != SQLITE_OK){std::cout << "SQLite3提交事务失败," << errmsg() << "\n";return false;}return true;}bool savePoint(const std::string &point){std::string savepoint_sql = "savepoint " + point + ";";if (sqlite3_exec(_handler, savepoint_sql.c_str(), nullptr, nullptr, nullptr) != SQLITE_OK){std::cout << "SQLite3 设置事务保存点失败:" << errmsg() << "\n";return false;}return true;}bool rollback(const std::string &point = ""){std::string rollback_sql = "rollback";if (!point.empty()){rollback_sql += " to " + point + ";";}if (sqlite3_exec(_handler, rollback_sql.c_str(), nullptr, nullptr, nullptr) != SQLITE_OK){std::cout << "SQLite3 事务回滚失败:" << errmsg() << "\n";return false;}return true;}std::string errmsg(){if (_handler != nullptr)return sqlite3_errmsg(_handler);elsereturn "sqlite3句柄为空";}private:std::string _dbfile;sqlite3 *_handler;
};

3.MySQLHelper

1.MySQL常用接口介绍

在这里插入图片描述

2.实现

我们要求用户将MySQL连接所需字段放到配置文件当中,将配置文件传递过来
我们内部进行解析

格式要求:一个字段占一行
以key:value的形式进行传递host:你MySQL服务器所在机器的IP地址
user:你的用户名
passwd:你的密码
db:你提前创建好并赋予权限了的数据库
port:你MySQL服务器的端口号
class MySQLHelper
{
public:MySQLHelper(const std::string &conf_file) : _handler(mysql_init(nullptr)), _conf_file(conf_file){}bool begin(){if (mysql_query(_handler, "begin;") != 0){std::cout << "mysql开启事务失败," << errmsg() << "\n";return false;}return true;}bool commit(){if (mysql_query(_handler, "commit;") != 0){std::cout << "mysql提交事务失败," << errmsg() << "\n";return false;}return true;}bool savePoint(const std::string &point){std::string savepoint_sql = "savepoint " + point + ";";if (mysql_query(_handler, savepoint_sql.c_str()) != 0){std::cout << "MySQL 设置事务保存点失败:" << errmsg() << "\n";return false;}return true;}bool rollback(const std::string &point = ""){std::string rollback_sql = "rollback";if (!point.empty()){rollback_sql += " to " + point + ";";}if (mysql_query(_handler, rollback_sql.c_str()) != 0){std::cout << "MySQL 事务回滚失败:" << errmsg() << "\n";return false;}return true;}bool open(){// 1. 读取配置文件,拿到host、user、passwd、db、portif (!load()){mysql_close(_handler);_handler = nullptr;return false;}// 2. 连接MySQL数据库_handler = mysql_real_connect(_handler, _conf_map["host"].c_str(), _conf_map["user"].c_str(),_conf_map["passwd"].c_str(), _conf_map["db"].c_str(), std::stoi(_conf_map["port"]), nullptr, 0);if (_handler == nullptr){std::cout << "MySQL连接失败, 原因: " << mysql_error(_handler) << "\n";mysql_close(_handler);_handler = nullptr;return false;}return true;}~MySQLHelper(){close();}void close(){if (_handler != nullptr){mysql_close(_handler);_handler = nullptr;}}bool exec(const std::string &sql){if (0 != mysql_query(_handler, sql.c_str())){std::cout << "sql语句执行失败: " << sql << " ,原因: " << errmsg() << std::endl;return false;}return true;}std::string errmsg(){if (_handler != nullptr)return mysql_error(_handler);elsereturn "mysql句柄为空";}private:bool load(){std::ifstream ifs(_conf_file);std::string line;while (std::getline(ifs, line)){size_t pos = line.find(':');if (pos == std::string::npos){std::cout << "MySQL配置文件解析失败,某一行不符合规范: " << line << "\n";return false;}_conf_map[line.substr(0, pos)] = line.substr(pos + 1);}return true;}MYSQL *_handler;std::string _conf_file;std::unordered_map<std::string, std::string> _conf_map;
};

4.FileHelper补充

FileHelper里面又加了一个createFile接口,用来创建文件

static bool createFile(const std::string &filename)
{// 1. 先看该文件是否存在if (exists(filename))return true;// 2. 写方式打开(即创建)// 这里以追加写打开,防止多线程重入该函数导致意想不到的bug,提高代码健壮性std::ofstream ofs(filename, std::ios::app);if (!ofs.is_open()){std::cout << "创建文件失败,filename: " << filename << "\n";return false;}ofs.close();return true;
}

三、数据库系列LogSink实现

1.DBLogSink实现

class DBLogSink
{
public:using ptr = std::shared_ptr<DBLogSink>;virtual ~DBLogSink() {}virtual bool log(const LogMessage &message) = 0;// 开启事务 ->  多次insert -> 关闭事务virtual void log(const std::vector<LogMessage> &message_vec, size_t sz) = 0;
};

2.SqliteSink实现

不要忘了先创建数据库目录和文件,再用_helper打开该文件

class SqliteSink : public DBLogSink
{
public:SqliteSink(const std::string &filename): _helper(filename){// 1. 创建文件所在目录if (!FileHelper::createDir(FileHelper::getPath(filename))){std::cout << "SQLite3数据库文件所在目录创建失败\n";abort();}// 2. 创建文件if (!FileHelper::createFile(filename)){std::cout << "SQLite3数据库文件创建失败\n";abort();}// 3. 打开数据库文件if (!_helper.open()){std::cout << "SQLite3数据库文件打开失败\n";abort();}// 4. 建表if (!createTable()){std::cout << "SQLite3数据库的建表失败\n";abort();}}virtual void log(const LogMessage &message){std::ostringstream insert_sql;insert_sql << "insert into LogData(level,file,line,logger_name,thread_id,body) values(";insert_sql << "'" << LogLevel::LogLevel_Name(message._level) << "',";insert_sql << "'" << message._file << "',";insert_sql << message._line << ",";insert_sql << "'" << message._logger_name << "',";insert_sql << message._thread_id << ",";insert_sql << "'" << message._body << "');";if (!_helper.exec(insert_sql.str(), nullptr, nullptr)){std::cout << "插入数据失败,表名:LogData\n";}}// 开启事务 ->  多次insert -> 关闭事务virtual void log(const std::vector<LogMessage> &message_vec, size_t sz){if (!_helper.begin()){std::cout << "SQLlite3:开启事务失败\n";return;}for (int i = 0; i < sz; i++){log(message_vec[i]);}if (!_helper.commit()){std::cout << "SQLlite3:提交事务失败\n";return;}}private:bool createTable(){// 指明autoincrement必须要用integer,不能用int// SQLite3的current_timestamp是格林威治时间,需要加上8小时改为北京时间static std::string create_sql = R"(create table if not exists LogData(id integer primary key autoincrement,date text default(datetime(current_timestamp,'+8 hours')),level varchar(10),file varchar(32),line int,logger_name varchar(32) not null,thread_id int,body text);)";if (!_helper.exec(create_sql, nullptr, nullptr)){std::cout << "创建SQLite3数据库表失败,表名:LogData\n";return false;}return true;}SqliteHelper _helper;
};

3.MySQLSink实现

注意:这里是要解析配置文件

class MySQLSink : public DBLogSink
{
public:MySQLSink(const std::string &conf_file): _helper(conf_file){// 打开数据库文件if (!_helper.open()){std::cout << "MySQL数据库文件打开失败\n";abort();}if (!createTable()){std::cout << "MySQL数据库的建表失败\n";abort();}}virtual void log(const LogMessage &message){std::ostringstream insert_sql;insert_sql << "insert into LogData(level,file,line,logger_name,thread_id,body) values(";insert_sql << "'" << LogLevel::LogLevel_Name(message._level) << "',";insert_sql << "'" << message._file << "',";insert_sql << message._line << ",";insert_sql << "'" << message._logger_name << "',";insert_sql << message._thread_id << ",";insert_sql << "'" << message._body << "');";if (!_helper.exec(insert_sql.str())){std::cout << "插入数据失败,表名:LogData\n";}}// 开启事务 ->  多次insert -> 关闭事务virtual void log(const std::vector<LogMessage> &message_vec, size_t sz){if (!_helper.begin()){std::cout << "MySQL:开启事务失败\n";return;}for (int i = 0; i < sz; i++){log(message_vec[i]);}if (!_helper.commit()){std::cout << "MySQL:提交事务失败\n";return;}}private:bool createTable(){std::string create_sql = R"(create table if not exists LogData(id int primary key auto_increment,date datetime default current_timestamp,level varchar(10),file varchar(32),line int,logger_name varchar(32) not null,thread_id bigint,body text);)";if (!_helper.exec(create_sql)){std::cout << "创建MySQL数据库表失败,表名:LogData\n";return false;}return true;}MySQLHelper _helper;
};

4.LogSinkFactory完善

增加一个create_db即可

class LogSinkFactory
{
public:// 函数模板template <class SinkType, class... Args>static FSLogSink::ptr create_fs(Args &&...args){return std::make_shared<SinkType>(std::forward<Args>(args)...);}template <class SinkType, class... Args>static DBLogSink::ptr create_db(Args &&...args){return std::make_shared<SinkType>(std::forward<Args>(args)...);}
};

四、同步日志器的修改

1.Logger抽象类的修改

Logger需要加一个成员:std::vector<DBLogSink::ptr> _db_sinks;

同时增加一个用来进行数据库日志落地的接口:

virtual void log_db(const LogMessage &message) = 0;

并且在construct当中继续复用该LogMessage:

void construct(LogLevel::value level, const std::string &file, size_t line, char *body)
{// 1. 构造LogMessageLogMessage message(level, file, line, _logger_name, body);// 2. free掉body,否则会内存泄漏free(body);body = nullptr;// 3. 将LogMessage进行格式化std::string real_message = _formatter->format(message);// 4. 复用log进行实际的日志落地log_fs(real_message.c_str(), real_message.size());log_db(message);
}

2.SyncLogger的修改

同步日志器需要重写父类的log_db函数

virtual void log_db(const LogMessage &message)
{// 这里必须要加锁,因为存在多线程同时调用同一个日志器对象的log函数的情况std::unique_lock<std::mutex> ulock(_mutex);for (auto &sp : _db_sinks){sp->log(message);}
}

直接加锁并且复用即可

3.LoggerBuilder的修改

LoggerBuilder需要加一个 能够往_db_sinks当中添加成员的函数
并且在reset的时候还要清理那里

template <class SinkType, class... Args>
void buildDBLogSink(Args &&...args)
{_db_sinks.push_back(LogSinkFactory::create_db<SinkType>(std::forward<Args>(args)...));
}
// 允许一个建造者在调用build接口构造完对象之后将建造者当中保存的原数据进行重置
void reset()
{_logger_name.clear();_logger_type = LoggerType::SYNC;_limit_level = LogLevel::value::DEBUG;_fs_sinks.clear();_db_sinks.clear();_formatter.reset();
}

4.LocalLoggerBuilder和GlobalLoggerBuilder的修改

  1. 修改一下同步和异步日志器的构造函数,将_db_sinks进行传入
  2. 只有当对应日志器既不向文件当中落地,也不向数据库当中落地时,才会默认加上标准输出方向的落地
virtual Logger::ptr build()
{if (_logger_name.empty()){std::cout << "日志器名称为空!!\n";abort();}if (_formatter.get() == nullptr){_formatter = std::make_shared<Formatter>();}if (_fs_sinks.empty() && _db_sinks.empty()){_fs_sinks.push_back(LogSinkFactory::create_fs<StdoutSink>());}Logger::ptr ret;if (_logger_type == LoggerType::SYNC){ret = std::make_shared<SyncLogger>(_logger_name, _limit_level, _formatter, _fs_sinks, _db_sinks);}else{// 后面实现完异步日志器之后完善ret = std::make_shared<AsyncLogger>(_logger_name, _limit_level, _formatter, _fs_sinks, _db_sinks, _async_type);}// 重置this->reset();return ret;
}

五、数据库日志缓冲区的实现

1.底层容器的选择

文件日志缓冲区当中存的是vector<char>,这是可行的,因为文件是面向字节流的,日志输出时以字节为单位没毛病

而对于数据库而言,日志的落地是需要一条一条进行落地的,因此vector行不通,除非进行LogMessage的序列化和反序列化

如果序列化+反序列化,那不还是得一条一条插入吗,直接传结构体他不香吗?
折腾来折腾去,毫无意义的好吗

因此我们搞成:vector<LogMessage>

2.原初的vector?

class DBBuffer0
{
public:void push(const LogMessage &message){_buffer.push_back(message);}bool empty(){return _buffer.empty();}void swap(DBBuffer2 &buffer){_buffer.swap(buffer._buffer);}void clear(){_buffer.clear();}const std::vector<LogMessage> &getBuffer(){return _buffer;}private:std::vector<LogMessage> _buffer;
};

没啥毛病,清晰易懂,可是我们知道:vector一般的size变化是:
插入1024*1024条数据,这种扩容方式在面对海量数据时,扩容代价还是太大
在这里插入图片描述
因此,我们可以用一下reserve
单论扩容而言,reserve是比resize快的,因为resize还会初始化元素,
而LogMessage的默认构造没用,因此我们不用resize,而是选择reserve

3.具体代码

class DBBuffer
{
public:DBBuffer(size_t default_size = default_buff_size) {_buffer.reserve(default_buff_size);}void push(const LogMessage &message){if (_buffer.size() == _buffer.capacity()){_buffer.reserve(2 * _buffer.capacity());}_buffer.push_back(message);}bool empty(){return _buffer.empty();}bool full(){return _buffer.size() == _buffer.capacity();}void swap(DBBuffer &buffer){_buffer.swap(buffer._buffer);}void clear(){_buffer.clear();}size_t readableSize(){return _buffer.size();}const std::vector<LogMessage> &getBuffer(){return _buffer;}private:std::vector<LogMessage> _buffer;
};

这个bool full()是给异步安全模式使用的

六、AsyncLooper的完善

跟文件日志缓冲区类似,就是多加一份资源的事
没什么特别的,跟文件日志缓冲区那里一样的

using AsyncFSCallback = std::function<void(FSBuffer &)>;
using AsyncDBCallback = std::function<void(DBBuffer &)>;class AsyncLooper
{struct Resource{std::mutex _mutex;std::condition_variable _cond_produce;std::condition_variable _cond_consume;};
public:using ptr = std::shared_ptr<AsyncLooper>;AsyncLooper(AsyncFSCallback fs_callback,AsyncDBCallback db_callback, AsyncType async_type = AsyncType::ASYNC_SAFE): _fs_callback(fs_callback),_db_callback(db_callback), _async_type(async_type), _isrunning(true),_fs_worker(&AsyncLooper::fs_thread_routine, this),_db_worker(&AsyncLooper::db_thread_routine,this) {}~AsyncLooper(){if (_isrunning){_isrunning = false;if (_fs_worker.joinable()){_fs_resource._cond_produce.notify_all();_fs_resource._cond_consume.notify_all();_fs_worker.join();}if(_db_worker.joinable()){_db_resource._cond_produce.notify_all();_db_resource._cond_consume.notify_all();_db_worker.join();}}}void push(const char *data, size_t len){{std::unique_lock<std::mutex> ulock(_fs_resource._mutex);if (_async_type == AsyncType::ASYNC_SAFE && _fs_buffer_produce.writeableSize() < len){_fs_resource._cond_produce.wait(ulock, [this, len]() -> bool{ return !_isrunning || _fs_buffer_produce.writeableSize() >= len; });}_fs_buffer_produce.push(data, len);}// 唤醒消费者_fs_resource._cond_consume.notify_all();}void push(const LogMessage& message){{std::unique_lock<std::mutex> ulock(_db_resource._mutex);if (_async_type == AsyncType::ASYNC_SAFE && _db_buffer_produce.full()){_db_resource._cond_produce.wait(ulock, [this]() -> bool{ return !_isrunning || !_db_buffer_produce.full(); });}_db_buffer_produce.push(message);}_db_resource._cond_consume.notify_all();}private:void fs_thread_routine(){while (true){{// 当停止运行,且生产缓冲区没有数据了,才能退出if (!_isrunning && _fs_buffer_produce.empty())break;std::unique_lock<std::mutex> ulock(_fs_resource._mutex);_fs_resource._cond_consume.wait(ulock, [this]() -> bool{ return !_isrunning || !_fs_buffer_produce.empty(); });// 交换生产和消费缓冲区_fs_buffer_consume.swap(_fs_buffer_produce);}if (_async_type == AsyncType::ASYNC_SAFE){// 唤醒生产者_fs_resource._cond_produce.notify_all();}// 调用日志落地回调函数_fs_callback(_fs_buffer_consume);// 把_buffer_consume重置_fs_buffer_consume.reset();}}void db_thread_routine(){while (true){{std::unique_lock<std::mutex> ulock(_db_resource._mutex);// 当停止运行,且生产缓冲区没有数据了,才能退出if (!_isrunning && _db_buffer_produce.empty())break;_db_resource._cond_consume.wait(ulock, [this]() -> bool{ return !_isrunning || !_db_buffer_produce.empty(); });// 醒了之后即使发现当停止运行,且生产缓冲区没有数据了,也不能退, 因为停止时业务线程有可能还需要放数据// 因为二者想要醒来需要竞争锁, 因此不敢说当前生产缓冲区无数据我就退出,不行,需要等到下次循环时的判断// 交换生产和消费缓冲区_db_buffer_consume.swap(_db_buffer_produce);}if (_async_type == AsyncType::ASYNC_SAFE){// 唤醒生产者_db_resource._cond_produce.notify_all();}// 调用日志落地回调函数_db_callback(_db_buffer_consume);// 把_buffer_consume重置_db_buffer_consume.clear();}}std::atomic<bool> _isrunning;Resource _fs_resource;Resource _db_resource;AsyncFSCallback _fs_callback;AsyncDBCallback _db_callback;AsyncType _async_type;FSBuffer _fs_buffer_produce;FSBuffer _fs_buffer_consume;DBBuffer _db_buffer_produce;DBBuffer _db_buffer_consume;std::thread _fs_worker;std::thread _db_worker;
};

七、异步日志器的完善

重写父类的log_db和realDBLog即可

virtual void log_db(const LogMessage &message)
{_looper->push(message);
}void realDBLog(DBBuffer &buffer)
{// 无需加锁, 因为_looper内部本来就加锁了size_t readableSize = buffer.readableSize();for (auto &sink : _db_sinks){sink->log(buffer.getBuffer(), readableSize);}
}

八、测试

1.前置工作

const std::string sync_logger_db = "./logdb/sync.db";
const std::string async_safe_logger_db = "./logdb/async_safe.db";
const std::string async_unsafe_logger_db = "./logdb/async_unsafe.db";const std::string mysql_conf = "./mysql.conf";void init_sqlite()
{// 创建全局日志器LoggerBuilder::ptr builder = std::make_shared<GlobalLoggerBuilder>();// 1. 同步日志器builder->buildLoggerName(sync_logger_name);builder->buildLoggerType(LoggerType::SYNC);builder->buildDBLogSink<SqliteSink>(sync_logger_db);builder->build();// 2. 异步安全日志器builder->buildLoggerName(async_safe_logger_name);builder->buildLoggerType(LoggerType::ASYNC);builder->buildDBLogSink<SqliteSink>(async_safe_logger_db);builder->buildAsyncType(AsyncType::ASYNC_SAFE);builder->build();// 3. 异步不安全日志器builder->buildLoggerName(async_unsafe_logger_name);builder->buildLoggerType(LoggerType::ASYNC);builder->buildDBLogSink<SqliteSink>(async_unsafe_logger_db);builder->buildAsyncType(AsyncType::ASYNC_UNSAFE);builder->build();
}void init_mysql()
{// 创建全局日志器LoggerBuilder::ptr builder = std::make_shared<GlobalLoggerBuilder>();// 1. 同步日志器builder->buildLoggerName(sync_logger_name);builder->buildLoggerType(LoggerType::SYNC);builder->buildDBLogSink<MySQLSink>(mysql_conf);builder->build();// 2. 异步安全日志器builder->buildLoggerName(async_safe_logger_name);builder->buildLoggerType(LoggerType::ASYNC);builder->buildDBLogSink<MySQLSink>(mysql_conf);builder->buildAsyncType(AsyncType::ASYNC_SAFE);builder->build();// 3. 异步不安全日志器builder->buildLoggerName(async_unsafe_logger_name);builder->buildLoggerType(LoggerType::ASYNC);builder->buildDBLogSink<MySQLSink>(mysql_conf);builder->buildAsyncType(AsyncType::ASYNC_UNSAFE);builder->build();
}

依然还是:
const int log_num = 1024*1024; const int per_size = 100;

2.SQLite3测试

因为同步是一条一条插入的,所以磁盘IO非常频繁,因此
同步我们就给2万条就行了,实际当中不推荐用同步

1.单线程

1.同步

在这里插入图片描述
在这里插入图片描述

2.异步安全

在这里插入图片描述
在这里插入图片描述

3.异步不安全

在这里插入图片描述

2.多线程

以三个线程为例,先把数据库文件删掉

1.同步

在这里插入图片描述
在这里插入图片描述

2.异步安全

在这里插入图片描述
在这里插入图片描述

3.异步不安全

在这里插入图片描述
在这里插入图片描述

3.MySQL测试

1.单线程

1.同步

在这里插入图片描述
在这里插入图片描述
这速度,算了吧,打印2万条就行啊

2.异步安全

异步的话最多耽误业务线程10s,可是要耽误工作线程几分钟。。。
这里数据量大的时候才能看出异步的好处
在这里插入图片描述
在这里插入图片描述
然后把表删了

3.异步不安全

在这里插入图片描述
在这里插入图片描述

2.多线程

依然是以3个线程为例

1.同步

2万条
在这里插入图片描述
在这里插入图片描述

2.异步安全

在这里插入图片描述
在这里插入图片描述

3.异步不安全

在这里插入图片描述
在这里插入图片描述

以上就是日志系统扩展一:日志落地数据库:MySQL、SQLite3的全部内容哦~

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

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

相关文章

ICM20948 DMP代码详解(40)

接前一篇文章&#xff1a;ICM20948 DMP代码详解&#xff08;39&#xff09; 上一回继续解析inv_icm20948_set_slave_compass_id函数&#xff0c;解析到第5段代码inv_icm20948_setup_compass_akm函数&#xff0c;本回解析接下来的代码。为了便于理解和回顾&#xff0c;再次贴出该…

77、Python之函数式编程:一文搞懂functools模块的核心应用

引言 Python作为一种支持多范式的编程语言&#xff0c;除了在“一切皆对象”的理念支持下的&#xff0c;函数对象也是一等公民、各种高阶函数的自然实现、lambda表达式快速编写纯函数之外。还有一个内置的模块functools&#xff0c;能够更好地支持我们在Python中应用函数式编程…

企业职工薪资查询系统小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;员工管理&#xff0c;部门管理&#xff0c;工资信息管理&#xff0c;工资安排管理&#xff0c;考勤信息管理&#xff0c;交流论坛&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&#…

LTE协议栈学习

1、高通Modem架构 LTE网络架构 3、LTE协议栈 1、 NAS协议栈: EPS Mobility Management (EMM) 支持UE中的移动功能 EPS Session Management (ESM) 支持在UE和PDN网关之间建立和维护IP连接 高通平台NAS层结构 根据3GPP TS 23.122描述&#xff0c; 自动搜网顺序如下 HPLMN EH…

数据结构之线性表——LeetCode:67. 二进制求和,27. 移除元素,26. 删除有序数组中的重复项

67. 二进制求和 题目描述 67. 二进制求和 给你两个二进制字符串 a 和 b &#xff0c;以二进制字符串的形式返回它们的和。 运行代码&#xff08;javaC) class Solution {public String addBinary(String a, String b) {StringBuilder ansnew StringBuilder();int ca0;for(i…

四川财谷通信息技术有限公司与抖音小店的深度合作

在数字经济蓬勃发展的今天&#xff0c;电商平台已成为推动社会经济增长的重要引擎。其中&#xff0c;抖音小店作为短视频与电商深度融合的产物&#xff0c;凭借其庞大的用户基础、精准的流量分发机制以及创新的购物体验&#xff0c;迅速崛起为电商领域的一股不可忽视的力量。而…

CSS的表格属性

border属性 规定CSS表格边框。 table,td{border: 1px solid green;/*1px表示设置边框的大小&#xff0c;solid表示边框为实线&#xff0c;green表示边框的颜*/} border-collpapse属性 设置表格的边框是否被折叠成一个单一的边框或隔开。 table{border-collapse: collapse;} wi…

[spring]springboot日志

文章目录 一. 日志的用途二. 打印日志三. 日志框架门面模式(外观模式)SLF4J框架介绍 四. 日志格式日志级别配置日志级别日志持久化配置日志文件分割配置日志格式 五. 更简单的日志输出 一. 日志的用途 二. 打印日志 得到日志对象: 需要使用日志工厂LoggerFactory RestControl…

【避雷指南】自学AI人工智能常踩的4个大雷区

1、数学基础 学习人工智能时&#xff0c;有一种常见的误解&#xff0c;认为一定要数学学的很好&#xff0c;才能进一步学人工智能。这种观念并不正确。虽然数学是AI的基石&#xff0c;为算法和模型提供了理论基础&#xff0c;但过分沉迷于数学理论可能会让学习过程变得枯燥无味…

【第十二章:Sentosa_DSML社区版-机器学习之回归】

目录 12.1 线性回归 12.2 决策树回归 12.3 梯度提升决策树回归 12.4 保序回归 12.5 XGBoost回归 12.6 随机森林回归 12.7 广义线性回归 12.8 LightGBM回归 12.9 因子分解机回归 12.10 AdaBoost回归 12.11 KNN回归 12.12 高斯过程回归 12.13 多层感知机回归 【第十…

UML类图绘制

目录 前言 一、如何在UML中表示一个类 二、类之间关系的表示 1.继承关系 2.关联关系 ①单向关联 ②双向关联关系 ③自关联关系 3.聚合关系 4.组合关系 5.实现关系 6.依赖关系 前言 在学习面向对象语言时&#xff0c;我们可以使用UML类图来描述将要编写的程序中类与…

NASA:A-Train 云分级数据集(用于深度学习模型)

目录 简介 摘要 代码 引用 网址推荐 0代码在线构建地图应用 机器学习 A-Train 云分级数据集 简介 ATCS 是一个数据集&#xff0c;旨在训练深度学习模型&#xff0c;以便对多角度卫星图像中的云进行体积分割。 该数据集包括来自 PARASOL 任务上 POLDER 传感器的多角度偏…

docker如何升级MySQL为最新版本

今天安全扫描发现MySQL存在漏洞&#xff0c;不用想别的升级到最新版。本篇文章有两个目的&#xff0c;1&#xff09;为自己做一个记录&#xff0c;下次升级的时候不用再浪费时间查资料&#xff1b;2&#xff09;给大家一点帮助&#xff1b; 因为我是docker部署&#xff0c;所以…

在Windows系统上安装的 flatbuffers C++ 库

步骤一 下载:https://github.com/google/flatbuffers git clone gitgithub.com:google/flatbuffers.git步骤二 打开安装目录,然后再打开该目录下的powershell, 新建build目录 cd build cmake ..步骤三 进入步骤二生成的build目录里面,点击FlatBuffers.sln,打开vs2019 补充…

【巅峰算力,静谧之作】4卡4090GPU深度学习“静音”服务器

各位同仁&#xff0c;随着人工智能浪潮的汹涌澎湃&#xff0c;我们正步入一个前所未有的创新纪元。在这个充满挑战与机遇的时代&#xff0c;我愈发频繁地在工作场景中邂逅那些致力于深度学习探索的智者们。他们&#xff0c;对计算力的渴望如同对知识的追求一般&#xff0c;永无…

阿里巴巴首页pc端1688店铺招牌店铺装修教程

1688运营1688批发首页1688装修模板1688店铺怎么装修模板自定义装修代码1688店铺装修模板旺铺装修阿里店铺首页怎么装修1688店铺装修教程视频全屏通栏代码1688店铺装修模板阿里巴巴店铺装修设计 阿里巴巴首页pc端1688店铺招牌店铺装修教程 工具&#xff1a;一秒美工

海外仓与前置仓有什么不同,如何选择合适的WMS系统?

在跨境电商和国际贸易的广阔舞台上&#xff0c;海外仓与前置仓作为两种重要的物流模式&#xff0c;各自以其独特的运营方式和目标&#xff0c;为卖家和消费者提供了高效、便捷的物流服务。 1.海外仓&#xff1a;海外仓是在国外设立的存储仓库&#xff0c;主要用于存放货物并服…

【WRF工具】WRF Domain Wizard第二期:服务器中下载及安装

【WRF工具】WRF Domain Wizard第二期&#xff1a;服务器下载及安装 准备WRF Domain Wizard下载及安装WRF Domain Wizard下载WRF Domain Wizard安装添加环境变量&#xff08;为当前用户永久添加环境变量&#xff09;Java环境安装报错-Exception in thread "main" java…

从入门到精通:Spring Boot 100个技术关键词

Spring Boot 是一个基于Spring框架的快速开发框架&#xff0c;旨在简化Spring应用的初始搭建以及开发过程。通过掌握本指南中的100个关键技术关键词&#xff0c;你将逐步了解Spring Boot的核心概念、自动配置、依赖管理、Web开发、数据库操作、安全性、测试等方面的知识。每个关…

【通俗易懂介绍OAuth2.0协议以及4种授权模式】

文章目录 一.OAuth2.0协议介绍二.设计来源于生活三.关于令牌与密码的区别四.应用场景五.接下来分别简单介绍下四种授权模式吧1.客户端模式1.1 介绍1.2 适用场景1.3 时序图 2.密码模式2.1 介绍2.2 适用场景2.3时序图 3.授权码模式3.1 介绍3.2 适用场景3.3 时序图 4.简化模式4.1 …