作者:明神特烦恼
公众号:明神特烦恼
在共识完成后进行区块提交阶段,对调用账本模块来对区块以及交易等进行持久化,本章节将一起分析一下账本模块实现过程。
带着问题读代码:
1)存储方式有哪些?
2)账本存储哪些内容,有哪些索引?
3)在写如多张数据库表,如果发生意外掉电,如何保证原子性?如何回滚?
第一个问题:存储方式有哪些?
长安链支持nosql、sql两种形式的存储,其中nosql支持rocksdb、leveldb,sql支持mysql、sqlite。多种存储形式满足不同应用场景及业务要求。
无论何种存储方式,上面已经高层封装,屏蔽掉底层存储逻辑,化简其他模块的操作复杂度,后面的分析以nosql为例。
const (
UnknownDb EngineType = 0
LevelDb EngineType = 1
RocksDb EngineType = 2
MySQL EngineType = 3
Sqlite EngineType = 4
)
第二个问题:账本存储哪些内容,有哪些索引?(以nosql为例)
1.写块流程:
1)记录最新写入的块号lastBlockNumKeyStr
2)记录BlockHeightKey
与BlockInfo
关系:
- BlockHeightKey =
blockNumIdxKeyPrefix + blocknum
- BlockInfo =
序列化(Header + Dag + TxIds + AdditionalData)
3)记录BlockHashKey
与 BlockHeight
关系
- BlockHashKey =
blockHashIdxKeyPrefix + blockhash
- BlockHeightKey
4)记录TxidKey
与 TxInfo
关系
- TxidKey =
txIDIdxKeyPrefix + txid
- TxInfo =
序列化(Transaction)
5)记录BlockTxidKey
与 BlockHeightKey
关系
- BlockTxidKey =
blockTxIDIdxKeyPrefix + txid
- BlockHeightKey
6)记录LastConfigKey
与 BlockHeightKey
关系(只有配置块记录该关系)
- LastConfigKey =
lastConfigBlockNumKey
- BlockHeightKey
账本模块通过上述记录的内容提供服务有:获取最后一个区块块号;通过块号获取块内容;通过块Hash获取块号;通过txid获取交易详情;通过txid获取其所在块号;获取最新配置块号。
2.写世界状态流程:
1)记录当前世界状态对应的最新块号stateDBSavepointKey
2)记录写入数据的KV值 Or 删除Key
- Key:
合约Name + WriteKey
- Value:世界状态值
- OP: 插入
Or
- Key:
合约Name + WriteKey
- OP: 删除
3.写入历史数据库流程:
1)记录当前历史数据库对应的最新块号historyDBSavepointKey
2)记录操作的Key与块号、Txid关系
- Key:
合约Name + WriteKey + BlockHeight + Txid
3)记录操作账户与块号、Txid关系
- Key:
账号ID + BlockHeight + Txid
4)记录合约与块号、Txid关系
- Key:
合约名称 + BlockHeight + Txid
4. 写入合约结果流程:
1)记录当前合约结果数据库对应的最新块号:resultDBSavepointKey
2)记录RWSetsTxid与读写集之间的关系
- RWSetsTxid:
txRWSetIdxKeyPrefix + txid
- 读写集:
序列化(TxRWSet)
第三个问题:在写如多张数据库表,如果发生意外掉电,如何保证原子性?如何回滚?
上面提到了多个流程,每个流程是原子操作,保证了事务一致性。但多个流程之间是非原子的,也就是世界状态写入完成,而写块流程还未完成,如果此时发生掉电(进程kill等),那么节点自身的数据将不完整,这是不被允许的。
为解决掉电问题,长安链处理办法如下:
- 同步写binlog,binlog非随机写入,机械硬盘执行速度较快。
- 并发完成写块流程、写世界状态流程、历史数据库流程等。
- 写入完成后,异步清理binlog。
当进行crash后重启检测及恢复流程:
- 创建存储Handler时判断binlog是否有内容,如果有下一步。
- 获取binlog中记录最后一个log的块高度logSavepoint
- 获取写块流程的最新块号lastBlockNumKeyStr
,如果logSavepoint
> lastBlockNumKeyStr
,表示有区块数据未执行写块流程,调用CommitBlock
进行写块。
- 其他几个流程同理。
注意:
1) 这里要保证每个流程自身内部是原子的。
2) 恢复流程中并未删除binlog,这是因为binlog的处理是每100个块删除一次,并不需要实时删除,也不会产生存储泄露。
3)恢复流程没有回滚,只有继续写入。
网友评论