IndexLib(7):内存管理与资源控制

13 分钟阅读

发布于:

在上一篇文章中,我们深入了解了 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 的内存管理包括以下核心概念:

  1. MemoryQuotaController:内存配额控制器,管理内存配额和分配
  2. TabletMemoryCalculator:Tablet 内存计算器,计算 Tablet 的内存使用
  3. IIndexMemoryReclaimer:索引内存回收器,回收不再使用的内存
  4. 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

分配机制详解

  1. 根控制器分配:根控制器有固定的总配额
    • 总配额设置:根控制器在创建时设置总配额
    • 配额分配:根控制器将配额分配给子控制器
    • 配额监控:根控制器监控总配额的使用情况
  2. 子控制器分配:子控制器从父控制器分配配额
    • 配额请求:子控制器向父控制器请求配额
    • 配额传递:父控制器将配额传递给子控制器
    • 配额隔离:不同子控制器的配额相互隔离
  3. 配额预留:通过 Reserve() 预留配额,保证后续分配
    • 预留机制:预留配额不会被其他操作占用
    • 预留释放:预留的配额可以释放,返回给父控制器
    • 预留用途:用于保证关键操作(如转储)的配额
  4. 配额释放:通过 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: 返回统计结果

统计流程详解

  1. 收集组件信息:从 TabletWriter 和 TabletReaderContainer 收集组件信息
    • 构建组件:收集构建中的 MemSegment、转储中的 Segment 等
    • 查询组件:收集 TabletReader、IndexReader 等查询组件
    • 索引组件:收集各种 Indexer(倒排、正排、主键等)
  2. 计算各组件内存:计算各个组件的内存使用量
    • Segment 内存:计算 MemSegment 和 DiskSegment 的内存使用
    • 索引内存:计算各个 Indexer 的内存使用
    • 缓存内存:计算缓存的内存使用
  3. 汇总内存使用:汇总所有组件的内存使用量
    • 分类汇总:按类型汇总内存使用(构建、查询、索引等)
    • 总内存使用:计算总的内存使用量
    • 内存占比:计算各组件内存占总内存的比例
  4. 返回统计结果:返回详细的内存使用统计结果
    • 详细统计:返回各个组件的详细内存使用量
    • 统计报告:生成内存使用统计报告
    • 监控数据:提供监控数据,用于告警和调优

统计的用途

  • 内存监控:实时监控内存使用情况,及时发现内存问题
  • 性能调优:根据统计结果优化内存分配,提高内存利用率
  • 资源规划:根据统计结果规划内存资源,合理分配配额
  • 问题诊断:通过统计结果诊断内存问题,定位内存泄漏

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

回收机制详解

  1. Retire:将不再使用的内存加入回收队列,延迟回收
    • 延迟回收:延迟回收可以避免频繁的内存分配和释放
    • 回收队列:使用队列管理待回收的内存,支持优先级
    • 回收标识:为每个回收项分配唯一标识,支持取消回收
  2. 延迟回收:延迟回收可以避免频繁的内存分配和释放
    • 性能优化:延迟回收减少内存操作次数,提高性能
    • 批量回收:可以批量回收多个内存块,提高回收效率
    • 时机选择:在合适的时机(如内存紧张时)进行回收
  3. TryReclaim:在合适的时机尝试回收一些内存
    • 非阻塞回收:TryReclaim 不阻塞,可以快速返回
    • 部分回收:只回收部分内存,避免影响性能
    • 智能回收:根据内存使用情况智能决定回收量
  4. 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

估算流程

  1. 收集指标:从 BuildResourceMetrics 收集构建指标
  2. 计算内存使用:根据指标计算内存使用量
  3. 估算转储资源:估算转储时的临时内存和文件大小
  4. 返回估算结果:返回详细的资源使用估算结果

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 内存配额优化

优化策略

  1. 配额分配优化
    • 动态调整:根据系统负载动态调整配额分配
    • 配额预留:为关键操作预留配额,保证操作成功
    • 配额共享:支持配额共享,提高配额利用率
  2. 层级管理优化
    • 层级设计:合理设计层级结构,平衡灵活性和复杂度
    • 配额隔离:不同组件的配额相互隔离,避免相互影响
    • 配额监控:监控每个层级的配额使用情况,及时调整
  3. 配额策略优化
    • 按需分配:根据实际需求分配配额,避免浪费
    • 预留分配:为关键操作预留配额,保证操作成功
    • 阻塞策略:合理使用阻塞和非阻塞分配,平衡性能和稳定性

10.2 内存回收优化

优化策略

  1. 回收时机优化
    • 延迟回收:延迟回收减少内存操作次数,提高性能
    • 按需回收:在内存紧张时按需回收,保证系统稳定性
    • 定期回收:定期触发回收,保持内存使用在合理范围
  2. 回收策略优化
    • 批量回收:批量回收多个内存块,提高回收效率
    • 智能回收:根据内存使用情况智能决定回收量
    • 并发回收:支持并发回收,提高回收效率
  3. 回收性能优化
    • 回收队列优化:优化回收队列的数据结构,提高操作效率
    • 回收算法优化:优化回收算法,减少回收开销
    • 回收监控:监控回收性能,及时调整回收策略

10.3 内存使用优化

优化策略

  1. 内存分配优化
    • 内存池:使用内存池减少内存分配开销
    • 批量分配:批量分配内存,减少分配次数
    • 对齐分配:内存对齐分配,提高访问效率
  2. 内存使用优化
    • 懒加载:按需加载数据,减少内存占用
    • 内存压缩:压缩内存数据,减少内存使用
    • 缓存控制:控制缓存大小,避免内存溢出
  3. 内存监控优化
    • 实时监控:实时监控内存使用情况,及时发现内存问题
    • 统计分析:统计分析内存使用趋势,预测内存需求
    • 告警机制:设置告警阈值,及时告警内存问题

11. 小结

内存管理与资源控制是 IndexLib 的核心功能,通过 MemoryQuotaController、TabletMemoryCalculator、IIndexMemoryReclaimer 等组件实现。通过本文的深入解析,我们了解到:

核心组件

  • MemoryQuotaController:内存配额控制器,管理内存配额和分配,支持层级配额管理
    • 配额管理:为每个组件分配内存配额,控制内存使用上限
    • 层级管理:支持层级配额管理,灵活分配配额
    • 配额预留:通过预留机制保证关键操作的配额
  • TabletMemoryCalculator:Tablet 内存计算器,计算 Tablet 的内存使用,监控内存状态
    • 实时统计:实时统计各个组件的内存使用量
    • 分类统计:按类型统计内存使用(构建、查询、索引等)
    • 监控告警:根据统计结果监控内存状态,及时告警
  • IIndexMemoryReclaimer:索引内存回收器,回收不再使用的内存,释放内存空间
    • 延迟回收:延迟回收避免频繁的内存操作
    • 按需回收:在内存紧张时按需回收,保证系统稳定性
    • 并发安全:支持并发回收,保证线程安全
  • BuildResourceCalculator:构建资源计算器,计算构建时的资源使用,优化构建效率
    • 资源估算:估算构建和转储所需的资源
    • 资源预留:预留构建和转储所需的资源
    • 资源控制:控制资源使用,避免资源浪费

设计亮点

  1. 层级配额管理:支持多层级配额管理,灵活分配配额,配额相互隔离
  2. 延迟回收机制:延迟回收减少内存操作次数,提高性能
  3. 按需回收策略:在内存紧张时按需回收,保证系统稳定性
  4. 资源估算机制:通过资源估算优化资源使用,提高构建效率
  5. 内存监控体系:实时监控内存使用,及时发现和解决内存问题

性能优化

  • 内存利用率:通过配额控制和回收机制,有效提升内存利用率
  • 内存分配性能:内存池和批量分配显著提高分配性能
  • 内存回收性能:延迟回收和批量回收显著提高回收性能
  • 系统稳定性:配额控制和回收机制大幅降低 OOM 风险

理解内存管理与资源控制,是掌握 IndexLib 资源管理机制的关键。在下一篇文章中,我们将深入介绍索引类型的实现细节,包括 NormalTable、KVTable、KKVTable 等不同索引类型的特点、实现原理和使用场景。