C++ 日志系統實戰第五步:日志器的設計

全是通俗易懂的講解,如果你本節之前的知識都掌握清楚,那就速速來看我的項目筆記吧~???

本文項目代碼編寫收尾!

日志器類 (Logger) 設計(建造者模式)

????????日志器主要用于和前端交互。當我們需要使用日志系統打印 log 時,只需創建 Logger 對象,調用該對象的 debug、info、warn、error、fatal 等方法,即可輸出想打印的日志。它支持解析可變參數列表和輸出格式,能做到像使用 printf 函數一樣打印日志。

當前日志系統支持同步日志與異步日志兩種模式。兩種日志器的差異僅在于日志的落地方式:

  • 同步日志器:直接輸出日志消息。
  • 異步日志器:將日志消息放入緩沖區,由異步線程進行輸出。

因此,設計日志器類時,先設計一個 Logger 基類,在此基礎上,繼承出 SyncLogger 同步日志器和 AsyncLogger 異步日志器。

????????由于日志器模塊整合了多個模塊,創建一個日志器時,需要設置日志器名稱、日志輸出等級、日志器類型、日志輸出格式以及落地方向(落地方向可能有多個) ,整個日志器的創建過程較為復雜。為保持良好的代碼風格,編寫出優雅的代碼,日志器的創建采用建造者模式。

?

logger.hpp?

#pragma once
/*
日志器實現
1.抽象基類
2.派生出日志器具體實現類
*/
#include "level.hpp"
#include "format.hpp"
#include "sink.hpp"
#include "message.hpp"
#include "looper.hpp"
#include <atomic>
#include <mutex>
#include <cstdarg>namespace mylog
{class Logger{public:Logger(std::string logger_name, Level limit_level, FormatBuilder::ptr format_builder, std::vector<mylog::LogSink::ptr> sinks) : logger_name_(logger_name),limit_level_(limit_level),sinks_(sinks.begin(), sinks.end()),format_builder_(format_builder){}using ptr = std::shared_ptr<Logger>;// 完成構造日志消息構造過程并格式化,然后調用log函數輸出日志void debug(std::string file_, size_t line_, const std::string &fat, ...){// 1.判斷是否達到日志等級if (limit_level_ > Level::Debug){return;}// 2.對fmt字符串和不定參數進行字符串組織,得到最終的日志消息va_list args;va_start(args, fat);char *res;int len = vasprintf(&res, fat.c_str(), args); // 獲取格式化后的字符串在res中if (len < 0){return;}va_end(args);serialize(Level::Debug, file_, line_, res); // 序列化日志消息free(res);                                  // 釋放res}void info(std::string file_, size_t line_, const std::string &fat, ...){// 1.判斷是否達到日志等級if (limit_level_ > Level::Info){return;}// 2.對fmt字符串和不定參數進行字符串組織,得到最終的日志消息va_list args;va_start(args, fat);char *res;int len = vasprintf(&res, fat.c_str(), args); // 獲取格式化后的字符串在res中if (len < 0){return;}va_end(args);serialize(Level::Info, file_, line_, res); // 序列化日志消息free(res);                                 // 釋放res}void warning(std::string file_, size_t line_, const std::string &fat, ...){// 1.判斷是否達到日志等級if (limit_level_ > Level::Warning){return;}// 2.對fmt字符串和不定參數進行字符串組織,得到最終的日志消息va_list args;va_start(args, fat);char *res;int len = vasprintf(&res, fat.c_str(), args); // 獲取格式化后的字符串在res中if (len < 0){return;}va_end(args);serialize(Level::Warning, file_, line_, res); // 序列化日志消息free(res);                                    // 釋放res}void error(std::string file_, size_t line_, const std::string &fat, ...){// 1.判斷是否達到日志等級if (limit_level_ > Level::Error){return;}// 2.對fmt字符串和不定參數進行字符串組織,得到最終的日志消息va_list args;va_start(args, fat);char *res;int len = vasprintf(&res, fat.c_str(), args); // 獲取格式化后的字符串在res中if (len < 0){return;}va_end(args);serialize(Level::Error, file_, line_, res); // 序列化日志消息free(res);                                  // 釋放res}void fatal(std::string file_, size_t line_, const std::string &fat, ...){// 1.判斷是否達到日志等級if (limit_level_ > Level::Fatal){return;}// 2.對fmt字符串和不定參數進行字符串組織,得到最終的日志消息va_list args;va_start(args, fat);char *res;int len = vasprintf(&res, fat.c_str(), args); // 獲取格式化后的字符串在res中if (len < 0){return;}va_end(args);serialize(Level::Fatal, file_, line_, res); // 序列化日志消息free(res);                                  // 釋放res}protected:void serialize(Level level, std::string file, size_t line, const char *str) // 序列化日志消息{// 3.構造日志消息LogMessage message(level, file.c_str(), line, str, logger_name_.c_str());// 4.格式化日志消息std::stringstream ss;format_builder_->format(ss, message);// 5.輸出日志消息log(ss.str().c_str(), ss.str().size());}// 抽象接口完成實際的落地輸出——不同日志器實現類完成具體的落地輸出virtual void log(const char *msg, size_t line) = 0;protected:std::mutex mutex_;                       // 日志器互斥鎖std::string logger_name_;                // 日志器名稱std::atomic<Level> limit_level_;         // 日志等級限制std::vector<mylog::LogSink::ptr> sinks_; // 日志輸出器FormatBuilder::ptr format_builder_;      // 日志格式構造器};// 日志器具體實現類——同步日志器class sync_logger : public Logger{// 同步日志器,將日志直接通過落地模塊句柄進行日志落地public:sync_logger(std::string logger_name, Level limit_level, FormatBuilder::ptr format_builder, std::vector<mylog::LogSink::ptr> sinks) : Logger(logger_name, limit_level, format_builder, sinks){}void log(const char *data, size_t len) override{// 加鎖std::unique_lock<std::mutex> lock(mutex_);if (sinks_.empty()){return;}for (auto &sink : sinks_){sink->log(data, len); // 調用sink的log函數輸出日志}}};class Async_logger : public Logger{public:Async_logger(std::string logger_name, Level limit_level, FormatBuilder::ptr format_builder, std::vector<mylog::LogSink::ptr> sinks, AsyncType async_type) : Logger(logger_name, limit_level, format_builder, sinks){looper_ = std::make_shared<Async_looper>(std::bind(&Async_logger::reallog, this, std::placeholders::_1));}void log(const char *data, size_t len) override // 將數據寫入緩沖區{looper_->push(data, len);}void reallog(Buffer &buf){if (sinks_.empty()){return;}for (auto &sink : sinks_){sink->log(buf.begin(), buf.readable_size()); // 調用sink的log函數輸出日志}}private:Async_looper::ptr looper_;};/*使用建造者模式來建造日志器,而不是讓用戶直接構造日志器,這樣可以避免用戶構造錯誤的日志器,簡化用戶的使用復雜度*/enum class LoggerType{Loggertype_Sync,  // 同步日志器Loggertype_Async, // 異步日志器};// 1.抽象一個建筑者類//   1.設置日志器類型//   2.將不同類型日志器的創建放到同一個日志器建造者中完成class Logger_builder{public:Logger_builder() : type_(LoggerType::Loggertype_Sync), limit_level_(Level::Debug),async_type_(AsyncType::ASYNC_SAFE){}void buildLoggerType(LoggerType type){type_ = type;}void buildLoggerName(std::string logger_name){logger_name_ = logger_name;}void buildLimitLevel(Level limit_level){limit_level_ = limit_level;}void buildFormatBuilder(const std::string &pattern){format_builder_ = std::make_shared<FormatBuilder>(pattern);}void buildAsyncType(AsyncType async_type){async_type_ = async_type;}template <typename LogSinkType, typename... Args>void buildSinks(Args &&...args){LogSink::ptr psink = std::make_shared<LogSinkType>(std::forward<Args>(args)...);sinks_.push_back(psink);}virtual Logger::ptr build() = 0;protected:AsyncType async_type_;LoggerType type_;std::string logger_name_;Level limit_level_;FormatBuilder::ptr format_builder_;std::vector<mylog::LogSink::ptr> sinks_;};// 2.派生出具體建筑者類——局部日志器的建造者 & 全局的日志器建造者class Local_logger_builder : public Logger_builder{public:Logger::ptr build() override{assert(logger_name_.empty() == false);if (format_builder_.get() == nullptr){format_builder_ = std::make_shared<FormatBuilder>();}if (sinks_.empty()){sinks_.push_back(std::make_shared<StdoutSink>());}if (type_ == LoggerType::Loggertype_Async){return std::make_shared<Async_logger>(logger_name_,limit_level_, format_builder_, sinks_, async_type_);}return std::make_shared<sync_logger>(logger_name_, limit_level_, format_builder_, sinks_);}};
}

?只有在交換的時候,生產者和消費者才會產生一次鎖沖突

?

?buffer.hpp

#pragma once/*實現異步日志緩沖區*/
#include "util.hpp"
#include <vector>
#include <cassert>namespace mylog
{
#define BUFFER_SIZE (1 * 1024 * 1024)
#define THRESHOLD_BUFFER_SIZE (8 * 1024 * 1024) // 內存閾值,沒有超過閾值,內存翻倍擴容,達到閾值后,內存線性增長
#define INCREASE_BUFFER_SIZE (1 * 1024 * 1024)  // 增加緩沖區大小class Buffer{private:std::vector<char> buffer_; // 緩沖區size_t reader_index;       // 當前可讀的位置size_t writer_index;       // 當前可寫的位置private:void move_writer(size_t len) // 移動寫指針{assert(len + writer_index <= writeable_size());writer_index += len;}void ensure_Enough_capacity(size_t len) // 確保緩沖區有足夠的空間容納len字節{if (len <= writeable_size()) // 如果寫入數據超過可寫空間,不需要擴容{return;}else // 如果寫入數據超過可寫空間,需要擴容{size_t new_size = 0;while (new_size < len){if (buffer_.size() < THRESHOLD_BUFFER_SIZE){new_size = buffer_.size() * 2; // 小于閾值,翻倍增長}else{new_size = buffer_.size() + INCREASE_BUFFER_SIZE; // 大于閾值,線性增長}buffer_.resize(new_size); // 擴容}}}public:Buffer() : buffer_(BUFFER_SIZE), reader_index(0), writer_index(0) {}void push(const char *data, size_t len) // 寫入數據{// 當前可寫空間不足//  if(len>writeable_size()) //如果寫入數據超過可寫空間//  {//      return;//1.直接返回//  }// 2.動態空間,用于極限性能測試——擴容ensure_Enough_capacity(len);// 寫入數據std::copy(data, data + len, &buffer_[writer_index]);// 將寫入位置后移len個字節move_writer(len);}const char *begin() // 返回緩沖區起始位置{return &buffer_[reader_index];}size_t readable_size() // 返回可讀字節數{return writer_index - reader_index;}size_t writeable_size() // 返回可寫字節數{// 對于擴容思路來說,不存在可寫空間大小,業務總是可寫return buffer_.size() - writer_index;}void move_reader(size_t len) // 移動讀指針{assert(len <= readable_size());reader_index += len;}void reset() // 重置緩沖區{reader_index = 0;writer_index = 0;}void swap(Buffer &other) // 交換緩沖區{buffer_.swap(other.buffer_);std::swap(reader_index, other.reader_index);std::swap(writer_index, other.writer_index);}bool empty() // 判斷緩沖區是否為空{return reader_index == writer_index;}};
}

雙緩沖區異步任務處理器(AsyncLooper)設計

  • 設計思想:異步處理線程 + 數據池
    • 使用者將需要完成的任務添加到任務池中,由異步線程來完成任務的實際執行操作。
  • 任務池的設計思想:雙緩沖區阻塞數據池
  • 優勢
    • 避免了空間的頻繁申請釋放。
    • 盡可能的減少了生產者與消費者之間鎖沖突的概率,提高了任務處理效率。

????????在任務池的設計中,有很多備選方案,比如循環隊列等等。但是不管是哪一種都會涉及到鎖沖突的情況,因為在生產者與消費者模型中,任何兩個角色之間都具有互斥關系,因此每一次的任務添加與取出都有可能涉及鎖的沖突。

????????而雙緩沖區不同,雙緩沖區是處理器將一個緩沖區中的任務全部處理完畢后,然后交換兩個緩沖區,重新對新的緩沖區中的任務進行處理。雖然同時多線程寫入也會沖突,但是沖突并不會像每次只處理一條的時候頻繁(減少了生產者與消費者之間的鎖沖突),且不涉及到空間的頻繁申請釋放所帶來的消耗。

0

?looper.hpp

/*異步器*/
#pragma once#include "buffer.hpp"
#include <condition_variable>
#include <mutex>
#include <thread>
#include <functional>
#include <memory>
#include <atomic>namespace mylog
{using Functor = std::function<void(Buffer &)>;enum class AsyncType{ASYNC_SAFE,  // 安全狀態,表示緩沖區滿了則阻塞,防止內存資源耗盡ASYNC_UNSAFE // 非安全狀態};class Async_looper{public:using ptr = std::shared_ptr<Async_looper>;Async_looper(const Functor &callback, AsyncType type = AsyncType::ASYNC_SAFE) : stop_(false), thread_(std::thread(&Async_looper::threadEntry, this)), callback_(callback), type_(type) {}void stop(){stop_ = true;con_cv_.notify_all(); // 通知消費者線程退出thread_.join();}~Async_looper(){stop();}void push(const char *data, size_t len){// 1.無線擴容——非安全; 2.固定大小——生產緩沖區滿后,生產者線程阻塞;std::unique_lock<std::mutex> lock(mutex_);// 條件變量控制,若緩沖區剩余大小大于數據長度,可以添加數據,否則等待if (type_ == AsyncType::ASYNC_SAFE)pro_cv_.wait(lock, [&](){ return pro_buffer_.writeable_size() >= len; });// 添加數據到緩沖區pro_buffer_.push(data, len);// 通知消費者線程有數據可消費if (type_ == AsyncType::ASYNC_SAFE)con_cv_.notify_all();}private:// 線程入口函數——對消費緩沖區的數據進行處理,處理完畢后,初始化緩沖區void threadEntry(){while (true){// 1.判斷生產緩沖區有沒有數據,有就交換; .無數據,等待;{std::unique_lock<std::mutex> lock(mutex_);con_cv_.wait(lock, [&](){ return stop_ || !pro_buffer_.empty(); });// 如果停止且生產者緩沖區為空則退出if (stop_ && pro_buffer_.empty())break;// 1.交換生產者緩沖區和消費者緩沖區con_buffer_.swap(pro_buffer_);// 2.通知生產者線程可以繼續添加數據if (type_ == AsyncType::ASYNC_SAFE)pro_cv_.notify_all();}// 3.被喚醒后,對消費緩沖區進行數據處理callback_(con_buffer_);// 4.初始化消費者緩沖區con_buffer_.reset();}}private:Functor callback_; // 具體對緩沖區數據進行操作的回調函數private:AsyncType type_;std::atomic<bool> stop_;Buffer pro_buffer_;              // 生產者緩沖區Buffer con_buffer_;              // 消費者緩沖區std::mutex mutex_;               // 互斥鎖std::condition_variable pro_cv_; // 生產者條件變量std::condition_variable con_cv_; // 消費者條件變量std::thread thread_;             // 異步線程};
}
class Async_logger : public Logger{public:Async_logger(std::string logger_name, Level limit_level, FormatBuilder::ptr format_builder, std::vector<mylog::LogSink::ptr> sinks,AsyncType async_type) : Logger(logger_name, limit_level, format_builder, sinks){looper_ = std::make_shared<Async_looper>(std::bind(&Async_logger::reallog, this, std::placeholders::_1));}void log(const char *data, size_t len) override//將數據寫入緩沖區{looper_->push(data, len);}void reallog(Buffer &buf){if(sinks_.empty()){return;}for(auto &sink : sinks_){sink->log(buf.begin(), buf.writeable_size()); // 調用sink的log函數輸出日志}}private:Async_looper::ptr looper_;};

單例日志器管理類設計(單例模式)

????????日志的輸出,我們希望能夠在任意位置都可以進行,但是當我們創建了一個日志器之后,就會受到日志器所在作用域的訪問屬性限制。

????????因此,為了突破訪問區域的限制,我們創建一個日志器管理類,且這個類是一個單例類,這樣的話,我們就可以在任意位置來通過管理器單例獲取到指定的日志器來進行日志輸出了。

????????基于單例日志器管理器的設計思想,我們對于日志器建造者類進行繼承,繼承出一個全局日志器建造者類,實現一個日志器在創建完畢后,直接將其添加到單例的日志器管理器中,以便于能夠在任何位置通過日志器名稱能夠獲取到指定的日志器進行日志輸出。

?在logger.hpp里面

class LoggerManager{private:std::mutex mutex_;Logger::ptr root_logger_;                              // 默認日志器std::unordered_map<std::string, Logger::ptr> loggers_; // 日志器名稱-日志器映射表private:LoggerManager(){std::unique_ptr<mylog::Local_logger_builder> builder(new mylog::Local_logger_builder());builder->buildLoggerName("root");root_logger_ = builder->build();addLogger(root_logger_);}public:static LoggerManager &getInstance(){// 在C++11之后,針對靜態變量,編譯器在編譯的層面實現了線程安全// 當靜態局部變量在沒有構造完成之前,其他線程進入就會阻塞static LoggerManager eton;return eton;}void addLogger(Logger::ptr logger){if (hasLogger(logger->get_logger_name()))return;std::unique_lock<std::mutex> lock(mutex_);loggers_.insert(std::make_pair(logger->get_logger_name(), logger));}bool hasLogger(const std::string &name){std::unique_lock<std::mutex> lock(mutex_);auto it = loggers_.find(name);if (it != loggers_.end()){return true;}return false;}Logger::ptr getLogger(const std::string &name){std::unique_lock<std::mutex> lock(mutex_);auto it = loggers_.find(name);if (it != loggers_.end()){return it->second;}return nullptr;}Logger::ptr rootLogger(){return root_logger_;}};

日志宏 & 全局接口設計(代理模式)

  • 提供全局的日志器獲取接口。
  • 使用代理模式,通過全局函數或宏函數來代理 Logger 類的 log、debug、info、warn、error、fatal 等接口,以便于控制源碼文件名稱和行號的輸出控制,簡化用戶操作。
  • 當僅需標準輸出日志的時候,可以通過主日志器來打印日志。且操作時只需要通過宏函數直接進行輸出即可。

mylog.h

#pragma once#include"logger.hpp"namespace mylog
{Logger::ptr get_logger(const std::string &name){return mylog::LoggerManager::getInstance().getLogger(name);}Logger::ptr rootLogger(){return mylog::LoggerManager::getInstance().rootLogger();}#define debug(fmt, ...) debug(__FILE__, __LINE__, fmt, ##__VA_ARGS__)#define info(fmt, ...) info(__FILE__, __LINE__, fmt, ##__VA_ARGS__)#define warn(fmt, ...) warn(__FILE__, __LINE__, fmt, ##__VA_ARGS__)#define error(fmt, ...) error(__FILE__, __LINE__, fmt, ##__VA_ARGS__)#define fatal(fmt, ...) fatal(__FILE__, __LINE__, fmt, ##__VA_ARGS__)#define DEBUG(fmt, ...) mylog::rootLogger()->debug(fmt, ##__VA_ARGS__)#define INFO(fmt, ...) mylog::rootLogger()->info(fmt, ##__VA_ARGS__)#define WARN(fmt, ...) mylog::rootLogger()->warn(fmt, ##__VA_ARGS__)#define ERROR(fmt, ...) mylog::rootLogger()->error(fmt, ##__VA_ARGS__)#define FATAL(fmt, ...) mylog::rootLogger()->fatal(fmt, ##__VA_ARGS__)
}

后續還有測試代碼,期待共同實現~

??如果你對日志系統感到興趣,歡迎關注我👉【A charmer】

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/86778.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/86778.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/86778.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Spring Boot + MyBatis日志前綴清除方法

在 Spring Boot 結合 MyBatis 的應用中&#xff0c;清空日志前綴&#xff08;如 > 、< 等&#xff09;需要通過 自定義 MyBatis 的日志實現 或 修改日志模板 來實現。以下是兩種常用方法&#xff1a; 方法 1&#xff1a;自定義 MyBatis 日志實現&#xff08;推薦&#xf…

【消息隊列】——如何實現消息保序

目錄 一、哪些場景需要消息保序?二、如何實現消息保序?三、保序消息的常見問題和應對策略3.1、重復消息3.2、節點故障3.3、分區擴容四、小結本文來源:極客時間vip課程筆記 一、哪些場景需要消息保序? 消息保序問題指的是,在通過消息中間件傳遞消息過程中,我們希望消費者收…

Transformer模型詳解

Transformer Transformer真是個細節滿滿的框架呢&#xff0c;大三讀到根本不敢看&#xff0c;考研復試前看了看&#xff0c;以為懂了其實差得還遠&#xff0c;兩個多月前看了&#xff0c;還是一知半解&#xff0c;如今終于經過細細分析&#xff0c;算是知道了Transformer的基本…

火山引擎發布豆包大模型 1.6 與視頻生成模型 Seedance 1.0 pro

6 月 11 日&#xff0c;在火山引擎 FORCE 原動力大會上&#xff0c;字節跳動旗下火山引擎正式發布豆包大模型 1.6、豆包?視頻生成模型 Seedance 1.0 pro、豆包?語音播客模型&#xff0c;豆包?實時語音模型也在火山引擎全量上線&#xff0c;豆包大模型家族已成為擁有全模態、…

PH熱榜 | 2025-06-12

1. Atlas 標語&#xff1a;幾秒鐘內了解定價情況 介紹&#xff1a;獲取即插即用的定價頁面&#xff0c;讓你輕松賺錢&#xff0c;不再辛苦操勞。 產品網站&#xff1a; 立即訪問 Product Hunt&#xff1a; View on Product Hunt 關鍵詞&#xff1a;Atlas, 定價快速, 插件式…

ChatGPT革命升級!o3-pro模型重磅發布:開啟AI推理新紀元

2025年6月10日&#xff0c;OpenAI以一場低調而震撼的發布&#xff0c;正式推出了新一代推理模型o3-pro&#xff0c;這標志著人工智能在復雜問題解決領域的重大突破。作為ChatGPT Pro和Team訂閱用戶的專屬工具&#xff0c;o3-pro不僅重新定義了AI的可靠性標準&#xff0c;更以其…

NVIDIA Isaac GR00T N1.5 適用于 LeRobot SO-101 機械臂

系列文章目錄 目錄 系列文章目錄 前言 一、簡介 二、詳細教程 2.1 數據集準備 2.1.1 創建或下載您的數據集 2.1.2 配置模態文件 2.2 模型微調 2.3 開環評估 2.4 部署 &#x1f389; 快樂編程&#xff01;&#x1f4bb;&#x1f6e0;? 立即開始&#xff01; 前言 一…

【編譯工具】(自動化)自動化測試工具:如何讓我的開發效率提升300%并保證代碼質量?

目錄 引言&#xff1a;自動化測試在現代開發中的關鍵作用 一、自動化測試金字塔&#xff1a;構建高效的測試策略 &#xff08;1&#xff09;測試金字塔模型 &#xff08;2&#xff09;各層級代表工具 二、前端自動化測試實戰&#xff1a;Jest Cypress &#xff08;1&…

R語言緩釋制劑QBD解決方案之一

本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》緩釋制劑包衣處方研究的R語言解決方案。 ER聚合物包衣處方優化研究 基于初步風險評估和初始可行性研究&#xff0c;進行帶3個中心點的24-1分式析因DOE。藥物的釋放被識別為CQA。本研究的…

行為模式-命令模式

定義&#xff1a; 命令模式是一個高內聚的模式&#xff0c;其定義為&#xff1a;Encapsulate a request as an object,thereby letting you parameterize clients with different requests,queue or log requests,and support undoable operations.&#xff08;將一個請求封裝成…

Ubuntu 24.04 上安裝與 Docker 部署 Sentinel

Ubuntu 24.04 上安裝與 Docker 部署 Sentinel 一、Sentinel 簡介 Sentinel 是阿里巴巴開源的分布式系統流量控制組件&#xff0c;提供流量控制、熔斷降級和系統負載保護等功能。它通過可視化控制臺&#xff08;Dashboard&#xff09;實現實時監控和規則管理&#xff0c;是微服…

IP 地址查詢在證券交易中的應用方式

網絡安全保障與IP地址查詢 證券交易平臺存儲著海量投資者的敏感信息以及巨額資金的交易數據&#xff0c;是網絡攻擊的重點目標。IP 地址查詢在檢測異常登錄行為方面至關重要。例如&#xff0c;當一個賬戶短時間內先在國內某城市登錄&#xff0c;隨后又在境外 IP 地址發起交易操…

Flutter 常用組件詳解:Text、Button、Image、ListView 和 GridView

Flutter 作為 Google 推出的跨平臺 UI 框架&#xff0c;憑借其高效的渲染性能和豐富的組件庫&#xff0c;已經成為移動應用開發的熱門選擇。本文將深入探討 Flutter 中最常用的五個基礎組件&#xff1a;Text、Button、Image、ListView 和 GridView&#xff0c;幫助開發者快速掌…

docker 單機部署redis集群(一)

docker 部署redis集群 1、創建redis網卡 docker network create redis --subnet 172.38.0.0/16查看網卡信息 docker network ls docker network inspect redis2、創建redis配置 #使用腳本創建6個redis配置for port in $(seq

MySQL 索引學習筆記

1.二叉樹&#xff0c;紅黑樹&#xff0c;B 樹&#xff0c;B樹 二叉樹&#xff1a;就是每個節點最多只能有兩個子節點的樹&#xff1b; 紅黑樹&#xff1a;就是自平衡二叉搜索樹&#xff0c;紅黑樹通過一下五個規則構建&#xff1a; 1.節點只能是紅色或黑色&#xff1b; 2.根…

Windows安裝docker及使用

下載 https://www.docker.com/ 安裝 啟動 此時拉取鏡像會報錯 Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers) 配置引擎 添加以…

多參表達式Hive UDF

支持的操作符 &#xff1a;跳過&#xff0c;即無條件篩選&#xff1a;等于!&#xff1a;不等于range&#xff1a;區間內&#xff0c;range[n,m]表示 between n and mnrange&#xff1a;區間外&#xff0c;即not between andin&#xff1a;集合內&#xff0c;in(n,m,j,k)表示 in…

GO后端開發內存管理及參考答案

什么是 Go 的逃逸分析&#xff08;Escape Analysis&#xff09;&#xff0c;為什么需要它&#xff1f; Go 的逃逸分析是一種編譯時技術&#xff0c;用于確定變量的生命周期是否超出其創建的函數作用域。通過分析變量的使用方式&#xff0c;編譯器能夠判斷變量是否需要在堆上分…

未來智能系統演進路線:從AGI到ASI的技術藍圖

引言&#xff1a;智能革命的下一個十年 在AI技術突破性發展的當下&#xff0c;我們正站在通用人工智能&#xff08;AGI&#xff09;向人工超級智能&#xff08;ASI&#xff09;躍遷的關鍵轉折點。本文將系統解析未來3-10年的技術演進路徑&#xff0c;通過模塊化組件插件&#…

eNSP-Cloud(實現本地電腦與eNSP內設備之間通信)

說明&#xff1a; 想象一下&#xff0c;你正在用eNSP搭建一個虛擬的網絡世界&#xff0c;里面有虛擬的路由器、交換機、電腦&#xff08;PC&#xff09;等等。這些設備都在你的電腦里面“運行”&#xff0c;它們之間可以互相通信&#xff0c;就像一個封閉的小王國。 但是&#…