2个版本
0.6.0-beta.3 | 2023年10月1日 |
---|---|
0.6.0-beta.2 | 2023年9月25日 |
在 数据结构 中排名第436
每月下载量47次
350KB
7K SLoC
稳定结构
一组可扩展的数据结构,用于在升级过程中持久化互联网计算机。
稳定结构设计用于使用稳定内存作为后端存储,允许它们在不使用pre_upgrade
/post_upgrade
钩子的情况下增长到几GB。
背景
传统的canister状态持久化方法是,在pre_upgrade
钩子中将整个状态序列化到稳定内存中,并在post_upgrade
钩子中解码它。这种方法易于实现,对于相对较小的数据集来说效果很好。然而,它扩展性不佳,可能会使canister无法升级。
这个库旨在简化在稳定内存中直接管理数据结构。有关库背后的哲学,请参阅Roman关于稳定结构的教程。
可用数据结构
- [BTreeMap]: 键值存储
- [Vec]: 可增长的数组
- [Log]: 可变大小条目的只追加列表
- [Cell]: 可序列化的值
- [MinHeap]: 优先队列
教程
工作原理
稳定结构能够直接在稳定内存中工作,因为每个数据结构都管理自己的内存。初始化稳定结构时,提供内存以供数据结构用于存储其数据。
以下是一个基本示例
use ic_stable_structures::{BTreeMap, DefaultMemoryImpl};
let mut map: BTreeMap<u64, u64, _> = BTreeMap::init(DefaultMemoryImpl::default());
map.insert(1, 2);
assert_eq!(map.get(&1), Some(2));
通过[Memory]特质抽象化内存,稳定结构可以与任何实现该特质的存储后端一起工作。这包括稳定内存、向量([VectorMemory])或甚至是平面文件([FileMemory])。
上面的示例使用[DefaultMemoryImpl]初始化了一个[BTreeMap],当在canister中使用时映射到稳定内存,否则映射到[VectorMemory]。
注意,稳定结构不能共享内存。每个内存只能属于一个稳定结构。例如,在canister中运行时这会失败
use ic_stable_structures::{BTreeMap, DefaultMemoryImpl};
let mut map_1: BTreeMap<u64, u64, _> = BTreeMap::init(DefaultMemoryImpl::default());
let mut map_2: BTreeMap<u64, u64, _> = BTreeMap::init(DefaultMemoryImpl::default());
map_1.insert(1, 2);
map_2.insert(1, 3);
assert_eq!(map_1.get(&1), Some(2)); // This assertion fails.
它失败的原因是map_1
和map_2
都在底层使用相同的稳定内存,因此map_1
中的更改最终会改变或损坏map_2
。
为了解决这个问题,我们使用了MemoryManager,它接受单个内存并为我们创建最多255个虚拟内存。下面是上面失败的示例,但是通过使用MemoryManager来修复
use ic_stable_structures::{
memory_manager::{MemoryId, MemoryManager},
BTreeMap, DefaultMemoryImpl,
};
let mem_mgr = MemoryManager::init(DefaultMemoryImpl::default());
let mut map_1: BTreeMap<u64, u64, _> = BTreeMap::init(mem_mgr.get(MemoryId::new(0)));
let mut map_2: BTreeMap<u64, u64, _> = BTreeMap::init(mem_mgr.get(MemoryId::new(1)));
map_1.insert(1, 2);
map_2.insert(1, 3);
assert_eq!(map_1.get(&1), Some(2)); // Succeeds, as expected.
示例 Canister
这是一个完全工作的canister示例,将所有内容结合起来。
依赖项
[dependencies]
ic-cdk = "0.6.8"
ic-cdk-macros = "0.6.8"
ic-stable-structures = "0.5.6"
代码
use ic_stable_structures::memory_manager::{MemoryId, MemoryManager, VirtualMemory};
use ic_stable_structures::{DefaultMemoryImpl, StableBTreeMap};
use std::cell::RefCell;
type Memory = VirtualMemory<DefaultMemoryImpl>;
thread_local! {
// The memory manager is used for simulating multiple memories. Given a `MemoryId` it can
// return a memory that can be used by stable structures.
static MEMORY_MANAGER: RefCell<MemoryManager<DefaultMemoryImpl>> =
RefCell::new(MemoryManager::init(DefaultMemoryImpl::default()));
// Initialize a `StableBTreeMap` with `MemoryId(0)`.
static MAP: RefCell<StableBTreeMap<u128, u128, Memory>> = RefCell::new(
StableBTreeMap::init(
MEMORY_MANAGER.with(|m| m.borrow().get(MemoryId::new(0))),
)
);
}
// Retrieves the value associated with the given key if it exists.
#[ic_cdk_macros::query]
fn get(key: u128) -> Option<u128> {
MAP.with(|p| p.borrow().get(&key))
}
// Inserts an entry into the map and returns the previous value of the key if it exists.
#[ic_cdk_macros::update]
fn insert(key: u128, value: u128) -> Option<u128> {
MAP.with(|p| p.borrow_mut().insert(key, value))
}
更多示例
组合持久性
如果您的项目仅依赖于稳定结构,则内存可以扩展大小,无需pre_upgrade
/post_upgrade
钩子。
但是,需要注意的是,如果您还打算对堆数据进行序列化/反序列化,则必须使用内存管理器。为了有效地结合两种方法,请参考快速入门示例以获取指导。