存储系统深度解析:Jellyfish Merkle树的应用
【免费下载链接】diem 项目地址: https://gitcode.com/gh_mirrors/diem/diem
本文深入探讨了Diem区块链中Jellyfish Merkle树的核心应用与实现机制。文章首先分析了存储接口设计与数据持久化架构,详细介绍了基于RocksDB的存储系统如何通过DbReader和DbWriter接口实现高效数据访问。接着阐述了Jellyfish Merkle树的三种核心节点类型(内部节点、叶子节点、节点键)的数据结构设计,重点解析了其哈希计算机制和序列化优化策略。最后探讨了状态同步与备份恢复机制,以及针对数据库性能的多维度优化策略,包括批量写入、内存缓存、数据压缩和并发控制等技术。
存储接口设计与数据持久化
Diem区块链的存储系统采用了精心设计的接口架构,实现了高效的数据持久化机制。整个存储架构基于RocksDB构建,通过抽象化的接口层和模式化的数据管理,为Jellyfish Merkle树提供了可靠的底层支持。
核心接口设计
Diem存储系统定义了清晰的读写接口,通过DbReader和DbWriter两个核心trait来抽象数据访问操作:
pub trait DbReader: Send + Sync {
fn get_epoch_ending_ledger_infos(
&self,
start_epoch: u64,
end_epoch: u64,
) -> Result<EpochChangeProof>;
fn get_transactions(
&self,
start_version: Version,
batch_size: u64,
ledger_version: Version,
fetch_events: bool,
) -> Result<TransactionListWithProof>;
fn get_latest_account_state(
&self,
address: AccountAddress
) -> Result<Option<AccountStateBlob>>;
// 其他查询方法...
}
pub trait DbWriter: Send + Sync {
fn save_transactions(
&self,
txns_to_commit: &[TransactionToCommit],
first_version: Version,
ledger_info_with_sigs: Option<&LedgerInfoWithSignatures>,
) -> Result<()>;
}
数据持久化流程
数据持久化过程采用原子性操作,确保状态一致性。以下是事务保存的核心流程:
模式化数据管理
Diem使用schemadb库对RocksDB进行封装,实现了类型安全的模式化数据存储:
// 模式定义示例
define_schema!(
JellyfishMerkleNodeSchema,
NodeKey,
Node,
"jellyfish_merkle_node"
);
define_schema!(
StaleNodeIndexSchema,
StaleNodeIndex,
(),
"stale_node_index"
);
每个模式定义了键值类型和列族名称,确保数据结构的严格一致性。
原子批处理机制
数据持久化采用SchemaBatch进行原子性批处理操作:
pub struct SchemaBatch {
rows: HashMap<ColumnFamilyName, BTreeMap<Vec<u8>, WriteOp>>,
}
impl SchemaBatch {
pub fn put<S: Schema>(&mut self, key: &S::Key, value: &S::Value) -> Result<()> {
let key = <S::Key as KeyCodec<S>>::encode_key(key)?;
let value = <S::Value as ValueCodec<S>>::encode_value(value)?;
self.rows
.entry(S::COLUMN_FAMILY_NAME)
.or_insert_with(BTreeMap::new)
.insert(key, WriteOp::Value(value));
Ok(())
}
pub fn delete<S: Schema>(&mut self, key: &S::Key) -> Result<()> {
let key = <S::Key as KeyCodec<S>>::encode_key(key)?;
self.rows
.entry(S::COLUMN_FAMILY_NAME)
.or_insert_with(BTreeMap::new)
.insert(key, WriteOp::Deletion);
Ok(())
}
}
Jellyfish Merkle树集成
StateStore作为Jellyfish Merkle树与底层存储的桥梁,实现了TreeReader和TreeWriter接口:
impl TreeReader<AccountStateBlob> for StateStore {
fn get_node_option(&self, node_key: &NodeKey) -> Result<Option<Node>> {
self.db.get::<JellyfishMerkleNodeSchema>(node_key)
}
fn get_rightmost_leaf(&self) -> Result<Option<(NodeKey, LeafNode)>> {
// 优化算法查找最右侧叶子节点
// ...
}
}
impl TreeWriter<AccountStateBlob> for StateStore {
fn write_node_batch(&self, node_batch: &NodeBatch) -> Result<()> {
let mut batch = SchemaBatch::new();
add_node_batch(&mut batch, node_batch)?;
self.db.write_schemas(batch)
}
}
列族设计
DiemDB使用多个列族来组织不同类型的数据,提高查询效率和存储优化:
| 列族名称 | 存储内容 | 用途 |
|---|---|---|
default | LedgerInfo | 账本信息 |
jellyfish_merkle_node | Jellyfish节点 | 状态树节点 |
stale_node_index | 过期节点索引 | 垃圾回收 |
transaction_info | 交易信息 | 交易元数据 |
event_by_version | 事件数据 | 事件查询 |
性能优化特性
存储接口设计包含多项性能优化措施:
- 批量处理:支持批量写入节点数据,减少I/O操作
- 原子提交:确保数据一致性,避免部分写入
- 内存管理:合理的缓存策略和内存分配
- 压缩优化:使用LZ4压缩算法减少存储空间
- 监控指标:详细的性能指标收集和报告
错误处理与恢复
系统实现了完善的错误处理机制:
#[derive(Debug, Deserialize, Error, PartialEq, Eq, Serialize)]
pub enum Error {
#[error("Service error: {:?}", error)]
ServiceError { error: String },
#[error("Serialization error: {0}")]
SerializationError(String),
#[error("Too many items requested: {0}, max allowed: {1}")]
TooManyRequested(u64, u64),
}
这种设计确保了存储系统的可靠性和数据完整性,为Diem区块链的高性能运行提供了坚实基础。
Jellyfish Merkle树数据结构
Jellyfish Merkle树是Diem区块链中用于状态存储的核心数据结构,它结合了稀疏Merkle树和Patricia Merkle树的优点,在保证密码学安全性的同时提供了高效的存储和查询性能。该数据结构通过巧妙的节点设计和压缩优化,实现了对区块链状态的高效管理。
核心节点类型
Jellyfish Merkle树包含三种主要的节点类型,每种节点都有其特定的结构和功能:
1. 内部节点(InternalNode)
内部节点是Jellyfish Merkle树的核心构建块,它代表了一个4层的二进制子树,最多可以包含16个子节点。这种设计将标准Merkle树中的31个节点压缩为单个节点,显著减少了I/O操作次数。
数据结构定义:
pub struct InternalNode {
// 最多16个子节点,使用HashMap存储,键为Nibble(4位),值为Child结构
children: Children, // Children = HashMap<Nibble, Child>
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Child {
pub hash: HashValue, // 子节点的哈希值
pub version: Version, // 子节点创建的版本号
pub is_leaf: bool, // 标识是否为叶子节点
}
位图机制: 内部节点使用两个16位的位图来高效管理子节点:
- 存在位图(Existence Bitmap):标记哪些子节点位置存在有效节点
- 叶子位图(Leaf Bitmap):标记哪些子节点是叶子节点
pub fn generate_bitmaps(&self) -> (u16, u16) {
let mut existence_bitmap = 0;
let mut leaf_bitmap = 0;
for (nibble, child) in self.children.iter() {
let i = u8::from(*nibble);
existence_bitmap |= 1u16 << i;
if child.is_leaf {
leaf_bitmap |= 1u16 << i;
}
}
(existence_bitmap, leaf_bitmap)
}
2. 叶子节点(LeafNode)
叶子节点存储实际的键值对数据,包含完整的账户地址哈希和对应的状态值。
数据结构定义:
pub struct LeafNode<V> {
account_key: HashValue, // 哈希后的账户地址
value_hash: HashValue, // 值的哈希
value: V, // 实际存储的值
}
叶子节点的哈希计算基于账户密钥和值的哈希:
fn hash(&self) -> HashValue {
SparseMerkleLeafNode::new(self.account_key, self.value_hash).hash()
}
3. 节点键(NodeKey)
每个节点在树中都有唯一的标识符,由版本号和nibble路径组成:
pub struct NodeKey {
version: Version, // 节点创建的版本号
nibble_path: NibblePath, // 节点的nibble路径
}
哈希计算机制
Jellyfish Merkle树采用递归的哈希计算方式,内部节点的哈希基于其子节点的哈希值:
哈希计算算法:
fn merkle_hash(&self, start: u8, width: u8, bitmaps: (u16, u16)) -> HashValue {
let (existence_bitmap, leaf_bitmap) = bitmaps;
// 处理空子树
if existence_bitmap == 0 {
return *SPARSE_MERKLE_PLACEHOLDER_HASH;
}
// 处理单个叶子节点
if width == 1 || (existence_bitmap.count_ones() == 1 && leaf_bitmap != 0) {
return self.get_only_child_hash(existence_bitmap);
}
// 递归计算左右子树
let left_hash = self.merkle_hash(start, width / 2, bitmaps);
let right_hash = self.merkle_hash(start + width / 2, width / 2, bitmaps);
SparseMerkleInternalNode::new(left_hash, right_hash).hash()
}
序列化与反序列化
Jellyfish Merkle树节点采用紧凑的二进制格式进行序列化,优化存储空间:
内部节点序列化格式:
+----------------+----------------+-------------------+---------------+
| 存在位图(2字节) | 叶子位图(2字节) | 子节点版本(varint) | 子节点哈希(32字节) |
+----------------+----------------+-------------------+---------------+
序列化过程:
pub fn serialize(&self, binary: &mut Vec<u8>) -> Result<()> {
let (mut existence_bitmap, leaf_bitmap) = self.generate_bitmaps();
binary.write_u16::<LittleEndian>(existence_bitmap)?;
binary.write_u16::<LittleEndian>(leaf_bitmap)?;
for _ in 0..existence_bitmap.count_ones() {
let next_child = existence_bitmap.trailing_zeros() as u8;
let child = &self.children[&Nibble::from(next_child)];
serialize_u64_varint(child.version, binary);
binary.extend(child.hash.to_vec());
existence_bitmap &= !(1 << next_child);
}
Ok(())
}
数据结构特性对比
下表总结了Jellyfish Merkle树中不同节点类型的特性:
| 节点类型 | 存储内容 | 子节点数量 | 哈希计算 | 序列化大小 |
|---|---|---|---|---|
| 内部节点 | 子节点引用 | 1-16个 | 递归Merkle哈希 | 可变(4字节头 + n×40字节) |
| 叶子节点 | 键值对数据 | 无子节点 | SparseMerkleLeaf哈希 | 固定(64字节 + 值大小) |
| 空节点 | 占位符 | 无子节点 | 固定占位符哈希 | 不存储 |
优化设计特点
- 路径压缩:通过nibble路径表示节点位置,减少存储开销
- 版本控制:每个节点关联创建版本,支持多版本状态管理
- 位图优化:使用位图高效管理稀疏子节点,减少内存占用
- 递归哈希:采用递归方式计算哈希,支持部分子树验证
Jellyfish Merkle树的数据结构设计充分考虑了区块链状态存储的特殊需求,在密码学安全性、存储效率和查询性能之间取得了良好的平衡。其创新的内部节点设计和位图机制为大规模状态管理提供了高效的基础设施。
状态同步与备份恢复机制
Diem区块链的状态同步与备份恢复机制是整个存储系统的关键组成部分,它确保了网络的高可用性、数据一致性和灾难恢复能力。这一机制基于Jellyfish Merkle树的高效状态验证特性,实现了快速的状态同步和可靠的备份恢复流程。
状态同步架构设计
Diem的状态同步采用分层架构设计,通过多个组件协同工作来实现高效的数据同步:
状态同步的核心组件包括:
- 协调器(Coordinator):管理整个同步流程的状态机
- 请求管理器(Request Manager):处理区块和状态块的请求调度
- 执行器代理(Executor Proxy):验证和执行交易块
- 网络层(Network):处理P2P网络通信
增量状态同步机制
Diem采用基于版本的状态同步机制,每个验证节点维护以下关键状态信息:
| 状态类型 | 描述 | 存储位置 |
|---|---|---|
| 最新提交版本 | 已达成共识的最高版本 | LedgerInfo |
| 同步目标版本 | 需要同步到的目标版本 | State Sync State |
| 已验证版本 | 已完成Merkle验证的版本 | Local Storage |
状态同步过程通过分块(chunk)方式进行,每个块包含一定数量的交易和对应的状态变化:
// 状态块请求数据结构
pub struct StateChunkRequest {
pub start_version: Version,
pub target_version: Version,
pub chunk_size: u64,
pub timeout_ms: u64,
}
// 状态块响应数据结构
pub struct StateChunkResponse {
pub transactions: Vec<Transaction>,
pub state_changes: Vec<StateChange>,
pub proof: AccumulatorProof,
pub ledger_info: LedgerInfoWithSignatures,
}
备份恢复系统架构
Diem的备份系统采用多类型并行备份策略,确保数据的完整性和可恢复性:
备份类型与策略
Diem支持三种核心备份类型,每种类型都有特定的备份间隔和验证机制:
1. 纪元结束备份(Epoch Ending Backup)
- 在每个纪元结束时触发
- 备份包含完整的纪元状态证明
- 确保跨纪元的状态连续性
2. 状态快照备份(State Snapshot Backup)
- 定期创建Jellyfish Merkle树的状态快照
- 默认间隔:10,000,000个版本(约2小时)
- 提供快速恢复的基础状态点
3. 交易备份(Transaction Backup)
- 批量备份交易数据
- 默认批次大小:100,000笔交易
- 与状态快照间隔保持整数倍关系
恢复流程与一致性验证
数据恢复过程采用严格的验证机制,确保恢复后的状态与区块链网络一致:
恢复验证关键步骤
-
元数据同步验证
async fn verify_metadata_consistency( &self, expected_epoch: u64, expected_version: Version ) -> Result<()> { let backup_state = metadata::cache::sync_and_load(...).await?; ensure!( backup_state.latest_epoch_ending_epoch >= expected_epoch, "Epoch consistency check failed" ); ensure!( backup_state.latest_state_snapshot_version <= expected_version, "Version consistency check failed" ); Ok(()) } -
状态证明验证
- 验证Jellyfish Merkle树的根哈希一致性
- 检查状态快照与交易序列的对应关系
- 确保所有中间状态的Merkle证明有效
-
交易重放验证
- 按版本顺序重放所有交易
- 验证每个交易执行后的状态根哈希
- 确保最终状态与网络共识状态一致
性能优化策略
Diem在状态同步和备份恢复方面采用了多项性能优化技术:
并行处理优化
- 多线程并发下载备份数据
- 流水线式的数据验证和处理
- 异步IO操作最大化存储吞吐量
增量同步机制
- 基于版本差量的增量状态传输
- 智能chunk大小调整算法
- 网络带宽自适应调节
缓存策略
- 元数据缓存减少存储访问
- 热点数据内存缓存
- 验证结果缓存避免重复计算
容错与故障恢复
系统设计了完善的容错机制来处理各种故障场景:
-
网络中断处理
- 自动重试机制
- 断点续传支持
- 多备份源故障转移
-
数据损坏检测
- 哈希校验和验证
- Merkle证明完整性检查
- 数据冗余校验
-
恢复过程监控
- 实时进度跟踪
- 性能指标监控
- 异常报警机制
通过这套完善的状态同步与备份恢复机制,Diem区块链能够确保在网络分区、节点故障或数据损坏等各种异常情况下,快速恢复服务并保持数据的一致性,为整个金融网络提供可靠的底层基础设施支持。
数据库性能优化策略
在Diem区块链的存储系统中,数据库性能优化是确保高吞吐量和低延迟交易处理的关键。Jellyfish Merkle树作为核心数据结构,其性能直接影响整个系统的效率。本节将深入探讨Diem项目中采用的多种数据库性能优化策略。
批量写入与原子性操作
Diem存储系统通过批量写入机制显著提升I/O性能。Jellyfish Merkle树的更新操作采用TreeUpdateBatch结构,将多个节点变更打包成单个原子操作:
pub struct TreeUpdateBatch<V> {
pub node_batch: NodeBatch<V>,
pub stale_node_index_batch: StaleNodeIndexBatch,
pub node_stats: Vec<NodeStats>,
}
pub type NodeBatch<V> = BTreeMap<NodeKey, Node<V>>;
pub type StaleNodeIndexBatch = BTreeSet<StaleNodeIndex>;
这种设计允许系统将多个节点更新、过期索引统计信息在一次数据库事务中提交,减少了磁盘I/O次数和锁竞争。
内存缓存优化策略
Diem实现了智能的树缓存机制TreeCache,用于减少对底层存储的频繁访问:
缓存系统采用LRU(最近最少使用)策略,并针对Jellyfish Merkle树的访问模式进行了特殊优化,优先缓存高频访问的内部节点和叶子节点。
数据压缩与序列化优化
Diem使用高效的序列化格式来减少存储空间和网络传输开销:
// 节点键的紧凑表示
pub struct NodeKey {
version: Version,
nibble_path: NibblePath,
}
// 内部节点的优化存储
pub struct InternalNode {
children: Children, // 16个子节点的紧凑数组
hash: HashValue, // 预计算哈希值
}
通过使用NibblePath而不是完整的哈希路径,系统节省了大量存储空间。同时,预计算节点哈希值避免了重复计算。
并发访问控制
Diem数据库实现了细粒度的锁策略来支持高并发访问:
| 锁类型 | 粒度 | 使用场景 | 性能影响 |
|---|---|---|---|
| 读写锁 | 表级别 | 元数据操作 | 低 |
| 行级锁 | 单个节点 | 节点读写 | 中 |
| 乐观锁 | 版本控制 | 并发更新 | 高 |
索引优化策略
Diem为Jellyfish Merkle树维护了多种索引来加速查询:
- 节点主索引:基于
(version, nibble_path)的复合索引 - 过期节点索引:快速定位可回收的存储空间
- 哈希缓存索引:预计算哈希值的快速查找
// 过期节点索引结构
pub struct StaleNodeIndex {
pub stale_since_version: Version,
pub node_key: NodeKey,
}
查询优化技术
Diem实现了多种查询优化技术:
路径压缩查询:利用Jellyfish Merkle树的稀疏特性,跳过空子树:
fn batch_insert_at(
root_node_key: NodeKey,
version: Version,
kvs: &[(HashValue, V)],
nibble_idx: usize,
hash_cache: &Option<&HashMap<NibblePath, HashValue>>,
tree_cache: &mut TreeCache<R, V>,
) -> Result<(NodeKey, HashValue)>
批量范围查询:通过NibbleRangeIterator高效处理连续键范围的查询:
struct NibbleRangeIterator<'a, V> {
sorted_kvs: &'a [(HashValue, V)],
nibble_idx: usize,
pos: usize,
}
存储分层与数据生命周期管理
Diem采用智能的数据分层策略:
| 数据层级 | 存储介质 | 访问频率 | 保留策略 |
|---|---|---|---|
| 热数据 | 内存缓存 | 非常高 | 实时更新 |
| 温数据 | SSD存储 | 高 | 近期版本 |
| 冷数据 | HDD存储 | 低 | 归档版本 |
系统通过Pruner组件自动管理数据生命周期,定期清理过期数据:
pub struct Pruner {
db: Arc<DB>,
prune_window: u64,
// 清理策略配置
}
监控与性能调优
Diem集成了全面的性能监控系统,实时收集关键指标:
// 性能监控指标
DIEM_STORAGE_API_LATENCY_SECONDS
DIEM_STORAGE_COMMITTED_TXNS
DIEM_STORAGE_LATEST_TXN_VERSION
DIEM_STORAGE_LEDGER_VERSION
这些指标帮助运维人员识别性能瓶颈并进行针对性优化,如调整缓存大小、优化压缩参数或重新平衡数据分布。
通过上述优化策略的组合使用,Diem的存储系统能够在保持数据一致性和完整性的同时,实现极高的读写性能,满足区块链应用对存储系统的高要求。这些优化措施不仅提升了单节点性能,还为整个分布式网络的扩展性奠定了坚实基础。
总结
Jellyfish Merkle树作为Diem区块链的核心存储数据结构,通过创新的内部节点设计和位图机制,在密码学安全性、存储效率和查询性能之间取得了卓越平衡。其分层架构支持高效的状态同步和可靠的备份恢复,而多层次的性能优化策略(包括批量处理、缓存机制、压缩算法和并发控制)确保了系统的高吞吐量和低延迟。这套完整的存储解决方案为区块链的大规模状态管理提供了可靠的基础设施,展现了现代分布式系统在数据持久化方面的最佳实践。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



