SeaStore 架构分析与流程

SeaStore 架构分析与流程

1. 概述

SeaStore 是 Ceph Crimson OSD 的存储引擎实现,基于 Seastar 框架构建。它采用日志结构存储(Log-Structured Storage)设计,提供高性能的异步存储操作。

1.1 核心特性

  • 日志结构存储:所有写入都追加到日志中,提供顺序写入性能
  • 事务性操作:支持 ACID 特性的事务处理
  • 多 Shard 架构:每个 CPU 核心独立处理 I/O,充分利用多核性能
  • 异步清理机制:后台自动回收和整理存储空间
  • 多设备支持:支持段式设备(Segment)和随机块设备(Random Block)

2. 架构层次

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
┌─────────────────────────────────────────────────────────┐
│ SeaStore (API层) │
│ - 实现 FuturizedStore 接口 │
│ - 提供对象存储操作 (read/write/omap/attr) │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│ TransactionManager (事务管理层) │
│ - 事务创建和管理 │
│ - 逻辑地址到物理地址映射 (LBA) │
│ - Extent 分配和读取 │
└─────────────────────────────────────────────────────────┘

┌───────────────────┴───────────────────┐
│ │
┌───────────────────┐ ┌──────────────────────┐
│ Cache (缓存层) │ │ Journal (日志层) │
│ - Extent 缓存 │ │ - 事务记录写入 │
│ - 事务状态管理 │ │ - 日志回放 │
│ - 脏页管理 │ │ - 日志裁剪 │
└───────────────────┘ └──────────────────────┘
│ │
└───────────────────┬───────────────────┘

┌─────────────────────────────────────────────────────────┐
│ ExtentPlacementManager (放置管理层) │
│ - Extent 物理地址分配 │
│ - 段管理和分配 │
└─────────────────────────────────────────────────────────┘

┌───────────────────┴───────────────────┐
│ │
┌───────────────────┐ ┌──────────────────────┐
│ SegmentManager │ │ RandomBlockManager │
│ (段式设备) │ │ (随机块设备) │
└───────────────────┘ └──────────────────────┘

3. 核心组件详解

3.1 SeaStore

位置: seastore.h, seastore.cc

职责:

  • 实现 FuturizedStore 接口,提供对象存储操作
  • 管理多个 Shard,每个 Shard 处理一个 CPU 核心的 I/O
  • 提供对象数据、OMAP、属性等存储功能

关键方法:

1
2
3
4
5
6
7
8
9
10
class SeaStore::Shard {
// 对象操作
seastar::future<struct stat> stat(CollectionRef, const ghobject_t&);
read_errorator::future<ceph::bufferlist> read(...);
seastar::future<> do_transaction_no_callbacks(...);

// 集合操作
seastar::future<CollectionRef> create_new_collection(...);
seastar::future<CollectionRef> open_collection(...);
}

3.2 TransactionManager

位置: transaction_manager.h, transaction_manager.cc

职责:

  • 管理事务生命周期
  • 提供逻辑地址到物理地址的映射(通过 LBA Manager)
  • 管理 Extent 的分配和读取
  • 提交事务到 Journal

关键方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class TransactionManager {
// 事务创建
TransactionRef create_transaction(Transaction::src_t, const char*, ...);

// Extent 操作
template<typename T>
read_extent_ret<T> read_extent(Transaction&, laddr_t, extent_len_t);

template<typename T>
alloc_extent_ret<T> alloc_non_data_extent(...);

// 事务提交
submit_transaction_iertr::future<> submit_transaction(Transaction&);
}

3.3 Cache

位置: cache.h, cache.cc

职责:

  • 管理 Extent 缓存
  • 处理事务的 read_set、write_set、retired_set
  • 提供 Extent 的读取和分配
  • 管理脏页列表

事务三阶段:

  1. 构造阶段: 用户调用 Cache::create_transaction() 并填充事务
  2. 提交阶段: 用户调用 Cache::try_start_transaction(),如果成功则提交到 Journal
  3. 完成阶段: 事务持久化后,调用 Cache::complete_commit()

关键数据结构:

1
2
3
4
5
class Cache {
ExtentIndex extents_index; // 所有缓存的 extent
CachedExtent::primary_ref_list dirty; // 脏页列表
RootBlockRef root; // 根块引用
}

3.4 Journal

位置: journal.h, journal/segmented_journal.cc

职责:

  • 将事务记录写入持久化存储
  • 提供日志回放功能
  • 管理日志空间和裁剪

关键方法:

1
2
3
4
5
6
7
8
9
10
11
class Journal {
// 提交记录
submit_record_ertr::future<> submit_record(
record_t&&, OrderingHandle&, ...);

// 日志回放
replay_ret replay(delta_handler_t&&);

// 刷新
seastar::future<> flush(OrderingHandle&);
}

3.5 SegmentManager

位置: segment_manager.h, segment_manager.cc

职责:

  • 管理段式存储设备
  • 提供段的分配、释放、写入操作
  • 跟踪段的状态(EMPTY/OPEN/CLOSED)

段状态:

  • EMPTY: 空段,可以分配
  • OPEN: 正在写入的段
  • CLOSED: 已关闭的段,等待清理

3.6 OnodeManager

位置: onode_manager.h, onode_manager/staged-fltree/fltree_onode_manager.cc

职责:

  • 管理对象节点(Onode)的创建、查找、删除
  • 维护 Onode 树结构
  • 提供对象列表功能

关键方法:

1
2
3
4
5
6
class OnodeManager {
get_onode_ret get_onode(Transaction&, const ghobject_t&);
get_or_create_onode_ret get_or_create_onode(...);
erase_onode_ret erase_onode(Transaction&, OnodeRef&);
list_onodes_ret list_onodes(...);
}

3.7 LBAManager

位置: lba_manager.h, lba/btree_lba_manager.cc

职责:

  • 管理逻辑地址(LBA)到物理地址(PBA)的映射
  • 提供 B+ 树结构存储映射关系
  • 支持间接映射(用于克隆操作)

关键概念:

  • 直接映射: LBA 直接映射到物理地址
  • 间接映射: LBA 映射到另一个 LBA,用于实现写时复制(COW)

3.8 AsyncCleaner

位置: async_cleaner.h, async_cleaner.cc

职责:

  • 后台清理已关闭的段
  • 回收无效数据占用的空间
  • 管理段的空间利用率

清理策略:

  • 基于段的利用率选择清理目标
  • 支持多种 GC 算法(GREEDY, BENEFIT, COST_BENEFIT)

4. 数据流程

4.1 写入流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
用户写入请求


SeaStore::Shard::do_transaction_no_callbacks()


解析 Transaction 操作


TransactionManager::submit_transaction()

├─► Cache::prepare_record() // 准备记录

├─► Journal::submit_record() // 写入日志

├─► ExtentPlacementManager::dispatch() // 分配物理地址

└─► Cache::complete_commit() // 完成提交

详细步骤:

  1. 事务创建: TransactionManager::create_transaction()

    • 创建新的事务对象
    • 初始化事务 ID 和状态
  2. 操作执行: 在事务中执行各种操作

    • _write(): 写入对象数据
    • _setattrs(): 设置属性
    • _omap_set_keys(): 设置 OMAP 键值
  3. 事务提交: TransactionManager::submit_transaction()

    • 准备记录: Cache::prepare_record()
      • 收集所有 dirty extents
      • 构建 record_t 结构
    • 提交到 Journal: Journal::submit_record()
      • 写入日志段
      • 等待持久化完成
    • 完成提交: Cache::complete_commit()
      • 更新 extent 的物理地址
      • 标记事务完成

4.2 读取流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
用户读取请求


SeaStore::Shard::read()


TransactionManager::with_transaction_intr()

├─► OnodeManager::get_onode() // 获取对象节点

├─► ObjectDataHandler::read() // 读取对象数据

├─► TransactionManager::read_extent() // 读取 extent

├─► LBAManager::get_mapping() // 获取 LBA 映射

└─► Cache::get_extent_if_cached() // 从缓存获取或读取磁盘

详细步骤:

  1. 创建读事务: TransactionManager::create_transaction(READ)

  2. 获取 Onode: OnodeManager::get_onode()

    • 从 Onode 树中查找对象
    • 返回 Onode 引用
  3. 读取数据: ObjectDataHandler::read()

    • 根据 Onode 中的 LBA 地址读取数据
    • 通过 TransactionManager::read_extent() 读取 extent
  4. LBA 映射: LBAManager::get_mapping()

    • 查找逻辑地址对应的物理地址
    • 处理间接映射
  5. 缓存查找: Cache::get_extent_if_cached()

    • 首先在事务的 read_set 中查找
    • 然后在 Cache 的 extents_index 中查找
    • 如果未命中,从磁盘读取

4.3 事务冲突处理

SeaStore 使用乐观并发控制(OCC)处理事务冲突:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
事务提交时


检查 read_set 中所有 extent 是否仍然有效

├─► 全部有效 → 提交成功

└─► 有无效 extent → 事务冲突


返回 eagain


用户重试事务

冲突检测:

  • 每个 extent 维护一个版本号或序列号
  • 事务提交时检查 read_set 中所有 extent 的版本
  • 如果版本发生变化,说明发生了冲突

4.4 日志回放流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
系统启动


Journal::replay()


读取日志记录

├─► 对于每个 delta:
│ │
│ ├─► Cache::replay_delta()
│ │
│ ├─► 读取相关 extent
│ │
│ └─► 应用 delta 变更

└─► 重建 Cache 状态

回放步骤:

  1. 扫描日志: 从日志头开始扫描所有记录

  2. 应用 Delta: 对于每个 delta

    • 读取对应的 extent
    • 应用 delta 变更
    • 标记 extent 为 dirty
  3. 重建索引:

    • 重建 LBA 树
    • 重建 Onode 树
    • 重建其他元数据结构
  4. 验证一致性: 检查所有数据结构的一致性

4.5 清理流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
后台清理任务


AsyncCleaner::clean_space()

├─► 选择要清理的段
│ │
│ └─► 基于利用率、时间等策略

├─► 扫描段中的有效 extent
│ │
│ └─► BackrefManager::retrieve_backref_extents_in_range()

├─► 重写有效 extent 到新段
│ │
│ └─► TransactionManager::rewrite_extent()

└─► 释放旧段空间

清理策略:

  1. 段选择:

    • 计算每个段的利用率
    • 选择利用率低的段进行清理
  2. Extent 扫描:

    • 使用 BackrefManager 查找段中的有效 extent
    • 确定哪些 extent 仍然被引用
  3. 重写:

    • 将有效 extent 写入新段
    • 更新 LBA 映射
    • 提交事务
  4. 释放:

    • 标记旧段为空
    • 更新空间跟踪器

5. 关键数据结构

5.1 Transaction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Transaction {
// 事务集合
read_set_t read_set; // 读取的 extent
write_set_t write_set; // 写入的 extent
retired_set_t retired_set; // 废弃的 extent

// 块列表
fresh_block_list_t fresh_block_list; // 新分配的块
mutated_block_list_t mutated_block_list; // 修改的块

// 元数据
transaction_id_t trans_id; // 事务 ID
Transaction::src_t src; // 事务来源
OrderingHandleRef handle; // 排序句柄
}

5.2 CachedExtent

1
2
3
4
5
6
7
8
9
10
11
12
13
class CachedExtent {
// 状态
extent_state_t state; // CLEAN/DIRTY/PENDING
paddr_t paddr; // 物理地址
laddr_t laddr; // 逻辑地址(如果是逻辑 extent)

// 版本控制
extent_version_t version; // 版本号
journal_seq_t dirty_from; // 变脏的日志序列号

// 引用计数
read_transaction_set_t read_transactions; // 读取事务集合
}

5.3 LBAMapping

1
2
3
4
5
6
7
8
struct LBAMapping {
laddr_t key; // 逻辑地址
paddr_t val; // 物理地址
extent_len_t length; // 长度
extent_ref_count_t refcount; // 引用计数
bool is_indirect; // 是否为间接映射
checksum_t checksum; // 校验和
}

5.4 record_t

1
2
3
4
5
6
7
struct record_t {
journal_seq_t seq; // 日志序列号
std::vector<CachedExtentRef> extents; // Extent 列表
std::vector<delta_info_t> deltas; // Delta 列表
extent_len_t dlength; // 数据长度
extent_len_t mlength; // 元数据长度
}

6. 地址空间

6.1 逻辑地址 (LBA)

  • 范围: L_ADDR_MINL_ADDR_MAX
  • 用途: 用户可见的地址空间
  • 管理: 由 LBAManager 通过 B+ 树管理

6.2 物理地址 (PBA)

SeaStore 支持两种物理地址类型:

  1. 段地址 (Segment Address):

    • paddr_t::make_seg_paddr(segment_id, offset)
    • 用于段式设备
  2. 块地址 (Block Address):

    • paddr_t::make_blk_paddr(device_id, offset)
    • 用于随机块设备

6.3 地址转换

1
2
3
4
5
6
7
8
逻辑地址 (LBA)


LBAManager::get_mapping()

├─► 直接映射 → 物理地址

└─► 间接映射 → 中间 LBA → 物理地址

7. 并发控制

7.1 Shard 隔离

  • 每个 CPU 核心运行一个独立的 Shard
  • Shard 之间通过消息传递通信
  • 每个 Shard 有独立的 Cache 和 TransactionManager

7.2 事务隔离

  • 读事务: 可以并发执行
  • 写事务: 通过 OrderingHandle 保证顺序
  • 冲突检测: 使用乐观并发控制

7.3 锁机制

  • Extent 锁: 每个 extent 维护读事务集合
  • 段锁: 段写入时加锁
  • 集合锁: SeastoreCollection::ordering_lock 保证操作顺序

8. 性能优化

8.1 缓存策略

  • LRU 缓存: 使用 ExtentPinboard 管理缓存优先级
  • 部分读取: 支持 extent 的部分范围读取
  • 预取: 可以预取相关 extent

8.2 写入优化

  • 批量提交: 多个操作合并到一个事务
  • 顺序写入: 日志结构保证顺序写入性能
  • 异步提交: 写入操作异步完成

8.3 清理优化

  • 增量清理: 每次只清理部分段
  • 智能选择: 基于利用率选择清理目标
  • 并行清理: 可以并行处理多个段

9. 错误处理

9.1 错误类型

  • input_output_error: I/O 错误
  • enospc: 空间不足
  • eagain: 事务冲突,需要重试
  • enoent: 对象不存在
  • value_too_large: 值过大

9.2 错误恢复

  • 事务回滚: 冲突时自动回滚
  • 日志回放: 系统重启时从日志恢复
  • 一致性检查: 定期检查数据结构一致性

10. 总结

SeaStore 是一个高性能的日志结构存储引擎,具有以下特点:

  1. 架构清晰: 分层设计,职责明确
  2. 高性能: 充分利用多核和异步 I/O
  3. 可靠性: 事务性保证和数据一致性
  4. 可扩展: 支持多种存储设备和配置

通过日志结构存储和异步清理机制,SeaStore 在保证数据一致性的同时,提供了优异的写入性能。

文章互动

阅读 --

留言

0 条留言

正在加载留言…