解密区块链存储机制

转载
2386 天前
12826
比特评级

火讯财经注:区块链的数据存储机制及结构是关系到区块链数据分布、记录、读取等操作行为的基本环境,区块链技术重构了数据世界的律法,而它的底层数据存储机制则是关系到区块链运作的稳定性、安全性和可扩展性“不可能三角”的实现途径。


作者 | 章鱼

来源 | 比特评级

从数据库的角度看待区块链的存储机制会简单直观很多。在一个标准的关系型数据库中,存储一般分为日志存储、用户数据存储、以及索引存储三大类(有些数据库可能还包含大对象存储等)。而区块链项目中基本所有的“账本”存储其本质就是交易日志存储。用户数据存储则根据项目不同而有选择性地采用。譬如说对于UTXO结构的区块链项目来说,其每个账号对应的余额直接保存在内存哈希表中(或类似LevelDB等嵌入式KV数据库中),因此不需要一个独立的外接用户数据存储模块。而类似Hyperledger等通用区块链框架则一般包含类似State Store等存储最终结果数据的模块。索引存储则在当前大部分区块链项目中均不存在。

1.账本格式

区块链的链式结构笔者在这里不再赘述,其每个块包含上一个块的哈希值,而内容则使用默克尔树进行校验以实现快速验证,每条记录则使用数字签名的方式保证其一定来自拥有私钥的用户。


(图1:区块链数据结构)

对比传统数据库的日志结构,区块链结构并不复杂。一般来说,所有传统数据库日志结构都比较相似,每个日志文件包含日志头与多个日志数据页,其中日志头代表其起始交易号(Oracle中叫做SCN,DB2中叫做LSN),也就是该日志文件中第一条日志记录在整个日志空间中的起始字节数,以及日志文件大小,日志页大小等元数据信息。紧接着真实的日志记录则以二进制码流的方式依次存放在文件中,每一条日志记录头包含该条记录的交易号、事务号、同一事务中上一条记录的交易号(反向指针),以及变更前与变更后的数据(满足回滚要求)。


(图2:数据库日志结构)

因此,从结构上来看,区块链账本与数据库日志本质上没有任何区别,仅仅在数据结构上为了满足一些特定要求做了部分优化。

2. 存储内容

通用型数据库与当前大部分区块链账本项目(例如比特币、以太坊等,而Hyperledger这类区块链平台则不包含在内)从日志的角度看,最大的区别在于区块链账本项目对于日志格式进行了高度定制化与业务绑定。

一般来说,传统的数据库日志包含的是数据页的变更信息,我们叫做“写前镜像”和“写后镜像”,代表新的数据写入前这条记录长什么样子,同时新数据写入后这个记录长什么样子。通过这种方式,可以很轻易地在磁盘中的某个数据页的指定偏移上进行数据前滚与回滚操作。

譬如说一条数据库日志并不会记录一个INSERT操作的具体命令,而是以“X数据页的第Y个槽位,其所对应的偏移地址数据由ABC变化为DEF,总长度Z”的方式体现出来。因此,数据库的日志几乎可以记录任何信息,这也是为什么通用数据库可以被用来实现任何业务逻辑的原因。

而大部分的账本项目则高度定制化其日志结构,例如以太坊的每一条交易信息都会包含输入金额、输出金额、燃料等信息,然后每一个节点在进行验证时必须判断其符合某种规则,否则不予通过。这种机制可以看做是一个完全高度定制化的数据库业务,每一条日志记录不仅仅记录数据内容的变更,而是与业务逻辑紧密耦合,记录每个账户的余额变化。

因此,从可扩展性来看,尽管以太坊等项目支持“智能合约”,但是其核心本质还是一个高度定制化的账本系统,其业务逻辑与交易结算进行了非常紧密的绑定。

3. 设计思路

从设计哲学上看,当前的区块链与数据库在对通用业务的支持上采用了两种不同的策略。对于传统数据库来说,其秉承的设计理念是“业务与数据分离”的思路,也就是说数据库仅负责数据的存放,通过提供一种灵活的查询语言能够让应用程序直接访问数据库进行增删改查,但是基本所有的业务逻辑由应用程序自行定义。

但是,对于区块链来说则是存储与业务逻辑紧耦合的思路。在区块链,尤其是公链的设计哲学中,因为每个存储节点和应用都是不可信的,因此大部分业务逻辑需要在协议层进行高度定制。一个区块链节点即需要对协议层进行解析和封装,同时也需要负责对数据本地化落盘和存储。

因而,尽管站在高层面可以将区块链看做是多活数据库,但是如果从具体实现层面来看,每个区块链节点又不能简单地看做是一个传统数据库的多活替代品,而是一套包含协议解析封装和一部分业务逻辑的应用软件。

4. 用户数据存储

如果未来区块链的目标是作为通用平台,用以存储多种类型的数据,则其日志格式与存储必须回归数据库的通用性本源。当前的账本模式可以作为该体系中的一个特别模块存在用以进行账户间结算,但是无法将其扩展为通用业务平台。

既然要成为通用数据存储平台,那么UTXO模型存在一定局限性。在一个典型的银行业务中,零售业务可能会包含千万甚至亿级别的账户,不同账户可能使用不同的利息计算规则,也可能存在冻结等特殊状态。而交易流水信息每天可能达到千万笔,如果将其业务扩展到非金融行业,流水信息每天几亿也是可能的。因此,从一个通用账户+流水的业务模型中,一般企业会建立一个账户表与一个流水表,以不同的策略进行管理。


账户表俗称余额类数据,在典型的数据治理体系中需要做到定期快照备份(例如月初数和月末数);而流水表则成为流水类数据,一般来说以原始交易格式直接存储和备份。通过对余额类数据快照备份的恢复,对指定账号重做某个时间范围内的全部交易流水,可以得到该账号任意时间点的余额信息。

而UTXO的本质在于日志存放的信息不是记录的最终结果,而是变化行为。在传统数据库中,每条事务记录的是数据的写前与写后内容。例如将一条记录从5更改为8,其数据库日志记录原始数据为5且新数据为8,而不是记录“+3”的操作。但是UTXO记录的是变更信息,其主要的目的是解决双花问题(例如对于一个有100块钱的账号,一个人在中国转走10块钱,另一个人在美国同时转走10块钱,如果记录的是最终结果,那么中国的服务器会认为这个人有90块,美国的服务器在没有全局锁的情况下也会认为这个人有90块,最终写到区块中就变成90块余额,而非80)。

UTXO的机制可以有效地在无锁的情况下避免双花问题,但是其劣势则在于不存储余额表,所有的信息均通过重做流水数据,从零开始生成。对于一个存在了十年以上,包含几百亿笔交易的系统来说,这样的做法就好比每次重启都要从都重做几百笔交易并存入内存中(或KV数据库里),是一种非常原始且不经济的方式。

另一方面,区块链日志的结构看来,由于多活系统中全局锁很难实现,因此需要通过交易日志结构的调整来满足传统数据库中事务的功能。传统数据库中当涉及到两账户之间转账操作时需要开启一个事务。在事务日志中一个账户增加一个账户减少的业务逻辑,需要体现为包含三条记录的链表(最后的提交操作也是一个记录)。在数据库崩溃或发生异常后,只要通过重做所有的任务,并最后对全部没有提交记录的事务进行反向操作,即可得到原子性(Atomic)与持久性(Durability)。

而在区块链体系中由于不存在事务的概念,同时操作日志与结算业务进行了紧密耦合,因此每条交易记录都会包含一个输入账号以及若干个输出账号,也就是说只要一条事务记录被成功发送给一个节点,则可以保证在该记录内部的全部输入输出账户统一进行了变更。可以说,区块链通过定制化交易日志简化了事务操作的复杂性,但是带来的影响便在于业务与代码的紧密耦合不可分割。

但是无论如何,首先UTXO并不是通用数据结构,而是为交易业务高度定制化的数据结构,如果想要运行图灵完备的智能合约(或者说存储过程),使用UTXO会有很多局限性。第二,对长期运行的大型系统(相比起大中型银行核心交易系统所产生的交易流水,比特币从诞生到现在的交易量少得可以忽略不计),UTXO每次初始化需要全部的历史交易日志。这种模式完全不可能适用于大型交易系统。

因此,可以存在两种做法解决该问题。第一种方式使用传统账户表与流水表的机制,将UTXO以流水的方式体现出来,同时定期保存账户快照,以避免每次重构数据库都需要重做全部交易(这种机制需要考虑到账户与流水表在多活系统中,没有全局锁的情况下如何实现一致性的问题)。而对于非结算类交易,通用型区块链项目则可能采用日志结合用户数据存储的模式,才能够普适性地满足通用业务需求(这种机制需要依靠比nonce更好的排序机制避免双花)。

5. 索引存储

当前基本没有任何区块链项目支持用户数据的自定义索引。这种机制在未来的通用型区块链项目一定会被弥补。从本质上看当前的区块链项目结构没有任何理由无法在其上构建通用索引能力(包括B树索引、位图索引、全文检索等)。

小结

区块链的存储体系现在还处于数据库上世纪80年代的阶段,其当前最大的问题在于日志结构与业务逻辑的紧密耦合(读者可以理解为应用程序为每种业务逻辑都要从头实现一遍Oracle)。而这样做的本质原因在于多活数据库中事务的原子性与锁极难保障,因此当涉及到多个账户的转账原子操作时,当前大部分账本类区块链项目均不得不定制日志结构,将每一笔交易的全部信息放在一条记录中。从数据库的角度看,在区块链项目中实现跨记录的原子操作(包括全局锁)极为复杂,而这也正是区块链技术向通用型数据存储进化的关键所在。

笔者认为,随着区块链应用越发广泛,人们在不久的将来一定会将各类区块链应用泛化出一系列典型的场景和需求。基于这些场景和需求,一定会出现一批优秀高效的多活数据存储。不论这些机制的后台到底是否基于“区块”的架构实现,其这正需要突破的是现有数据库体系中无法做到active-active的局限(也就是去中心化)。