log_reader 架构解析:嵌入式日志检索引擎的设计之道
一、写在前面
在嵌入式设备、工业网关或任何资源敏感型系统的开发中,日志检索往往是一个令人头疼的难题:
- 存储受限:Flash 容量有限,无法像服务器那样存放数百 GB 的日志;
- 掉电风险:设备随时可能断电,日志系统必须具备自动恢复能力;
- 检索需求 :故障定位时要求快速精准,不可能逐行
grep。
今天要介绍的 log_reader,是 microLog 项目中专司日志读取与检索的模块。它通过一套精心设计的架构,在嵌入式资源约束 与检索效率之间找到了恰到好处的平衡。
二、整体架构:分层设计
先看全景图:
#mermaid-svg-bB4gZWU1dCJXxN51{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bB4gZWU1dCJXxN51 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bB4gZWU1dCJXxN51 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bB4gZWU1dCJXxN51 .error-icon{fill:#552222;}#mermaid-svg-bB4gZWU1dCJXxN51 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bB4gZWU1dCJXxN51 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bB4gZWU1dCJXxN51 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bB4gZWU1dCJXxN51 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bB4gZWU1dCJXxN51 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bB4gZWU1dCJXxN51 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bB4gZWU1dCJXxN51 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bB4gZWU1dCJXxN51 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bB4gZWU1dCJXxN51 .marker.cross{stroke:#333333;}#mermaid-svg-bB4gZWU1dCJXxN51 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bB4gZWU1dCJXxN51 p{margin:0;}#mermaid-svg-bB4gZWU1dCJXxN51 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-bB4gZWU1dCJXxN51 .cluster-label text{fill:#333;}#mermaid-svg-bB4gZWU1dCJXxN51 .cluster-label span{color:#333;}#mermaid-svg-bB4gZWU1dCJXxN51 .cluster-label span p{background-color:transparent;}#mermaid-svg-bB4gZWU1dCJXxN51 .label text,#mermaid-svg-bB4gZWU1dCJXxN51 span{fill:#333;color:#333;}#mermaid-svg-bB4gZWU1dCJXxN51 .node rect,#mermaid-svg-bB4gZWU1dCJXxN51 .node circle,#mermaid-svg-bB4gZWU1dCJXxN51 .node ellipse,#mermaid-svg-bB4gZWU1dCJXxN51 .node polygon,#mermaid-svg-bB4gZWU1dCJXxN51 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bB4gZWU1dCJXxN51 .rough-node .label text,#mermaid-svg-bB4gZWU1dCJXxN51 .node .label text,#mermaid-svg-bB4gZWU1dCJXxN51 .image-shape .label,#mermaid-svg-bB4gZWU1dCJXxN51 .icon-shape .label{text-anchor:middle;}#mermaid-svg-bB4gZWU1dCJXxN51 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bB4gZWU1dCJXxN51 .rough-node .label,#mermaid-svg-bB4gZWU1dCJXxN51 .node .label,#mermaid-svg-bB4gZWU1dCJXxN51 .image-shape .label,#mermaid-svg-bB4gZWU1dCJXxN51 .icon-shape .label{text-align:center;}#mermaid-svg-bB4gZWU1dCJXxN51 .node.clickable{cursor:pointer;}#mermaid-svg-bB4gZWU1dCJXxN51 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bB4gZWU1dCJXxN51 .arrowheadPath{fill:#333333;}#mermaid-svg-bB4gZWU1dCJXxN51 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bB4gZWU1dCJXxN51 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bB4gZWU1dCJXxN51 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bB4gZWU1dCJXxN51 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bB4gZWU1dCJXxN51 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bB4gZWU1dCJXxN51 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bB4gZWU1dCJXxN51 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bB4gZWU1dCJXxN51 .cluster text{fill:#333;}#mermaid-svg-bB4gZWU1dCJXxN51 .cluster span{color:#333;}#mermaid-svg-bB4gZWU1dCJXxN51 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bB4gZWU1dCJXxN51 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bB4gZWU1dCJXxN51 rect.text{fill:none;stroke-width:0;}#mermaid-svg-bB4gZWU1dCJXxN51 .icon-shape,#mermaid-svg-bB4gZWU1dCJXxN51 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bB4gZWU1dCJXxN51 .icon-shape p,#mermaid-svg-bB4gZWU1dCJXxN51 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bB4gZWU1dCJXxN51 .icon-shape .label rect,#mermaid-svg-bB4gZWU1dCJXxN51 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bB4gZWU1dCJXxN51 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bB4gZWU1dCJXxN51 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bB4gZWU1dCJXxN51 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 存储层
优化层
索引层 - search_index_itfc
核心层 - log_reader
应用层
命令行工具 log_reader
C++ API
log_reader
统一读取入口
热词缓存
hot_word_cache
索引抽象接口
memory_search_index
内存索引
sqlite3_search_index
数据库索引
file_search_index
文件倒排索引
布隆过滤器
bloom_filter
前缀树
trie
分词器
text_segmenter
tinySeqFile
环形文件引擎
.dict 索引文件
.posting 倒排列表
设计哲学:
- 接口抽象:索引层提供统一接口,底层实现可插拔替换;
- 分层优化:布隆过滤器 → 前缀树 → 倒排索引,逐层加速;
- 资源友好:文件索引配合 mmap,内存占用极低。
三、核心模块详解
3.1 log_reader:统一读取入口
log_reader 是整个系统的门面,对外提供简洁明了的 API:
cpp
// 打开日志文件
log_reader reader;
reader.open("./app.log");
// 设置加密(可选)
reader.set_crypto(emCryptoType::AES256, key, iv);
// 构建搜索索引
reader.build_search_index();
// 搜索关键词
vector<uint64_t> indices;
reader.search("error", indices);
// 获取完整记录
vector<stRecord> results;
reader.search_records("warning", results);
关键设计要点:
-
Pimpl 模式 :实现细节隐藏在
impl结构体中,头文件保持整洁。cppstruct impl; // 前向声明 impl* p_impl_; // 实现指针 -
三种读取模式:
- TERMINAL:直接打印输出,适合调试;
- MEMORY:将日志加载到内存,支持随机访问;
- TEXT:纯文本导出,便于外部处理。
-
加密透明:读取时自动解密,上层完全无感知。
搜索索引 tinySeqFile log_reader 应用层 搜索索引 tinySeqFile log_reader 应用层 #mermaid-svg-uZ2YjB09NmBxvHYY{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-uZ2YjB09NmBxvHYY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-uZ2YjB09NmBxvHYY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-uZ2YjB09NmBxvHYY .error-icon{fill:#552222;}#mermaid-svg-uZ2YjB09NmBxvHYY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-uZ2YjB09NmBxvHYY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-uZ2YjB09NmBxvHYY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-uZ2YjB09NmBxvHYY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-uZ2YjB09NmBxvHYY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-uZ2YjB09NmBxvHYY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-uZ2YjB09NmBxvHYY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-uZ2YjB09NmBxvHYY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-uZ2YjB09NmBxvHYY .marker.cross{stroke:#333333;}#mermaid-svg-uZ2YjB09NmBxvHYY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-uZ2YjB09NmBxvHYY p{margin:0;}#mermaid-svg-uZ2YjB09NmBxvHYY .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-uZ2YjB09NmBxvHYY text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-uZ2YjB09NmBxvHYY .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-uZ2YjB09NmBxvHYY .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-uZ2YjB09NmBxvHYY .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-uZ2YjB09NmBxvHYY .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-uZ2YjB09NmBxvHYY #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-uZ2YjB09NmBxvHYY .sequenceNumber{fill:white;}#mermaid-svg-uZ2YjB09NmBxvHYY #sequencenumber{fill:#333;}#mermaid-svg-uZ2YjB09NmBxvHYY #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-uZ2YjB09NmBxvHYY .messageText{fill:#333;stroke:none;}#mermaid-svg-uZ2YjB09NmBxvHYY .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-uZ2YjB09NmBxvHYY .labelText,#mermaid-svg-uZ2YjB09NmBxvHYY .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-uZ2YjB09NmBxvHYY .loopText,#mermaid-svg-uZ2YjB09NmBxvHYY .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-uZ2YjB09NmBxvHYY .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-uZ2YjB09NmBxvHYY .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-uZ2YjB09NmBxvHYY .noteText,#mermaid-svg-uZ2YjB09NmBxvHYY .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-uZ2YjB09NmBxvHYY .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-uZ2YjB09NmBxvHYY .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-uZ2YjB09NmBxvHYY .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-uZ2YjB09NmBxvHYY .actorPopupMenu{position:absolute;}#mermaid-svg-uZ2YjB09NmBxvHYY .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-uZ2YjB09NmBxvHYY .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-uZ2YjB09NmBxvHYY .actor-man circle,#mermaid-svg-uZ2YjB09NmBxvHYY line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-uZ2YjB09NmBxvHYY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} loop 每条日志 open("./app.log") 解析文件头 返回配置信息 初始化索引 build_search_index() 读取所有日志 返回日志内容 添加到索引 search("error") 查询关键词 返回匹配索引 根据索引读取日志 返回完整内容 返回搜索结果
3.2 搜索索引:策略模式的优雅实现
索引层定义了统一接口 search_index_itfc,并提供了三种可选的实现:
#mermaid-svg-K9zKPyaYEGtuOlez{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-K9zKPyaYEGtuOlez .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-K9zKPyaYEGtuOlez .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-K9zKPyaYEGtuOlez .error-icon{fill:#552222;}#mermaid-svg-K9zKPyaYEGtuOlez .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-K9zKPyaYEGtuOlez .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-K9zKPyaYEGtuOlez .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-K9zKPyaYEGtuOlez .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-K9zKPyaYEGtuOlez .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-K9zKPyaYEGtuOlez .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-K9zKPyaYEGtuOlez .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-K9zKPyaYEGtuOlez .marker{fill:#333333;stroke:#333333;}#mermaid-svg-K9zKPyaYEGtuOlez .marker.cross{stroke:#333333;}#mermaid-svg-K9zKPyaYEGtuOlez svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-K9zKPyaYEGtuOlez p{margin:0;}#mermaid-svg-K9zKPyaYEGtuOlez .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-K9zKPyaYEGtuOlez .cluster-label text{fill:#333;}#mermaid-svg-K9zKPyaYEGtuOlez .cluster-label span{color:#333;}#mermaid-svg-K9zKPyaYEGtuOlez .cluster-label span p{background-color:transparent;}#mermaid-svg-K9zKPyaYEGtuOlez .label text,#mermaid-svg-K9zKPyaYEGtuOlez span{fill:#333;color:#333;}#mermaid-svg-K9zKPyaYEGtuOlez .node rect,#mermaid-svg-K9zKPyaYEGtuOlez .node circle,#mermaid-svg-K9zKPyaYEGtuOlez .node ellipse,#mermaid-svg-K9zKPyaYEGtuOlez .node polygon,#mermaid-svg-K9zKPyaYEGtuOlez .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-K9zKPyaYEGtuOlez .rough-node .label text,#mermaid-svg-K9zKPyaYEGtuOlez .node .label text,#mermaid-svg-K9zKPyaYEGtuOlez .image-shape .label,#mermaid-svg-K9zKPyaYEGtuOlez .icon-shape .label{text-anchor:middle;}#mermaid-svg-K9zKPyaYEGtuOlez .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-K9zKPyaYEGtuOlez .rough-node .label,#mermaid-svg-K9zKPyaYEGtuOlez .node .label,#mermaid-svg-K9zKPyaYEGtuOlez .image-shape .label,#mermaid-svg-K9zKPyaYEGtuOlez .icon-shape .label{text-align:center;}#mermaid-svg-K9zKPyaYEGtuOlez .node.clickable{cursor:pointer;}#mermaid-svg-K9zKPyaYEGtuOlez .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-K9zKPyaYEGtuOlez .arrowheadPath{fill:#333333;}#mermaid-svg-K9zKPyaYEGtuOlez .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-K9zKPyaYEGtuOlez .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-K9zKPyaYEGtuOlez .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-K9zKPyaYEGtuOlez .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-K9zKPyaYEGtuOlez .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-K9zKPyaYEGtuOlez .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-K9zKPyaYEGtuOlez .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-K9zKPyaYEGtuOlez .cluster text{fill:#333;}#mermaid-svg-K9zKPyaYEGtuOlez .cluster span{color:#333;}#mermaid-svg-K9zKPyaYEGtuOlez div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-K9zKPyaYEGtuOlez .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-K9zKPyaYEGtuOlez rect.text{fill:none;stroke-width:0;}#mermaid-svg-K9zKPyaYEGtuOlez .icon-shape,#mermaid-svg-K9zKPyaYEGtuOlez .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-K9zKPyaYEGtuOlez .icon-shape p,#mermaid-svg-K9zKPyaYEGtuOlez .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-K9zKPyaYEGtuOlez .icon-shape .label rect,#mermaid-svg-K9zKPyaYEGtuOlez .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-K9zKPyaYEGtuOlez .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-K9zKPyaYEGtuOlez .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-K9zKPyaYEGtuOlez :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} search_index_itfc
抽象接口
memory_search_index
内存索引
sqlite3_search_index
SQLite索引
file_search_index
文件索引
优点: 最快
缺点: 内存占用高
适用: 临时查询
优点: 持久化
缺点: 依赖SQLite
适用: 长期存储
优点: 低内存
缺点: 构建慢
适用: 嵌入式
工厂模式创建索引:
cpp
search_index_itfc* create_index(emIndexType type) {
switch (type) {
case emIndexType::MEMORY:
return new memory_search_index();
case emIndexType::SQLITE3:
return new sqlite3_search_index();
case emIndexType::FILE:
return new file_search_index();
}
}
性能对比一览:
| 索引类型 | 检索速度 | 内存占用 | 持久化 | 适用场景 |
|---|---|---|---|---|
| MEMORY | 最快(微秒级) | 高(全内存) | 否 | 临时查询、开发调试 |
| SQLITE3 | 中等(毫秒级) | 中等 | 是 | 单机应用、长期存储 |
| FILE | 快(毫秒级) | 低(mmap) | 是 | 嵌入式、资源受限 |
3.3 文件索引:倒排索引的工程实现
这是最核心的部分,下面重点展开。
什么是倒排索引?
正排索引 :文档 ID → 文档内容
倒排索引:关键词 → 文档 ID 列表
#mermaid-svg-ojoAbmZ7NFE5DTLg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ojoAbmZ7NFE5DTLg .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ojoAbmZ7NFE5DTLg .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ojoAbmZ7NFE5DTLg .error-icon{fill:#552222;}#mermaid-svg-ojoAbmZ7NFE5DTLg .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ojoAbmZ7NFE5DTLg .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ojoAbmZ7NFE5DTLg .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ojoAbmZ7NFE5DTLg .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ojoAbmZ7NFE5DTLg .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ojoAbmZ7NFE5DTLg .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ojoAbmZ7NFE5DTLg .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ojoAbmZ7NFE5DTLg .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ojoAbmZ7NFE5DTLg .marker.cross{stroke:#333333;}#mermaid-svg-ojoAbmZ7NFE5DTLg svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ojoAbmZ7NFE5DTLg p{margin:0;}#mermaid-svg-ojoAbmZ7NFE5DTLg .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ojoAbmZ7NFE5DTLg .cluster-label text{fill:#333;}#mermaid-svg-ojoAbmZ7NFE5DTLg .cluster-label span{color:#333;}#mermaid-svg-ojoAbmZ7NFE5DTLg .cluster-label span p{background-color:transparent;}#mermaid-svg-ojoAbmZ7NFE5DTLg .label text,#mermaid-svg-ojoAbmZ7NFE5DTLg span{fill:#333;color:#333;}#mermaid-svg-ojoAbmZ7NFE5DTLg .node rect,#mermaid-svg-ojoAbmZ7NFE5DTLg .node circle,#mermaid-svg-ojoAbmZ7NFE5DTLg .node ellipse,#mermaid-svg-ojoAbmZ7NFE5DTLg .node polygon,#mermaid-svg-ojoAbmZ7NFE5DTLg .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ojoAbmZ7NFE5DTLg .rough-node .label text,#mermaid-svg-ojoAbmZ7NFE5DTLg .node .label text,#mermaid-svg-ojoAbmZ7NFE5DTLg .image-shape .label,#mermaid-svg-ojoAbmZ7NFE5DTLg .icon-shape .label{text-anchor:middle;}#mermaid-svg-ojoAbmZ7NFE5DTLg .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ojoAbmZ7NFE5DTLg .rough-node .label,#mermaid-svg-ojoAbmZ7NFE5DTLg .node .label,#mermaid-svg-ojoAbmZ7NFE5DTLg .image-shape .label,#mermaid-svg-ojoAbmZ7NFE5DTLg .icon-shape .label{text-align:center;}#mermaid-svg-ojoAbmZ7NFE5DTLg .node.clickable{cursor:pointer;}#mermaid-svg-ojoAbmZ7NFE5DTLg .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ojoAbmZ7NFE5DTLg .arrowheadPath{fill:#333333;}#mermaid-svg-ojoAbmZ7NFE5DTLg .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ojoAbmZ7NFE5DTLg .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ojoAbmZ7NFE5DTLg .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ojoAbmZ7NFE5DTLg .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ojoAbmZ7NFE5DTLg .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ojoAbmZ7NFE5DTLg .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ojoAbmZ7NFE5DTLg .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ojoAbmZ7NFE5DTLg .cluster text{fill:#333;}#mermaid-svg-ojoAbmZ7NFE5DTLg .cluster span{color:#333;}#mermaid-svg-ojoAbmZ7NFE5DTLg div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ojoAbmZ7NFE5DTLg .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ojoAbmZ7NFE5DTLg rect.text{fill:none;stroke-width:0;}#mermaid-svg-ojoAbmZ7NFE5DTLg .icon-shape,#mermaid-svg-ojoAbmZ7NFE5DTLg .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ojoAbmZ7NFE5DTLg .icon-shape p,#mermaid-svg-ojoAbmZ7NFE5DTLg .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ojoAbmZ7NFE5DTLg .icon-shape .label rect,#mermaid-svg-ojoAbmZ7NFE5DTLg .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ojoAbmZ7NFE5DTLg .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ojoAbmZ7NFE5DTLg .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ojoAbmZ7NFE5DTLg :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 倒排索引
正排索引
文档1: error in module A
文档2: warning in module B
文档3: error in module C
error → 1, 3
warning → 2
module → 1, 2, 3
文件索引的存储结构
app.log.dict # 词典文件(关键词元信息)
app.log.posting # 倒排列表文件(文档ID列表)
词典文件结构:
┌─────────────────────────────────┐
│ index_header (64字节) │ 文件头:魔数、版本、关键词数量
├─────────────────────────────────┤
│ keyword_entry 1 │ 关键词1:长度、文档频率、偏移量
│ keyword_entry 2 │ 关键词2
│ ... │
├─────────────────────────────────┤
│ 关键词字符串数据 │
└─────────────────────────────────┘
倒排列表文件结构:
┌─────────────────────────────────┐
│ 差值编码后的文档ID列表 │ [1, 5, 8] → [1, 4, 3]
├─────────────────────────────────┤
│ Varint 压缩后的数据 │ 小数字占用更少字节
└─────────────────────────────────┘
关键优化技术
1. 差值编码(Delta Encoding)
将有序的文档 ID 列表转换为相邻差值:
cpp
// 原始: [100, 105, 110, 120]
// 差值: [100, 5, 5, 10]
void delta_encode__(const vector<uint64_t>& ids, vector<uint64_t>& deltas) {
deltas.push_back(ids[0]);
for (size_t i = 1; i < ids.size(); ++i) {
deltas.push_back(ids[i] - ids[i-1]);
}
}
好处:差值通常远小于原始 ID,压缩效果显著。
2. Varint 压缩
变长编码,小数字占用更少的字节:
数字 1: 1字节 [0x01]
数字 300: 2字节 [0xAC, 0x02]
数字 10000: 3字节 [0x90, 0x4E, 0x00]
效果:倒排列表体积减少 40%~60%。
3. mmap 优化
在 Linux 下利用内存映射,减少系统调用开销:
cpp
#if HAS_MMAP
int dict_fd_ = -1;
uint8_t* dict_mmap_addr_ = nullptr;
size_t dict_mmap_size_ = 0;
#endif
优势:
- 操作系统自动管理页面缓存;
- 避免用户态与内核态之间的数据拷贝;
- 大文件也能高效随机访问。
3.4 布隆过滤器:快速排除不存在的关键词
问题:用户搜索一个不存在的关键词,也要查询倒排索引吗?
方案:通过布隆过滤器快速判断"肯定不存在",从而提前返回。
#mermaid-svg-aK82RbyWklhr1WWQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-aK82RbyWklhr1WWQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-aK82RbyWklhr1WWQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-aK82RbyWklhr1WWQ .error-icon{fill:#552222;}#mermaid-svg-aK82RbyWklhr1WWQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-aK82RbyWklhr1WWQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-aK82RbyWklhr1WWQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-aK82RbyWklhr1WWQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-aK82RbyWklhr1WWQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-aK82RbyWklhr1WWQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-aK82RbyWklhr1WWQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-aK82RbyWklhr1WWQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-aK82RbyWklhr1WWQ .marker.cross{stroke:#333333;}#mermaid-svg-aK82RbyWklhr1WWQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-aK82RbyWklhr1WWQ p{margin:0;}#mermaid-svg-aK82RbyWklhr1WWQ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-aK82RbyWklhr1WWQ .cluster-label text{fill:#333;}#mermaid-svg-aK82RbyWklhr1WWQ .cluster-label span{color:#333;}#mermaid-svg-aK82RbyWklhr1WWQ .cluster-label span p{background-color:transparent;}#mermaid-svg-aK82RbyWklhr1WWQ .label text,#mermaid-svg-aK82RbyWklhr1WWQ span{fill:#333;color:#333;}#mermaid-svg-aK82RbyWklhr1WWQ .node rect,#mermaid-svg-aK82RbyWklhr1WWQ .node circle,#mermaid-svg-aK82RbyWklhr1WWQ .node ellipse,#mermaid-svg-aK82RbyWklhr1WWQ .node polygon,#mermaid-svg-aK82RbyWklhr1WWQ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-aK82RbyWklhr1WWQ .rough-node .label text,#mermaid-svg-aK82RbyWklhr1WWQ .node .label text,#mermaid-svg-aK82RbyWklhr1WWQ .image-shape .label,#mermaid-svg-aK82RbyWklhr1WWQ .icon-shape .label{text-anchor:middle;}#mermaid-svg-aK82RbyWklhr1WWQ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-aK82RbyWklhr1WWQ .rough-node .label,#mermaid-svg-aK82RbyWklhr1WWQ .node .label,#mermaid-svg-aK82RbyWklhr1WWQ .image-shape .label,#mermaid-svg-aK82RbyWklhr1WWQ .icon-shape .label{text-align:center;}#mermaid-svg-aK82RbyWklhr1WWQ .node.clickable{cursor:pointer;}#mermaid-svg-aK82RbyWklhr1WWQ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-aK82RbyWklhr1WWQ .arrowheadPath{fill:#333333;}#mermaid-svg-aK82RbyWklhr1WWQ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-aK82RbyWklhr1WWQ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-aK82RbyWklhr1WWQ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-aK82RbyWklhr1WWQ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-aK82RbyWklhr1WWQ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-aK82RbyWklhr1WWQ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-aK82RbyWklhr1WWQ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-aK82RbyWklhr1WWQ .cluster text{fill:#333;}#mermaid-svg-aK82RbyWklhr1WWQ .cluster span{color:#333;}#mermaid-svg-aK82RbyWklhr1WWQ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-aK82RbyWklhr1WWQ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-aK82RbyWklhr1WWQ rect.text{fill:none;stroke-width:0;}#mermaid-svg-aK82RbyWklhr1WWQ .icon-shape,#mermaid-svg-aK82RbyWklhr1WWQ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-aK82RbyWklhr1WWQ .icon-shape p,#mermaid-svg-aK82RbyWklhr1WWQ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-aK82RbyWklhr1WWQ .icon-shape .label rect,#mermaid-svg-aK82RbyWklhr1WWQ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-aK82RbyWklhr1WWQ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-aK82RbyWklhr1WWQ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-aK82RbyWklhr1WWQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 不存在
可能存在
查询关键词
布隆过滤器
直接返回空
查倒排索引
返回结果
实现原理:
cpp
class bloom_filter {
vector<uint8_t> m_bits_; // 位数组
size_t m_hash_count_; // 哈希函数数量
void add(const string& key) {
for (size_t i = 0; i < m_hash_count_; ++i) {
uint64_t hash = hash_i(key, i);
m_bits_[hash % m_bits_.size() * 8] = 1;
}
}
bool contains(const string& key) const {
for (size_t i = 0; i < m_hash_count_; ++i) {
uint64_t hash = hash_i(key, i);
if (!m_bits_[hash % m_bits_.size() * 8]) {
return false; // 肯定不存在
}
}
return true; // 可能存在
}
};
配置示例:
cpp
bloom_filter(size_t expected_items = 100000,
double false_positive_rate = 0.001);
- 预期关键词数量 10 万;
- 误判率 0.1%;
- 内存占用约 240 KB。
3.5 前缀树(Trie):高效存储关键词
用途:存储所有关键词,并支持前缀查询。
#mermaid-svg-3NvNICXhBSW0TYQW{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3NvNICXhBSW0TYQW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3NvNICXhBSW0TYQW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3NvNICXhBSW0TYQW .error-icon{fill:#552222;}#mermaid-svg-3NvNICXhBSW0TYQW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3NvNICXhBSW0TYQW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3NvNICXhBSW0TYQW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3NvNICXhBSW0TYQW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3NvNICXhBSW0TYQW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3NvNICXhBSW0TYQW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3NvNICXhBSW0TYQW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3NvNICXhBSW0TYQW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3NvNICXhBSW0TYQW .marker.cross{stroke:#333333;}#mermaid-svg-3NvNICXhBSW0TYQW svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3NvNICXhBSW0TYQW p{margin:0;}#mermaid-svg-3NvNICXhBSW0TYQW .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3NvNICXhBSW0TYQW .cluster-label text{fill:#333;}#mermaid-svg-3NvNICXhBSW0TYQW .cluster-label span{color:#333;}#mermaid-svg-3NvNICXhBSW0TYQW .cluster-label span p{background-color:transparent;}#mermaid-svg-3NvNICXhBSW0TYQW .label text,#mermaid-svg-3NvNICXhBSW0TYQW span{fill:#333;color:#333;}#mermaid-svg-3NvNICXhBSW0TYQW .node rect,#mermaid-svg-3NvNICXhBSW0TYQW .node circle,#mermaid-svg-3NvNICXhBSW0TYQW .node ellipse,#mermaid-svg-3NvNICXhBSW0TYQW .node polygon,#mermaid-svg-3NvNICXhBSW0TYQW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3NvNICXhBSW0TYQW .rough-node .label text,#mermaid-svg-3NvNICXhBSW0TYQW .node .label text,#mermaid-svg-3NvNICXhBSW0TYQW .image-shape .label,#mermaid-svg-3NvNICXhBSW0TYQW .icon-shape .label{text-anchor:middle;}#mermaid-svg-3NvNICXhBSW0TYQW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3NvNICXhBSW0TYQW .rough-node .label,#mermaid-svg-3NvNICXhBSW0TYQW .node .label,#mermaid-svg-3NvNICXhBSW0TYQW .image-shape .label,#mermaid-svg-3NvNICXhBSW0TYQW .icon-shape .label{text-align:center;}#mermaid-svg-3NvNICXhBSW0TYQW .node.clickable{cursor:pointer;}#mermaid-svg-3NvNICXhBSW0TYQW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3NvNICXhBSW0TYQW .arrowheadPath{fill:#333333;}#mermaid-svg-3NvNICXhBSW0TYQW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3NvNICXhBSW0TYQW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3NvNICXhBSW0TYQW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3NvNICXhBSW0TYQW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3NvNICXhBSW0TYQW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3NvNICXhBSW0TYQW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3NvNICXhBSW0TYQW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3NvNICXhBSW0TYQW .cluster text{fill:#333;}#mermaid-svg-3NvNICXhBSW0TYQW .cluster span{color:#333;}#mermaid-svg-3NvNICXhBSW0TYQW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3NvNICXhBSW0TYQW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3NvNICXhBSW0TYQW rect.text{fill:none;stroke-width:0;}#mermaid-svg-3NvNICXhBSW0TYQW .icon-shape,#mermaid-svg-3NvNICXhBSW0TYQW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3NvNICXhBSW0TYQW .icon-shape p,#mermaid-svg-3NvNICXhBSW0TYQW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3NvNICXhBSW0TYQW .icon-shape .label rect,#mermaid-svg-3NvNICXhBSW0TYQW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3NvNICXhBSW0TYQW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3NvNICXhBSW0TYQW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3NvNICXhBSW0TYQW :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} root
e
w
r
r
o
r
●
freq:42
a
r
n
i
n
g
●
freq:15
节点结构:
cpp
struct trie_node {
unordered_map<char, trie_node*> children;
bool is_end = false; // 是否为一个完整单词
uint64_t posting_offset = 0; // 倒排列表偏移量
uint32_t doc_freq = 0; // 文档频率
};
前缀查询接口:
cpp
bool starts_with(const string& prefix, vector<string>& results) const {
trie_node* node = find_node(prefix);
if (!node) return false;
collect_words(node, prefix, results);
return true;
}
典型应用场景:
- 输入 "err",自动提示 "error";
- 实现搜索框的自动补全功能。
3.6 热词缓存:LRU 策略加速高频查询
问题:用户反复搜索 "error",每次都要走一遍完整的索引流程?
方案:采用 LRU 策略,缓存最近的高频查询结果。
cpp
class hot_word_cache {
struct cached_result {
vector<uint64_t> indices;
uint64_t access_count = 0;
uint64_t last_access_time = 0;
};
// LRU 淘汰策略
void evict_if_needed() {
if (cache.size() >= max_size_) {
// 选择访问次数最少、最近未使用的条目
auto victim = find_lru_entry();
cache.erase(victim);
}
}
};
实际效果:
- 热词查询从毫秒级降至微秒级;
- 真实场景中缓存命中率可达 60%~80%。
四、工作流程:一次完整的检索
让我们跟踪一次查询 "error" 的完整路径:
tinySeqFile 倒排列表 前缀树 布隆过滤器 热词缓存 log_reader 用户 tinySeqFile 倒排列表 前缀树 布隆过滤器 热词缓存 log_reader 用户 #mermaid-svg-EQqCGZoFPKUoVnoN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-EQqCGZoFPKUoVnoN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-EQqCGZoFPKUoVnoN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-EQqCGZoFPKUoVnoN .error-icon{fill:#552222;}#mermaid-svg-EQqCGZoFPKUoVnoN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EQqCGZoFPKUoVnoN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-EQqCGZoFPKUoVnoN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EQqCGZoFPKUoVnoN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EQqCGZoFPKUoVnoN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-EQqCGZoFPKUoVnoN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EQqCGZoFPKUoVnoN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EQqCGZoFPKUoVnoN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EQqCGZoFPKUoVnoN .marker.cross{stroke:#333333;}#mermaid-svg-EQqCGZoFPKUoVnoN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EQqCGZoFPKUoVnoN p{margin:0;}#mermaid-svg-EQqCGZoFPKUoVnoN .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-EQqCGZoFPKUoVnoN text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-EQqCGZoFPKUoVnoN .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-EQqCGZoFPKUoVnoN .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-EQqCGZoFPKUoVnoN .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-EQqCGZoFPKUoVnoN .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-EQqCGZoFPKUoVnoN #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-EQqCGZoFPKUoVnoN .sequenceNumber{fill:white;}#mermaid-svg-EQqCGZoFPKUoVnoN #sequencenumber{fill:#333;}#mermaid-svg-EQqCGZoFPKUoVnoN #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-EQqCGZoFPKUoVnoN .messageText{fill:#333;stroke:none;}#mermaid-svg-EQqCGZoFPKUoVnoN .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-EQqCGZoFPKUoVnoN .labelText,#mermaid-svg-EQqCGZoFPKUoVnoN .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-EQqCGZoFPKUoVnoN .loopText,#mermaid-svg-EQqCGZoFPKUoVnoN .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-EQqCGZoFPKUoVnoN .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-EQqCGZoFPKUoVnoN .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-EQqCGZoFPKUoVnoN .noteText,#mermaid-svg-EQqCGZoFPKUoVnoN .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-EQqCGZoFPKUoVnoN .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-EQqCGZoFPKUoVnoN .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-EQqCGZoFPKUoVnoN .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-EQqCGZoFPKUoVnoN .actorPopupMenu{position:absolute;}#mermaid-svg-EQqCGZoFPKUoVnoN .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-EQqCGZoFPKUoVnoN .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-EQqCGZoFPKUoVnoN .actor-man circle,#mermaid-svg-EQqCGZoFPKUoVnoN line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-EQqCGZoFPKUoVnoN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} loop 每个文档ID alt 布隆过滤器判断不存在 可能存在 alt 缓存命中 缓存未命中 search("error") get("error") 返回结果 返回匹配记录 contains("error") false 返回空 search("error") 返回倒排列表偏移 读取文档ID列表 1, 5, 8, 12 at(id) 返回日志内容 put("error", results) 返回匹配记录
性能优化层级:
- 第一层:热词缓存直接返回结果;
- 第二层:布隆过滤器快速筛掉不存在的词;
- 第三层:前缀树精确定位倒排列表;
- 第四层:mmap 高效读取倒排列表和日志内容。
五、设计优势总结
5.1 架构优势
| 优势 | 说明 |
|---|---|
| 模块解耦 | 接口抽象,实现可插拔替换 |
| 分层优化 | 缓存 → 布隆过滤器 → 倒排索引,逐层加速 |
| 资源友好 | 文件索引 + mmap,内存占用极低 |
| 嵌入式适配 | 掉电安全、Flash 寿命保护 |
5.2 性能优势
#mermaid-svg-7oroPLXgUUVctgpY{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7oroPLXgUUVctgpY .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7oroPLXgUUVctgpY .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7oroPLXgUUVctgpY .error-icon{fill:#552222;}#mermaid-svg-7oroPLXgUUVctgpY .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7oroPLXgUUVctgpY .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7oroPLXgUUVctgpY .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7oroPLXgUUVctgpY .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7oroPLXgUUVctgpY .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7oroPLXgUUVctgpY .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7oroPLXgUUVctgpY .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7oroPLXgUUVctgpY .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7oroPLXgUUVctgpY .marker.cross{stroke:#333333;}#mermaid-svg-7oroPLXgUUVctgpY svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7oroPLXgUUVctgpY p{margin:0;}#mermaid-svg-7oroPLXgUUVctgpY .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-7oroPLXgUUVctgpY .cluster-label text{fill:#333;}#mermaid-svg-7oroPLXgUUVctgpY .cluster-label span{color:#333;}#mermaid-svg-7oroPLXgUUVctgpY .cluster-label span p{background-color:transparent;}#mermaid-svg-7oroPLXgUUVctgpY .label text,#mermaid-svg-7oroPLXgUUVctgpY span{fill:#333;color:#333;}#mermaid-svg-7oroPLXgUUVctgpY .node rect,#mermaid-svg-7oroPLXgUUVctgpY .node circle,#mermaid-svg-7oroPLXgUUVctgpY .node ellipse,#mermaid-svg-7oroPLXgUUVctgpY .node polygon,#mermaid-svg-7oroPLXgUUVctgpY .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-7oroPLXgUUVctgpY .rough-node .label text,#mermaid-svg-7oroPLXgUUVctgpY .node .label text,#mermaid-svg-7oroPLXgUUVctgpY .image-shape .label,#mermaid-svg-7oroPLXgUUVctgpY .icon-shape .label{text-anchor:middle;}#mermaid-svg-7oroPLXgUUVctgpY .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-7oroPLXgUUVctgpY .rough-node .label,#mermaid-svg-7oroPLXgUUVctgpY .node .label,#mermaid-svg-7oroPLXgUUVctgpY .image-shape .label,#mermaid-svg-7oroPLXgUUVctgpY .icon-shape .label{text-align:center;}#mermaid-svg-7oroPLXgUUVctgpY .node.clickable{cursor:pointer;}#mermaid-svg-7oroPLXgUUVctgpY .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-7oroPLXgUUVctgpY .arrowheadPath{fill:#333333;}#mermaid-svg-7oroPLXgUUVctgpY .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-7oroPLXgUUVctgpY .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-7oroPLXgUUVctgpY .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7oroPLXgUUVctgpY .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-7oroPLXgUUVctgpY .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7oroPLXgUUVctgpY .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-7oroPLXgUUVctgpY .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-7oroPLXgUUVctgpY .cluster text{fill:#333;}#mermaid-svg-7oroPLXgUUVctgpY .cluster span{color:#333;}#mermaid-svg-7oroPLXgUUVctgpY div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-7oroPLXgUUVctgpY .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-7oroPLXgUUVctgpY rect.text{fill:none;stroke-width:0;}#mermaid-svg-7oroPLXgUUVctgpY .icon-shape,#mermaid-svg-7oroPLXgUUVctgpY .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-7oroPLXgUUVctgpY .icon-shape p,#mermaid-svg-7oroPLXgUUVctgpY .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-7oroPLXgUUVctgpY .icon-shape .label rect,#mermaid-svg-7oroPLXgUUVctgpY .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-7oroPLXgUUVctgpY .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-7oroPLXgUUVctgpY .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-7oroPLXgUUVctgpY :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 查询性能
缓存命中: 微秒级
布隆过滤: 微秒级
索引查询: 毫秒级
100x 加速
10x 加速
基线
5.3 工程优势
1. 代码质量高
- 头文件注释规范,参数说明详尽;
- 采用 Pimpl 模式隐藏内部实现细节;
- 遵循 RAII 原则管理资源,无内存泄漏。
2. 可扩展性强
- 新增索引类型只需实现
search_index_itfc; - 新增后端存储也只需适配相应接口。
3. 编译选项灵活
cmake
-DWITH_SQLITE3=ON # 启用 SQLite3 索引
-DWITH_MMAP=ON # 启用 mmap 优化
-DWITH_OPENSSL=ON # 启用 AES 加密
六、实际应用场景
6.1 嵌入式设备日志分析
场景:工业网关持续运行 6 个月,累积 100 万条日志。
需求:快速检索某个时间段的错误日志。
方案:
- 选用 FILE 索引(内存占用最低);
- 离线或定时构建倒排索引;
- 按关键词 "error" + 时间范围检索。
效果:
- 索引文件大小约 20 MB(仅为原始日志的 10%~20%);
- 检索耗时 < 10 ms;
- 内存占用 < 5 MB。
6.2 实时日志监控
场景:开发调试过程中实时查看日志。
需求:快速过滤、实时显示。
方案:
- 选用 MEMORY 索引(检索速度最快);
- 配合热词缓存高频查询;
- 终端模式直接输出结果。
效果:
- 检索延迟 < 1 ms;
- 无持久化开销。
6.3 长期日志归档
场景:日志需要保存 1 年以上,用于历史审计和合规检查。
需求:支持复杂查询、数据安全、便于迁移。
方案:
- 选用 SQLITE3 索引(持久化存储);
- 定期将日志归档到 SQLite 数据库;
- 支持时间范围、关键词组合等复杂查询。
效果:
- 查询灵活(支持 SQL 语法);
- 数据安全(事务支持);
- 备份与迁移方便。
七、总结与思考
7.1 核心设计思想
log_reader 的成功离不开以下几点:
- 问题驱动:从嵌入式场景的真实痛点出发,而非照搬通用方案;
- 分层设计:接口抽象、实现可插拔、优化分层,各层职责清晰;
- 性能优先:缓存、布隆过滤器、倒排索引、mmap,层层递进优化;
- 资源意识:始终在内存、存储和 CPU 开销之间权衡取舍。
7.2 可借鉴的设计模式
- 策略模式:索引类型可灵活切换;
- 工厂模式:统一创建索引实例;
- Pimpl 模式:隐藏实现细节,降低编译依赖;
- 装饰器模式:布隆过滤器作为倒排索引的前置过滤层。
7.3 值得学习的工程实践
- 条件编译:适配不同平台和需求;
- RAII:资源自动管理,杜绝泄漏;
- Doxygen 注释规范:参数、返回值清晰明确;
- 文件版本管理:头部保留版本号,支持平滑升级。
八、写在最后
log_reader 的架构,是嵌入式约束与检索效率之间一次优雅的平衡。它没有盲目追求"大而全",而是深耕特定场景,将每一个细节打磨到极致。
好架构不是凭空设计出来的,而是从真实问题中生长出来的。
参考资料:
- 项目地址:https://gitee.com/galaxy_0/micro-log.git
- 代码版本:v3.0(支持全文检索)