IndexLib(7):内存管理与资源控制
发布于:
在上一篇文章中,我们深入了解了 Segment 合并策略的实现。本文将继续深入,详细解析内存管理与资源控制的机制,这是理解 IndexLib 如何高效管理内存和资源的关键。
内存管理与资源控制概览:从内存配额到内存回收的完整机制:
flowchart TB
Start([内存管理与资源控制概览<br/>Memory Management & Resource Control Overview]) --> QuotaLayer[内存配额层<br/>Memory Quota Layer]
subgraph QuotaGroup["内存配额 Memory Quota"]
direction TB
Q1[MemoryQuotaController<br/>配额控制器<br/>管理内存配额和分配]
Q2[层级配额管理<br/>Hierarchical Quota<br/>支持多级配额管理]
Q3[配额分配<br/>Quota Allocation<br/>动态分配内存配额]
Q1 --> Q2
Q2 --> Q3
end
QuotaLayer --> CalculateLayer[内存计算层<br/>Memory Calculation Layer]
subgraph CalculateGroup["内存计算 Memory Calculation"]
direction TB
C1[TabletMemoryCalculator<br/>内存计算器<br/>计算Tablet内存使用]
C2[实时统计<br/>Real-time Statistics<br/>实时统计内存使用]
C3[分类统计<br/>Categorized Statistics<br/>按类型统计内存]
C1 --> C2
C2 --> C3
end
CalculateLayer --> ReclaimLayer[内存回收层<br/>Memory Reclaim Layer]
subgraph ReclaimGroup["内存回收 Memory Reclaim"]
direction TB
R1[IIndexMemoryReclaimer<br/>内存回收器<br/>回收不再使用的内存]
R2[延迟回收<br/>Delayed Reclaim<br/>延迟回收避免频繁操作]
R3[按需回收<br/>On-Demand Reclaim<br/>内存紧张时按需回收]
R1 --> R2
R2 --> R3
end
ReclaimLayer --> ResourceLayer[资源控制层<br/>Resource Control Layer]
subgraph ResourceGroup["资源控制 Resource Control"]
direction TB
RE1[BuildResourceCalculator<br/>构建资源计算器<br/>计算构建资源使用]
RE2[资源估算<br/>Resource Estimation<br/>估算资源需求]
RE3[资源预留<br/>Resource Reservation<br/>预留构建资源]
RE1 --> RE2
RE2 --> RE3
end
ResourceLayer --> End([内存管理完成<br/>Memory Management Complete])
QuotaLayer -.->|包含| QuotaGroup
CalculateLayer -.->|包含| CalculateGroup
ReclaimLayer -.->|包含| ReclaimGroup
ResourceLayer -.->|包含| ResourceGroup
Q3 -.->|使用| C1
C3 -.->|触发| R1
R3 -.->|使用| RE1
style Start fill:#c8e6c9,stroke:#388e3c,stroke-width:3px
style End fill:#c8e6c9,stroke:#388e3c,stroke-width:3px
style QuotaLayer fill:#e3f2fd,stroke:#1976d2,stroke-width:3px
style CalculateLayer fill:#fff3e0,stroke:#f57c00,stroke-width:3px
style ReclaimLayer fill:#f3e5f5,stroke:#7b1fa2,stroke-width:3px
style ResourceLayer fill:#e8f5e9,stroke:#2e7d32,stroke-width:3px
style QuotaGroup fill:#e3f2fd,stroke:#1976d2,stroke-width:3px
style Q1 fill:#90caf9,stroke:#1976d2,stroke-width:2px
style Q2 fill:#90caf9,stroke:#1976d2,stroke-width:2px
style Q3 fill:#90caf9,stroke:#1976d2,stroke-width:2px
style CalculateGroup fill:#fff3e0,stroke:#f57c00,stroke-width:3px
style C1 fill:#ffcc80,stroke:#f57c00,stroke-width:2px
style C2 fill:#ffcc80,stroke:#f57c00,stroke-width:2px
style C3 fill:#ffcc80,stroke:#f57c00,stroke-width:2px
style ReclaimGroup fill:#f3e5f5,stroke:#7b1fa2,stroke-width:3px
style R1 fill:#ce93d8,stroke:#7b1fa2,stroke-width:2px
style R2 fill:#ce93d8,stroke:#7b1fa2,stroke-width:2px
style R3 fill:#ce93d8,stroke:#7b1fa2,stroke-width:2px
style ResourceGroup fill:#e8f5e9,stroke:#2e7d32,stroke-width:3px
style RE1 fill:#a5d6a7,stroke:#2e7d32,stroke-width:2px
style RE2 fill:#a5d6a7,stroke:#2e7d32,stroke-width:2px
style RE3 fill:#a5d6a7,stroke:#2e7d32,stroke-width:2px
1. 内存管理概览
1.1 内存管理的核心概念
IndexLib 的内存管理包括以下核心概念:
- MemoryQuotaController:内存配额控制器,管理内存配额和分配
- TabletMemoryCalculator:Tablet 内存计算器,计算 Tablet 的内存使用
- IIndexMemoryReclaimer:索引内存回收器,回收不再使用的内存
- BuildResourceCalculator:构建资源计算器,计算构建时的资源使用
让我们先通过图来理解内存管理的整体架构:
内存管理架构:MemoryQuotaController、TabletMemoryCalculator、IIndexMemoryReclaimer 的关系(已在上面类图中展示,此处不再重复):
1.2 内存管理的作用
内存管理在 IndexLib 中起到关键作用,是系统稳定性和性能的基础。让我们通过类图来理解内存管理的整体架构:
classDiagram
class MemoryQuotaController {
- string _name
- int64_t _rootQuota
- atomic_int64_t _localFreeQuota
- atomic_int64_t _reservedParentQuota
- MemoryQuotaController _parentController
+ Allocate()
+ TryAllocate()
+ Reserve()
+ Free()
+ GetAllocatedQuota()
}
class TabletMemoryCalculator {
- TabletWriter _tabletWriter
- TabletReaderContainer _tabletReaderContainer
+ GetRtBuiltSegmentsMemsize()
+ GetRtIndexMemsize()
+ GetBuildingSegmentMemsize()
}
class IIndexMemoryReclaimer {
<<interface>>
+ Retire()
+ DropRetireItem()
+ TryReclaim()
+ Reclaim()
}
class BuildResourceCalculator {
+ GetCurrentTotalMemoryUse()
+ EstimateDumpTempMemoryUse()
+ EstimateDumpExpandMemoryUse()
}
MemoryQuotaController --> MemoryQuotaController : 层级关系
TabletMemoryCalculator --> MemoryQuotaController : 使用
IIndexMemoryReclaimer --> MemoryQuotaController : 使用
BuildResourceCalculator --> MemoryQuotaController : 使用
内存管理的核心作用:
- 内存配额控制:通过 MemoryQuotaController 控制内存使用,避免内存溢出
- 配额管理:为每个组件分配内存配额,控制内存使用上限
- 层级管理:支持层级配额管理,灵活分配配额
- 配额预留:通过预留机制保证关键操作的配额
- 内存使用统计:通过 TabletMemoryCalculator 统计内存使用,监控内存状态
- 实时统计:实时统计各个组件的内存使用量
- 分类统计:按类型统计内存使用(构建、查询、索引等)
- 监控告警:根据统计结果监控内存状态,及时告警
- 内存回收:通过 IIndexMemoryReclaimer 回收不再使用的内存,释放内存空间
- 延迟回收:延迟回收避免频繁的内存操作
- 按需回收:在内存紧张时按需回收,保证系统稳定性
- 并发安全:支持并发回收,保证线程安全
- 资源优化:通过 BuildResourceCalculator 优化构建资源使用,提高构建效率
- 资源估算:估算构建和转储所需的资源
- 资源预留:预留构建和转储所需的资源
- 资源控制:控制资源使用,避免资源浪费
2. MemoryQuotaController:内存配额控制器
2.1 MemoryQuotaController 的结构
MemoryQuotaController 是内存配额控制器,定义在 base/MemoryQuotaController.h 中:
// base/MemoryQuotaController.h
class MemoryQuotaController
{
public:
// 构造函数:创建根配额控制器
MemoryQuotaController(std::string name, int64_t totalQuota);
// 构造函数:创建子配额控制器
MemoryQuotaController(std::string name,
std::shared_ptr<MemoryQuotaController> parentController);
// 分配内存配额
void Allocate(int64_t quota);
Status TryAllocate(int64_t quota); // 尝试分配,不阻塞
// 预留内存配额
Status Reserve(int64_t quota);
// 释放内存配额
void Free(int64_t quota);
// 获取内存配额信息
int64_t GetAllocatedQuota() const; // 已分配配额
int64_t GetFreeQuota() const; // 可用配额
int64_t GetTotalQuota() const; // 总配额
private:
std::string _name; // 控制器名称
const int64_t _rootQuota; // 根配额(根控制器)
std::atomic<int64_t> _localFreeQuota; // 本地可用配额
std::atomic<int64_t> _reservedParentQuota; // 从父控制器预留的配额
std::shared_ptr<MemoryQuotaController> _parentController; // 父控制器
};
MemoryQuotaController 的关键字段:
MemoryQuotaController 的结构:包含配额信息和父控制器:
flowchart TD
subgraph Controller["MemoryQuotaController"]
C1["name<br/>控制器名称"]
C2["rootQuota<br/>根配额"]
C3["localFreeQuota<br/>本地可用配额"]
C4["reservedParentQuota<br/>预留父配额"]
C5["parentController<br/>父控制器引用"]
end
subgraph Methods["关键方法"]
M1["Allocate<br/>分配配额"]
M2["TryAllocate<br/>尝试分配"]
M3["Reserve<br/>预留配额"]
M4["Free<br/>释放配额"]
end
C1 --> M1
C2 --> M1
C3 --> M1
C4 --> M3
C5 --> M1
style Controller fill:#e3f2fd
style Methods fill:#fff3e0
- rootQuota:根配额,根控制器的总配额
- localFreeQuota:本地可用配额,当前控制器可用的配额
- reservedParentQuota:从父控制器预留的配额
- parentController:父控制器,支持层级配额管理
2.2 内存配额分配
内存配额的分配机制:
内存配额分配:从父控制器到子控制器的配额分配(已在上面详细展示,此处不再重复):
内存配额分配流程图:
flowchart TD
Start[请求分配内存<br/>Allocate请求] --> CheckLocal[检查本地配额<br/>localFreeQuota]
CheckLocal --> LocalEnough{本地配额足够?}
LocalEnough -->|是| AllocateLocal[从本地分配<br/>减少localFreeQuota]
LocalEnough -->|否| RequestParent[向父控制器请求<br/>parentController.Allocate]
AllocateLocal --> UpdateLocal[更新本地配额<br/>更新localFreeQuota]
RequestParent --> CheckParent{父控制器有配额?}
CheckParent -->|是| AllocateParent[从父控制器分配<br/>减少父控制器的配额]
CheckParent -->|否| HandleNoQuota[处理配额不足]
AllocateParent --> ReserveParent[预留父配额<br/>增加reservedParentQuota]
ReserveParent --> UpdateLocal
HandleNoQuota --> WaitOrReject{等待或拒绝?}
WaitOrReject -->|等待| WaitQuota[等待配额释放<br/>阻塞或轮询]
WaitOrReject -->|拒绝| RejectAlloc[拒绝分配<br/>返回失败]
WaitQuota --> CheckLocal
UpdateLocal --> Success[分配完成<br/>返回成功]
RejectAlloc --> Fail[分配失败<br/>返回错误]
style Start fill:#e3f2fd,stroke:#1976d2,stroke-width:3px
style CheckLocal fill:#e3f2fd,stroke:#1976d2,stroke-width:1px
style LocalEnough fill:#e3f2fd,stroke:#1976d2,stroke-width:2px
style AllocateLocal fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
style RequestParent fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style CheckParent fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style AllocateParent fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style ReserveParent fill:#fff3e0,stroke:#f57c00,stroke-width:1px
style UpdateLocal fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px
style HandleNoQuota fill:#ffebee,stroke:#c62828,stroke-width:1px
style WaitOrReject fill:#ffebee,stroke:#c62828,stroke-width:1px
style WaitQuota fill:#fff9c4,stroke:#f57f17,stroke-width:1px
style RejectAlloc fill:#ffebee,stroke:#c62828,stroke-width:2px
style Success fill:#e8f5e9,stroke:#2e7d32,stroke-width:2px
style Fail fill:#ffebee,stroke:#c62828,stroke-width:2px
分配机制详解:
内存配额的分配是内存管理的核心机制。让我们通过序列图来理解完整的分配流程:
sequenceDiagram
participant Component as 组件
participant ChildCtrl as 子控制器
participant ParentCtrl as 父控制器
participant RootCtrl as 根控制器
Component->>ChildCtrl: Allocate(quota)
ChildCtrl->>ChildCtrl: 检查本地配额
alt 本地配额足够
ChildCtrl->>ChildCtrl: 从本地分配
ChildCtrl-->>Component: Success
else 本地配额不足
ChildCtrl->>ParentCtrl: 请求配额
ParentCtrl->>ParentCtrl: 检查配额
alt 父控制器有配额
ParentCtrl->>ChildCtrl: 分配配额
ChildCtrl->>ChildCtrl: 更新本地配额
ChildCtrl-->>Component: Success
else 父控制器配额不足
ParentCtrl->>RootCtrl: 请求配额
RootCtrl->>ParentCtrl: 分配配额
ParentCtrl->>ChildCtrl: 分配配额
ChildCtrl->>ChildCtrl: 更新本地配额
ChildCtrl-->>Component: Success
end
end
分配机制详解:
- 根控制器分配:根控制器有固定的总配额
- 总配额设置:根控制器在创建时设置总配额
- 配额分配:根控制器将配额分配给子控制器
- 配额监控:根控制器监控总配额的使用情况
- 子控制器分配:子控制器从父控制器分配配额
- 配额请求:子控制器向父控制器请求配额
- 配额传递:父控制器将配额传递给子控制器
- 配额隔离:不同子控制器的配额相互隔离
- 配额预留:通过 Reserve() 预留配额,保证后续分配
- 预留机制:预留配额不会被其他操作占用
- 预留释放:预留的配额可以释放,返回给父控制器
- 预留用途:用于保证关键操作(如转储)的配额
- 配额释放:通过 Free() 释放配额,返回给父控制器
- 释放时机:当内存不再使用时释放配额
- 释放传递:释放的配额返回给父控制器
- 配额回收:父控制器可以回收子控制器的配额
分配策略的优势:
- 灵活性:支持层级配额管理,灵活分配配额
- 隔离性:不同组件的配额相互隔离,避免相互影响
- 可控性:通过配额控制内存使用,避免内存溢出
- 可扩展性:支持动态创建和销毁配额控制器
2.3 层级配额管理
MemoryQuotaController 支持层级配额管理:
层级配额管理:从根控制器到子控制器的层级结构:
flowchart TD
subgraph Root["根控制器"]
R1["总配额<br/>Total Quota"]
end
subgraph Partition["分区控制器"]
P1["分区配额<br/>Partition Quota"]
P2["分区配额<br/>Partition Quota"]
end
subgraph Tablet["Tablet 控制器"]
T1["Tablet配额<br/>Tablet Quota"]
T2["Tablet配额<br/>Tablet Quota"]
end
R1 --> P1
R1 --> P2
P1 --> T1
P2 --> T2
style Root fill:#e3f2fd
style Partition fill:#fff3e0
style Tablet fill:#f3e5f5
层级结构:
层级配额管理是 IndexLib 内存管理的核心设计。让我们通过类图来理解层级结构:
classDiagram
class RootController {
- int64_t _rootQuota = 100GB
+ Allocate()
+ GetFreeQuota()
}
class PartitionController {
- MemoryQuotaController _parent
- int64_t _localFreeQuota
+ Allocate()
+ GetFreeQuota()
}
class TabletController {
- MemoryQuotaController _parent
- int64_t _localFreeQuota
+ Allocate()
+ GetFreeQuota()
}
class BuildController {
- MemoryQuotaController _parent
- int64_t _localFreeQuota
+ Allocate()
}
class QueryController {
- MemoryQuotaController _parent
- int64_t _localFreeQuota
+ Allocate()
}
RootController --> PartitionController : 分配配额
PartitionController --> TabletController : 分配配额
TabletController --> BuildController : 分配配额
TabletController --> QueryController : 分配配额
层级结构详解:
- 根控制器:管理总配额,分配给子控制器
- 总配额:根控制器管理系统的总内存配额(如 100GB)
- 配额分配:将总配额分配给分区控制器或 Tablet 控制器
- 配额监控:监控总配额的使用情况,防止超限
- 分区控制器:管理分区的配额,分配给 Tablet 控制器
- 分区配额:每个分区有独立的内存配额
- 配额分配:将分区配额分配给该分区下的 Tablet 控制器
- 配额隔离:不同分区的配额相互隔离
- Tablet 控制器:管理 Tablet 的配额,分配给各个组件
- Tablet 配额:每个 Tablet 有独立的内存配额
- 配额分配:将 Tablet 配额分配给构建、查询等组件
- 配额平衡:根据组件的重要性平衡配额分配
- 组件控制器:管理组件的配额,如构建配额、查询配额等
- 构建配额:管理构建操作的内存配额
- 查询配额:管理查询操作的内存配额
- 索引配额:管理索引数据的内存配额
层级管理的优势:
- 灵活分配:支持多层级配额管理,灵活分配配额
- 配额隔离:不同层级的配额相互隔离,避免相互影响
- 配额共享:支持配额共享,提高配额利用率
- 配额监控:可以监控每个层级的配额使用情况
2.4 配额分配策略
配额分配的策略:
配额分配策略:按需分配、预留分配等策略:
flowchart TD
subgraph Strategies["分配策略"]
S1["按需分配<br/>On-Demand"]
S2["预留分配<br/>Reserved"]
S3["阻塞分配<br/>Blocking"]
S4["非阻塞分配<br/>Non-Blocking"]
end
subgraph Methods["分配方法"]
M1["Allocate<br/>阻塞分配"]
M2["TryAllocate<br/>非阻塞分配"]
M3["Reserve<br/>预留分配"]
end
S1 --> M1
S2 --> M3
S3 --> M1
S4 --> M2
style Strategies fill:#e3f2fd
style Methods fill:#fff3e0
分配策略:
- 按需分配:根据实际需求分配配额,灵活适应不同场景
- 预留分配:通过 Reserve() 预留配额,保证关键操作的配额
- 阻塞分配:Allocate() 会阻塞直到有可用配额
- 非阻塞分配:TryAllocate() 不阻塞,立即返回结果
3. TabletMemoryCalculator:Tablet 内存计算器
3.1 TabletMemoryCalculator 的结构
TabletMemoryCalculator 是 Tablet 内存计算器,定义在 framework/TabletMemoryCalculator.h 中:
// framework/TabletMemoryCalculator.h
class TabletMemoryCalculator final
{
public:
TabletMemoryCalculator(const std::shared_ptr<TabletWriter>& tabletWriter,
const std::shared_ptr<TabletReaderContainer>& tabletReaderContainer);
// 获取各种内存使用量
size_t GetRtBuiltSegmentsMemsize() const; // 实时已构建 Segment 内存
size_t GetRtIndexMemsize() const; // 实时索引内存
size_t GetIncIndexMemsize() const; // 增量索引内存
size_t GetBuildingSegmentMemsize() const; // 构建中 Segment 内存
size_t GetDumpingSegmentMemsize() const; // 转储中 Segment 内存
size_t GetBuildingSegmentDumpExpandMemsize() const; // 转储扩展内存
private:
std::shared_ptr<TabletWriter> _tabletWriter;
std::shared_ptr<TabletReaderContainer> _tabletReaderContainer;
};
TabletMemoryCalculator 的关键方法:
TabletMemoryCalculator 的方法:计算各种内存使用量:
flowchart TD
subgraph Calculator["TabletMemoryCalculator"]
C1["GetRtBuiltSegmentsMemsize<br/>实时构建段内存"]
C2["GetRtIndexMemsize<br/>实时索引内存"]
C3["GetBuildingSegmentMemsize<br/>构建中段内存"]
C4["GetDumpingSegmentMemsize<br/>转储中段内存"]
end
subgraph Components["统计组件"]
CO1["TabletWriter<br/>写入器内存"]
CO2["TabletReaderContainer<br/>查询器容器内存"]
CO3["IndexReader<br/>索引读取器内存"]
end
C1 --> CO1
C2 --> CO2
C3 --> CO1
C4 --> CO1
style Calculator fill:#e3f2fd
style Components fill:#fff3e0
- GetRtBuiltSegmentsMemsize():计算实时已构建 Segment 的内存使用
- GetRtIndexMemsize():计算实时索引的内存使用
- GetIncIndexMemsize():计算增量索引的内存使用
- GetBuildingSegmentMemsize():计算构建中 Segment 的内存使用
- GetDumpingSegmentMemsize():计算转储中 Segment 的内存使用
3.2 内存使用统计
内存使用统计的流程:
内存使用统计:从 Tablet 组件到内存使用量的统计流程(已在上面详细展示,此处不再重复):
统计流程:
内存使用统计是监控和调优的基础。让我们通过序列图来理解完整的统计流程:
sequenceDiagram
participant Calculator as TabletMemoryCalculator
participant Writer as TabletWriter
participant ReaderContainer as TabletReaderContainer
participant MemSeg as MemSegment
participant DiskSeg as DiskSegment
participant Indexer as Indexer
Calculator->>Writer: GetBuildingSegment()
Writer-->>Calculator: MemSegment
Calculator->>MemSeg: EvaluateCurrentMemUsed()
MemSeg->>Indexer: GetMemUsed()
Indexer-->>MemSeg: memUsed
MemSeg-->>Calculator: buildingMemSize
Calculator->>Writer: GetDumpingSegment()
Writer-->>Calculator: DumpingSegment
Calculator->>DumpingSegment: EvaluateCurrentMemUsed()
DumpingSegment-->>Calculator: dumpingMemSize
Calculator->>ReaderContainer: GetTabletReaders()
ReaderContainer-->>Calculator: TabletReaders
loop 遍历每个TabletReader
Calculator->>DiskSeg: GetIndexMemsize()
DiskSeg-->>Calculator: indexMemSize
end
Calculator->>Calculator: 汇总所有内存使用
Calculator-->>Calculator: 返回统计结果
统计流程详解:
- 收集组件信息:从 TabletWriter 和 TabletReaderContainer 收集组件信息
- 构建组件:收集构建中的 MemSegment、转储中的 Segment 等
- 查询组件:收集 TabletReader、IndexReader 等查询组件
- 索引组件:收集各种 Indexer(倒排、正排、主键等)
- 计算各组件内存:计算各个组件的内存使用量
- Segment 内存:计算 MemSegment 和 DiskSegment 的内存使用
- 索引内存:计算各个 Indexer 的内存使用
- 缓存内存:计算缓存的内存使用
- 汇总内存使用:汇总所有组件的内存使用量
- 分类汇总:按类型汇总内存使用(构建、查询、索引等)
- 总内存使用:计算总的内存使用量
- 内存占比:计算各组件内存占总内存的比例
- 返回统计结果:返回详细的内存使用统计结果
- 详细统计:返回各个组件的详细内存使用量
- 统计报告:生成内存使用统计报告
- 监控数据:提供监控数据,用于告警和调优
统计的用途:
- 内存监控:实时监控内存使用情况,及时发现内存问题
- 性能调优:根据统计结果优化内存分配,提高内存利用率
- 资源规划:根据统计结果规划内存资源,合理分配配额
- 问题诊断:通过统计结果诊断内存问题,定位内存泄漏
4. IIndexMemoryReclaimer:索引内存回收器
4.1 IIndexMemoryReclaimer 接口
IIndexMemoryReclaimer 是索引内存回收器的接口,定义在 framework/mem_reclaimer/IIndexMemoryReclaimer.h 中:
// framework/mem_reclaimer/IIndexMemoryReclaimer.h
class IIndexMemoryReclaimer
{
public:
// 回收内存:将内存加入回收队列
virtual int64_t Retire(void* addr, std::function<void(void*)> deAllocator) = 0;
// 取消回收:从回收队列中移除
virtual void DropRetireItem(int64_t itemId) = 0;
// 尝试回收:尝试回收一些内存
virtual void TryReclaim() = 0;
// 强制回收:强制回收所有可回收的内存
virtual void Reclaim() = 0;
};
IIndexMemoryReclaimer 的关键方法:
IIndexMemoryReclaimer 接口:提供内存回收的抽象:
flowchart TD
subgraph Interface["IIndexMemoryReclaimer 接口"]
I1["Retire<br/>标记待回收"]
I2["DropRetireItem<br/>删除待回收项"]
I3["TryReclaim<br/>尝试回收"]
I4["Reclaim<br/>强制回收"]
end
subgraph Lifecycle["生命周期"]
L1["使用中<br/>In Use"]
L2["待回收<br/>Retired"]
L3["已回收<br/>Reclaimed"]
L1 -->|Retire| L2
L2 -->|Reclaim| L3
end
I1 --> L2
I3 --> L3
style Interface fill:#e3f2fd
style Lifecycle fill:#fff3e0
- Retire():将内存加入回收队列,延迟回收
- DropRetireItem():取消回收,从回收队列中移除
- TryReclaim():尝试回收一些内存,不阻塞
- Reclaim():强制回收所有可回收的内存
4.2 内存回收机制
内存回收的机制:
内存回收机制:从 Retire 到 Reclaim 的回收流程:
flowchart TB
Start([内存回收流程<br/>Memory Reclaim Flow]) --> UseLayer[使用中阶段<br/>In Use Phase]
subgraph UseGroup["使用中 In Use"]
direction TB
U1[内存使用中<br/>Memory In Use<br/>内存正在被使用]
end
UseLayer --> RetireLayer[标记待回收阶段<br/>Retire Phase]
subgraph RetireGroup["标记待回收 Retire"]
direction TB
R1[标记待回收<br/>Retire<br/>标记为待回收状态]
R2[加入回收队列<br/>Add to Reclaim Queue<br/>加入延迟回收队列]
R1 --> R2
end
RetireLayer --> DelayLayer[延迟回收阶段<br/>Delayed Reclaim Phase]
subgraph DelayGroup["延迟回收 Delayed Reclaim"]
direction TB
D1[延迟回收<br/>Delayed Reclaim<br/>延迟一段时间后回收]
D2{内存紧张?<br/>Memory Pressure?}
D1 --> D2
end
DelayLayer --> ReclaimLayer[执行回收阶段<br/>Reclaim Phase]
subgraph ReclaimGroup["执行回收 Execute Reclaim"]
direction TB
E1[执行回收<br/>Reclaim<br/>尝试回收内存]
E2[释放内存<br/>Free Memory<br/>释放内存空间]
E1 --> E2
end
ReclaimLayer --> End([回收完成<br/>Reclaim Complete])
UseLayer -.->|包含| UseGroup
RetireLayer -.->|包含| RetireGroup
DelayLayer -.->|包含| DelayGroup
ReclaimLayer -.->|包含| ReclaimGroup
U1 --> R1
R2 --> D1
D2 -->|是| E1
D2 -->|否| D1
E2 --> End
style Start fill:#c8e6c9,stroke:#388e3c,stroke-width:3px
style End fill:#c8e6c9,stroke:#388e3c,stroke-width:3px
style UseLayer fill:#e3f2fd,stroke:#1976d2,stroke-width:3px
style RetireLayer fill:#fff3e0,stroke:#f57c00,stroke-width:3px
style DelayLayer fill:#f3e5f5,stroke:#7b1fa2,stroke-width:3px
style ReclaimLayer fill:#e8f5e9,stroke:#2e7d32,stroke-width:3px
style UseGroup fill:#e3f2fd,stroke:#1976d2,stroke-width:3px
style U1 fill:#90caf9,stroke:#1976d2,stroke-width:2px
style RetireGroup fill:#fff3e0,stroke:#f57c00,stroke-width:3px
style R1 fill:#ffcc80,stroke:#f57c00,stroke-width:2px
style R2 fill:#ffcc80,stroke:#f57c00,stroke-width:2px
style DelayGroup fill:#f3e5f5,stroke:#7b1fa2,stroke-width:3px
style D1 fill:#ce93d8,stroke:#7b1fa2,stroke-width:2px
style D2 fill:#ce93d8,stroke:#7b1fa2,stroke-width:2px
style ReclaimGroup fill:#e8f5e9,stroke:#2e7d32,stroke-width:3px
style E1 fill:#a5d6a7,stroke:#2e7d32,stroke-width:2px
style E2 fill:#a5d6a7,stroke:#2e7d32,stroke-width:2px
内存回收流程图:
graph TD
A[内存不再使用] --> B[Retire 加入回收队列]
B --> C[延迟回收]
C --> D{内存是否紧张?}
D -->|否| E[继续延迟]
D -->|是| F[TryReclaim 尝试回收]
E --> D
F --> G{回收成功?}
G -->|是| H[释放内存]
G -->|否| I[Reclaim 强制回收]
I --> H
H --> J[回收完成]
style C fill:#e3f2fd
style F fill:#fff3e0
style I fill:#f3e5f5
style H fill:#e8f5e9
回收机制详解:
内存回收是保证系统稳定性的关键机制。让我们通过序列图来理解完整的回收流程:
sequenceDiagram
participant Component as 组件
participant Reclaimer as IIndexMemoryReclaimer
participant RetireQueue as RetireQueue
participant MemoryQuota as MemoryQuotaController
Component->>Reclaimer: Retire(addr, deAllocator)
Reclaimer->>RetireQueue: AddRetireItem(addr, deAllocator)
RetireQueue-->>Reclaimer: itemId
Reclaimer-->>Component: itemId
Note over Reclaimer: 延迟回收,等待合适时机
MemoryQuota->>Reclaimer: 内存紧张,触发回收
Reclaimer->>Reclaimer: TryReclaim()
Reclaimer->>RetireQueue: GetRetireItems()
RetireQueue-->>Reclaimer: RetireItems
loop 遍历回收项
Reclaimer->>Reclaimer: 检查是否可以回收
alt 可以回收
Reclaimer->>Component: deAllocator(addr)
Component->>MemoryQuota: Free(quota)
MemoryQuota-->>Component: Success
end
end
alt 内存仍然紧张
Reclaimer->>Reclaimer: Reclaim()
Reclaimer->>RetireQueue: ForceReclaimAll()
RetireQueue-->>Reclaimer: Success
end
回收机制详解:
- Retire:将不再使用的内存加入回收队列,延迟回收
- 延迟回收:延迟回收可以避免频繁的内存分配和释放
- 回收队列:使用队列管理待回收的内存,支持优先级
- 回收标识:为每个回收项分配唯一标识,支持取消回收
- 延迟回收:延迟回收可以避免频繁的内存分配和释放
- 性能优化:延迟回收减少内存操作次数,提高性能
- 批量回收:可以批量回收多个内存块,提高回收效率
- 时机选择:在合适的时机(如内存紧张时)进行回收
- TryReclaim:在合适的时机尝试回收一些内存
- 非阻塞回收:TryReclaim 不阻塞,可以快速返回
- 部分回收:只回收部分内存,避免影响性能
- 智能回收:根据内存使用情况智能决定回收量
- Reclaim:在内存紧张时强制回收所有可回收的内存
- 强制回收:Reclaim 会强制回收所有可回收的内存
- 阻塞回收:Reclaim 可能会阻塞,直到回收完成
- 紧急回收:在内存严重不足时使用,保证系统稳定性
回收策略的优势:
- 性能优化:延迟回收减少内存操作次数,提高性能
- 稳定性保证:在内存紧张时强制回收,保证系统稳定性
- 灵活性:支持取消回收,适应动态场景
- 并发安全:支持并发回收,保证线程安全
4.3 内存回收策略
内存回收的策略:
内存回收策略:延迟回收、按需回收等策略:
flowchart TD
subgraph Strategies["回收策略"]
S1["延迟回收<br/>Delayed Reclaim"]
S2["按需回收<br/>On-Demand Reclaim"]
S3["批量回收<br/>Batch Reclaim"]
end
subgraph Triggers["触发条件"]
T1["内存紧张<br/>Memory Pressure"]
T2["定期回收<br/>Periodic Reclaim"]
T3["手动触发<br/>Manual Trigger"]
end
T1 --> S2
T2 --> S1
T3 --> S3
style Strategies fill:#e3f2fd
style Triggers fill:#fff3e0
回收策略:
- 延迟回收:通过 Retire() 延迟回收,避免频繁的内存操作
- 按需回收:在内存紧张时通过 TryReclaim() 按需回收
- 强制回收:在内存严重不足时通过 Reclaim() 强制回收
- 取消回收:通过 DropRetireItem() 取消不需要的回收
5. BuildResourceCalculator:构建资源计算器
5.1 BuildResourceCalculator 的结构
BuildResourceCalculator 是构建资源计算器,定义在 util/memory_control/BuildResourceCalculator.h 中:
// util/memory_control/BuildResourceCalculator.h
class BuildResourceCalculator
{
public:
// 获取当前总内存使用
static int64_t GetCurrentTotalMemoryUse(const BuildResourceMetricsPtr& metrics);
// 估算转储临时内存使用
static int64_t EstimateDumpTempMemoryUse(const BuildResourceMetricsPtr& metrics,
int dumpThreadCount);
// 估算转储扩展内存使用
static int64_t EstimateDumpExpandMemoryUse(const BuildResourceMetricsPtr& metrics);
// 估算转储文件大小
static int64_t EstimateDumpFileSize(const BuildResourceMetricsPtr& metrics);
};
BuildResourceCalculator 的关键方法:
BuildResourceCalculator 的方法:计算构建资源使用:
flowchart TD
subgraph Calculator["BuildResourceCalculator"]
C1["GetCurrentTotalMemoryUse<br/>当前总内存使用"]
C2["EstimateDumpTempMemoryUse<br/>估算转储临时内存"]
C3["EstimateDumpExpandMemoryUse<br/>估算转储扩展内存"]
end
subgraph Metrics["资源指标"]
M1["构建内存<br/>Build Memory"]
M2["转储内存<br/>Dump Memory"]
M3["索引内存<br/>Index Memory"]
end
C1 --> M1
C2 --> M2
C3 --> M2
style Calculator fill:#e3f2fd
style Metrics fill:#fff3e0
- GetCurrentTotalMemoryUse():获取当前总内存使用
- EstimateDumpTempMemoryUse():估算转储临时内存使用
- EstimateDumpExpandMemoryUse():估算转储扩展内存使用
- EstimateDumpFileSize():估算转储文件大小
5.2 构建资源估算
构建资源估算的流程:
构建资源估算:从 BuildResourceMetrics 到资源使用量的估算流程:
flowchart TD
subgraph Metrics["BuildResourceMetrics"]
M1["文档数量<br/>Doc Count"]
M2["索引大小<br/>Index Size"]
M3["字段数量<br/>Field Count"]
end
subgraph Estimate["资源估算"]
E1["估算构建内存<br/>Estimate Build Memory"]
E2["估算转储内存<br/>Estimate Dump Memory"]
E3["估算总内存<br/>Estimate Total Memory"]
end
M1 --> E1
M2 --> E2
M3 --> E1
E1 --> E3
E2 --> E3
style Metrics fill:#e3f2fd
style Estimate fill:#fff3e0
估算流程:
- 收集指标:从 BuildResourceMetrics 收集构建指标
- 计算内存使用:根据指标计算内存使用量
- 估算转储资源:估算转储时的临时内存和文件大小
- 返回估算结果:返回详细的资源使用估算结果
6. 内存分配策略
6.1 内存分配策略
内存分配的策略:
内存分配策略:按需分配、预留分配等策略(已在上面详细展示,此处不再重复):
分配策略:
- 按需分配:根据实际需求分配内存,灵活适应不同场景
- 预留分配:通过 Reserve() 预留内存,保证关键操作的内存
- 阻塞分配:Allocate() 会阻塞直到有可用内存
- 非阻塞分配:TryAllocate() 不阻塞,立即返回结果
6.2 内存分配优化
内存分配的优化:
内存分配优化:批量分配、内存池等优化策略:
flowchart TD
subgraph Optimization["优化策略"]
O1["批量分配<br/>Batch Allocation"]
O2["内存池<br/>Memory Pool"]
O3["对齐分配<br/>Aligned Allocation"]
end
subgraph Benefits["优化收益"]
B1["减少分配次数<br/>Reduce Allocations"]
B2["减少内存碎片<br/>Reduce Fragmentation"]
B3["提高访问效率<br/>Improve Access"]
end
O1 --> B1
O2 --> B2
O3 --> B3
style Optimization fill:#e3f2fd
style Benefits fill:#fff3e0
优化策略:
- 批量分配:批量分配内存,减少分配次数
- 内存池:使用内存池减少内存分配开销
- 对齐分配:内存对齐分配,提高访问效率
- 预分配:预分配常用大小的内存,减少分配延迟
7. 内存回收机制
7.1 内存回收时机
内存回收的时机:
内存回收时机:延迟回收、按需回收等时机:
flowchart TD
subgraph Timing["回收时机"]
T1["延迟回收<br/>Delayed Reclaim<br/>延迟一段时间后回收"]
T2["按需回收<br/>On-Demand Reclaim<br/>内存紧张时回收"]
T3["定期回收<br/>Periodic Reclaim<br/>定期触发回收"]
end
subgraph Conditions["触发条件"]
C1["内存使用率超过阈值"]
C2["配额不足"]
C3["定时器触发"]
end
C1 --> T2
C2 --> T2
C3 --> T3
style Timing fill:#e3f2fd
style Conditions fill:#fff3e0
回收时机:
- 延迟回收:通过 Retire() 延迟回收,在合适的时机回收
- 按需回收:在内存紧张时通过 TryReclaim() 按需回收
- 强制回收:在内存严重不足时通过 Reclaim() 强制回收
- 定期回收:定期触发回收,保持内存使用在合理范围
7.2 内存回收优化
内存回收的优化:
内存回收优化:批量回收、延迟回收等优化策略(已在上面详细展示,此处不再重复):
优化策略:
- 批量回收:批量回收内存,减少回收次数
- 延迟回收:延迟回收可以避免频繁的内存操作
- 智能回收:根据内存使用情况智能决定回收时机
- 并发回收:支持并发回收,提高回收效率
8. 内存优化策略
8.1 内存使用优化
内存使用的优化:
内存使用优化:内存池、缓存控制等优化策略:
flowchart TD
subgraph Optimization["优化策略"]
O1["内存池<br/>Memory Pool"]
O2["缓存控制<br/>Cache Control"]
O3["懒加载<br/>Lazy Loading"]
end
subgraph Benefits["优化收益"]
B1["减少内存分配开销"]
B2["控制内存使用上限"]
B3["按需加载减少内存占用"]
end
O1 --> B1
O2 --> B2
O3 --> B3
style Optimization fill:#e3f2fd
style Benefits fill:#fff3e0
优化策略:
- 内存池:使用内存池减少内存分配开销
- 缓存控制:控制缓存大小,避免内存溢出
- 内存压缩:压缩内存数据,减少内存使用
- 懒加载:按需加载数据,减少内存占用
8.2 内存监控与告警
内存监控与告警:
内存监控与告警:实时监控内存使用,及时告警:
flowchart TD
subgraph Monitor["监控"]
M1["实时统计<br/>Real-time Statistics"]
M2["内存使用率<br/>Memory Usage Rate"]
M3["配额使用率<br/>Quota Usage Rate"]
end
subgraph Alert["告警"]
A1["阈值告警<br/>Threshold Alert"]
A2["异常告警<br/>Anomaly Alert"]
A3["趋势告警<br/>Trend Alert"]
end
M1 --> A1
M2 --> A2
M3 --> A3
style Monitor fill:#e3f2fd
style Alert fill:#fff3e0
监控与告警:
- 实时监控:实时监控内存使用情况
- 阈值告警:当内存使用超过阈值时告警
- 统计分析:统计分析内存使用趋势
- 优化建议:根据监控数据提供优化建议
9. 内存管理的关键设计
9.1 层级配额管理
层级配额管理的设计:
层级配额管理:从根控制器到子控制器的层级结构:
flowchart TD
subgraph Root["根控制器"]
R1["总配额<br/>Total Quota"]
end
subgraph Partition["分区控制器"]
P1["分区配额<br/>Partition Quota"]
P2["分区配额<br/>Partition Quota"]
end
subgraph Tablet["Tablet 控制器"]
T1["Tablet配额<br/>Tablet Quota"]
T2["Tablet配额<br/>Tablet Quota"]
end
R1 --> P1
R1 --> P2
P1 --> T1
P2 --> T2
style Root fill:#e3f2fd
style Partition fill:#fff3e0
style Tablet fill:#f3e5f5
设计要点:
- 层级结构:支持多层级配额管理,灵活分配配额
- 配额继承:子控制器从父控制器继承配额
- 配额隔离:不同层级的配额相互隔离,避免相互影响
- 配额共享:支持配额共享,提高配额利用率
9.2 内存回收设计
内存回收的设计:
内存回收设计:延迟回收、按需回收等设计:
flowchart TD
subgraph Main["主要组件"]
A["延迟回收<br/>DelayedRecycle"]
B["按需回收<br/>OnDemandRecycle"]
C["并发安全<br/>ConcurrentSafe"]
end
subgraph Sub["子组件"]
D["资源释放<br/>ResourceRelease"]
E["内存清理<br/>MemoryCleanup"]
end
A --> D
B --> E
C --> D
style Main fill:#e3f2fd
style Sub fill:#fff3e0
设计要点:
- 延迟回收:延迟回收可以避免频繁的内存操作
- 按需回收:在内存紧张时按需回收,保证系统稳定性
- 并发安全:内存回收支持并发,保证线程安全
- 资源释放:及时释放不再使用的资源,避免内存泄漏
9.3 性能优化设计
性能优化的设计:
性能优化设计:内存池、批量操作等优化策略:
flowchart TD
subgraph Main["主要组件"]
A["内存池<br/>MemoryPool"]
B["批量操作<br/>BatchOperation"]
C["缓存优化<br/>CacheOptimization"]
end
subgraph Sub["子组件"]
D["资源控制<br/>ResourceControl"]
E["性能调优<br/>PerformanceTuning"]
end
A --> D
B --> E
C --> D
style Main fill:#e3f2fd
style Sub fill:#fff3e0
设计要点:
- 内存池:使用内存池减少内存分配开销
- 批量操作:批量分配和回收内存,减少操作次数
- 缓存优化:优化缓存策略,提高内存利用率
- 资源控制:控制资源使用,避免资源浪费
10. 性能优化与最佳实践
10.1 内存配额优化
优化策略:
- 配额分配优化:
- 动态调整:根据系统负载动态调整配额分配
- 配额预留:为关键操作预留配额,保证操作成功
- 配额共享:支持配额共享,提高配额利用率
- 层级管理优化:
- 层级设计:合理设计层级结构,平衡灵活性和复杂度
- 配额隔离:不同组件的配额相互隔离,避免相互影响
- 配额监控:监控每个层级的配额使用情况,及时调整
- 配额策略优化:
- 按需分配:根据实际需求分配配额,避免浪费
- 预留分配:为关键操作预留配额,保证操作成功
- 阻塞策略:合理使用阻塞和非阻塞分配,平衡性能和稳定性
10.2 内存回收优化
优化策略:
- 回收时机优化:
- 延迟回收:延迟回收减少内存操作次数,提高性能
- 按需回收:在内存紧张时按需回收,保证系统稳定性
- 定期回收:定期触发回收,保持内存使用在合理范围
- 回收策略优化:
- 批量回收:批量回收多个内存块,提高回收效率
- 智能回收:根据内存使用情况智能决定回收量
- 并发回收:支持并发回收,提高回收效率
- 回收性能优化:
- 回收队列优化:优化回收队列的数据结构,提高操作效率
- 回收算法优化:优化回收算法,减少回收开销
- 回收监控:监控回收性能,及时调整回收策略
10.3 内存使用优化
优化策略:
- 内存分配优化:
- 内存池:使用内存池减少内存分配开销
- 批量分配:批量分配内存,减少分配次数
- 对齐分配:内存对齐分配,提高访问效率
- 内存使用优化:
- 懒加载:按需加载数据,减少内存占用
- 内存压缩:压缩内存数据,减少内存使用
- 缓存控制:控制缓存大小,避免内存溢出
- 内存监控优化:
- 实时监控:实时监控内存使用情况,及时发现内存问题
- 统计分析:统计分析内存使用趋势,预测内存需求
- 告警机制:设置告警阈值,及时告警内存问题
11. 小结
内存管理与资源控制是 IndexLib 的核心功能,通过 MemoryQuotaController、TabletMemoryCalculator、IIndexMemoryReclaimer 等组件实现。通过本文的深入解析,我们了解到:
核心组件:
- MemoryQuotaController:内存配额控制器,管理内存配额和分配,支持层级配额管理
- 配额管理:为每个组件分配内存配额,控制内存使用上限
- 层级管理:支持层级配额管理,灵活分配配额
- 配额预留:通过预留机制保证关键操作的配额
- TabletMemoryCalculator:Tablet 内存计算器,计算 Tablet 的内存使用,监控内存状态
- 实时统计:实时统计各个组件的内存使用量
- 分类统计:按类型统计内存使用(构建、查询、索引等)
- 监控告警:根据统计结果监控内存状态,及时告警
- IIndexMemoryReclaimer:索引内存回收器,回收不再使用的内存,释放内存空间
- 延迟回收:延迟回收避免频繁的内存操作
- 按需回收:在内存紧张时按需回收,保证系统稳定性
- 并发安全:支持并发回收,保证线程安全
- BuildResourceCalculator:构建资源计算器,计算构建时的资源使用,优化构建效率
- 资源估算:估算构建和转储所需的资源
- 资源预留:预留构建和转储所需的资源
- 资源控制:控制资源使用,避免资源浪费
设计亮点:
- 层级配额管理:支持多层级配额管理,灵活分配配额,配额相互隔离
- 延迟回收机制:延迟回收减少内存操作次数,提高性能
- 按需回收策略:在内存紧张时按需回收,保证系统稳定性
- 资源估算机制:通过资源估算优化资源使用,提高构建效率
- 内存监控体系:实时监控内存使用,及时发现和解决内存问题
性能优化:
- 内存利用率:通过配额控制和回收机制,有效提升内存利用率
- 内存分配性能:内存池和批量分配显著提高分配性能
- 内存回收性能:延迟回收和批量回收显著提高回收性能
- 系统稳定性:配额控制和回收机制大幅降低 OOM 风险
理解内存管理与资源控制,是掌握 IndexLib 资源管理机制的关键。在下一篇文章中,我们将深入介绍索引类型的实现细节,包括 NormalTable、KVTable、KKVTable 等不同索引类型的特点、实现原理和使用场景。