写点什么

区块链 ≠ 分布式存储

用户头像
趣链科技
关注
发布于: 4 小时前

【背景】

随着区块链技术的发展和应用场景的逐步丰富,越来越多的人开始接触区块链。但在过程中,很多人提过这样的问题:“底层用区块链系统和用数据库有什么区别呢?”、“区块链系统是不是就是一个 OLTP 数据库系统?”...

直观的角度来看,完整的区块链系统内部一定会包含一个「存储模块」,整体而言,区块链系统确实可以起到持久化数据的作用。

但是如果从这个角度出发,直接将区块链系统看作是一个数据库,这样的观点也是有待商榷的。在作出最终比较之前,我们先来分析一下传统数据库系统的运行机制以及区块链系统内部存储模块的功能职责

【传统 OLTP 数据库 VS 区块链存储】

 ▲ 传统 OLTP 数据库存什么?

现阶段的数据库系统、存储引擎的设计一般是面向某一通用场景的,比如 sql 型数据库、NoSQL 型数据库(kv 数据库、文档数据库等),而不是面向具体业务场景的,那一般的 OLTP 数据库内部的数据分为四大类:

  • 数据库内部的管理性质的元数据。这部分数据基本上对用户是透明的,负责数据库内部的管理与控制逻辑;

  • 用户自定义数据。这部分数据是用户通过 API 向数据库写入的数,数据库系统一般不关心这部分数据的具体内容,而是侧重于如何正确、完整的将这些数据保存到持久化设备;

  • 索引数据。索引数据一般是数据库设计中不可或缺的一个组成部分。为了保证数据库“读数据”功能的响应时间在用户可接受范围内,几乎所有数据库系统都需要或多或少的引入索引;

  • 日志数据。日志数据是一种数据库内部数据,其内容一般是记录数据变更行为,一般用于数据库宕机重启后的数据恢复。在不同的数据库中,日志数据的内容差距也非常大,比如:有的数据库使用日志来记录存储层的数据变更(磁盘上位置 X 开始,连续 N 个字节从 Value1 变成了 Value2),有的数据库使用日志来记录用户的写入命令请求(比如插入操作,内容为 key=1,value="ABC")等等。


 ▲ 区块链存储模块有何不同?

当我们站在区块链系统内部“数据存储”功能的角度看待“区块链系统”时,我们会发现,区块链系统具有确定性的系统架构、确定性的内部业务逻辑,以及一些通用的数据组织格式(比如:区块是一种 append-only 形式的数据、只有虚拟机执行指令的过程中会修改状态数据等)。区块链系统中的数据存储只需要满足这一套运转逻辑过程中的持久化需求即可,也就是说,区块链系统为其存储模块划定了比通用数据库更小的模块功能边界。

▲区块链存储存什么?——世界状态

从使用者角度来看,一个最常规的区块链服务是由一个区块链网络提供的。区块链网络由多个节点构成,用户可以向区块链服务发送交易,区块链网络中的所有参与共识的节点会针对交易的内容、执行过程、执行结果达成一致的决议,并将执行结果返回给发起交易的用户。

上述过程是一个最常规的区块链服务使用流程。但是,如果从区块链网络中的节点的角度出发,节点感知的过程或者变化有哪些呢?

我们略去网络交互、共识、执行等等的细节说明,直接讨论我们关注的重点——“世界状态”:

如果我们将区块链可以看为一个分布式的状态机:所有节点从同一个创世状态开始,依次执行相同顺序的交易,驱动各个节点的状态按照相同操作序列不断变化,实现所有节点在同一交易序列执行完成后,状态完全一致。而这个状态,就称为“世界状态”。


在区块链网路中,所有诚实节点本地维护的“世界状态”是一致的,这个“世界状态”就是区块链存储模块要重点关注的内容。

解释了 “世界状态” 的定义,我们还是要关注 “世界状态” 到底包括什么内容:首先,一个区块链系统中的数据状态的更迭,一定是由 “用户交易” 驱动的,那么我们只需要保证 ‘世界状态’ 能够涵盖交易的“请求内容、执行过程和执行结果” 三部分内容,就可以满足上述 “分布式状态机” 中的一致性要求。

接下来,我们将从三个角度分别分析“世界状态”中要存储的具体内容。

区块链系统通用的存储内容——块链结构

区块链系统中,区块通过保存前序区块的标识(一般都是用区块哈希)来形成逻辑上的一条链。这样做的目的和意义本文不再赘述,这里重点关注的是这个“区块”的数据组织存储形式

区块里面会存储哪些内容呢?一般的区块链系统中,区块会被分为区块头区块体两个部分。

“区块体” 部分相对简单,这个部分负责将交易内容按照一个确定的顺序存储下来;存储交易内容是很好理解的,但“一个确定的顺序”是指什么呢?回顾上文,“世界状态”是由交易驱动的,不同的交易执行顺序可能会导致数据变更操作的顺序不同,进而很可能导致世界状态的不同。因此“区块体”的内容,即体现了完整的交易内容,还同时体现了确定的交易顺序。

“区块头”部分会包含很多的元素,区块头中一般会包含一个最重要的区块哈希字段,这个区块哈希一般会用于表示当前系统的世界状态。这就要求区块哈希的计算来源至少包含:前一区块的区块哈希(代表了前序的世界状体)、当前区块对世界状态造成的变更影响(当前区块包含的所有交易的内容、执行结果和执行过程中对账本数据的修改)。但是,区块哈希的具体计算方式可以由执行层来决定,不同系统间计算方式不尽相同[1][2]。


注:目前也存在一些新型的区块链系统采用了 DAG 模型[3]组织交易,甚至形成区块结构,这种情况本文暂时不做分析。

账本数据——UTXO 模型账本 VS 账户模型账本

区块数据内部包含了交易内容,但是交易的内容一般只定义了对区块链上数据的修改请求,“执行过程” 和 “执行结果” 这两部分数据可能并不包含于交易内容中。本节将首先讨论交易 “执行过程” 对 “世界状态” 的影响。为此,我们引入了 “账本数据” 的概念。

目前比较通用的账本数据组织形式包括:UTXO 模型账户模型

UTXO 模型相对比较简单,系统中仅支持一种运行模式:“UTXO” 在不同的账户间流通。在这种运作模式下,一笔交易代表了一次流通行为,具体的流通过程为:输入的 UTXO 被销毁,输出的 UTXO 被创建,输入总额需要大于或等于输出总额[4]。

在 UTXO 模型下的账本数据定义较为直观:当前链上全量的未消费的 UTXO(包括 UTXO 上的绑定的脚本)。因为在这种模式下,如果交易执行成功,则输出有效,如果交易执行失败,则输出无效。而执行过程中的锁定、解锁等脚本的验证过程不会产生额外的账本数据修改。因此交易执行过程不涉及账本数据的更改。



但是账户模型的账本组织方式会略微复杂一些。一般账户模型的账本都是可以支持 “智能合约” 的,账户模型下的区块链系统会建立一个 “账户空间” 。

“账户空间” 是由很多个账户组成的,其中的账户可能分为多种类型。以以太坊为例,系统中以一个通用的数据结构定义了普通账户、合约账户两种类型。普通账户的行为包括:发起交易、互相转账、创建账户等等;而合约账户则对应了一份部署在链上的智能合约,相应的,合约账户会管理所有在这份智能合约中定义的、需要存储到区块链账本中的 key-value 数据对,我们称之为 “状态数据” 。

在交易执行的过程中,可能涉及到 “账户数据” 和 “状态数据” 的变更,这些改变可能既不会体现在合约内容本身中,也不会体现在交易执行结果中。因此,为了保证 “世界状态” 的完整性,经过交易执行修改后的整个 “账户空间” 的数据状态,需要被纳入“世界状态”的范畴。

那么作为世界状态的一部分,系统会规定一种特定的方式(以太坊的 MPT 树、Fabric 的 bucketTree 等等)计算出一个能反应账户空间整体状态的、易于比较的结果值,这个结果值会参与到上一节提到的“区块哈希”的计算中。(某些计算过程中的数据可能也会被持久化,比如 MPT 会将计算树根过程中的路径上的节点数据也存储在账本数据中)


交易执行结果/回执数据

“UTXO 模型” 的系统交易执行结果比较直接,所有的合法交易的执行结果就是成功标识+ “交易输出” 内容;所有非法交易的执行结果就是失败标识,而交易的输入部分保持原状。

但是 “账户模型” 的区块链系统中,一般会包含转账交易和智能合约调用交易。交易的执行结果一般要经过虚拟机执行得到,考虑合约调用的场景,交易的执行结果很可能并不体现在交易内容和账户空间变更结果中;因此,交易的回执数据需要独立存储,为了统一账户创建、账户间转账、智能合约调用等多种交易的执行结果,系统一般会设计统一的回执数据组织格式。

至此,交易数据+账户空间+交易执行结果三者一同构成了账户模型下的系统的最基本的世界状态。相应的,这类系统中的存储模块,需要提供这三类数据的存储功能。在具体设计和实现存储模块时,可以结合区块链系统的运作模式,综合考虑吞吐量和延迟等指标,根据不同数据的业务特点设计和使用不同的底层存储引擎。

索引数据

一个完整的区块链系统除了提供上述写入功能外,还需要提供历史数据的查询功能。如果系统中只存储了上述世界状态数据,那么用户对历史数据的查询只能通过遍历所有区块实现,这样的代价和延迟很显然是无法接受的。

因此,一般的区块链系统都会考虑设置一种基本的索引——交易索引。借由这种索引,系统能够快速的确认交易所在的区块,甚至可以更细粒度的直接定位到交易存储于这个区块内的哪个位置。索引的内容一般就是“交易哈希(交易标识)”到“交易所在位置”的映射。

这样的索引设计会给系统持久化区块的过程带来一定的 “写放大” ,单无论从数据量还是从必要性角度出发,一般的区块链系统都会选择接受这种代价。

【区块链存储相关的 “一致性”】

至此,一个简单通用的区块链系统必要的存储内容已经介绍完毕。但是这还不是区块链系统的存储模块需要考虑的全部内容。在区块链网络中,所有诚实节点在相同的区块执行完毕后,需要拥有完全一致的世界状态。这个世界状态可以由区块确认后的 “区块哈希”表示。

但是,区块链系统一般会运行在一个拜占庭网络环境中;那么,区块链系统随时存在着数据落后的情况,整个网络也存在着随时新增和删除节点的情况。无论是落后的诚实节点,还是新增进入网络的诚实节点,都需要尽快追赶上网络中的其他诚实节点。

那么落后的节点如何 “追赶” 其他的诚实节点呢?这里我们再重新审视一下 “交易” 这个角色。前文提到:“区块链系统的整体数据状态是依赖交易的执行而不断向后演进的”,那么交易就可以看作是区块链系统世界状态变更的 “日志” 。

基于这样的理解角度,落后节点 “追赶” 其他诚实节点可以通过同步诚实节点的区块来实现,因为区块中的区块体包括了全量的交易内容,那么落后节点可以通过重放其缺少交易,最终达到与其他节点一致的世界状态。这个恢复的过程,落后节点不需要参与共识,而是借由一系列校验协议来确保数据的完整性和正确性。

进一步分析上述交易重放流程,如果是 UTXO 模型的系统,交易的内容和执行过程比较简单;但是对于账户模型的系统,则交易可能是合约执行请求,那么执行这笔交易的过程就不可避免的会涉及执行环境的创建,使用虚拟机执行完整的合约指令等等步骤。那么,对于节点间网络环境较好的场景,是不是有进一步提高落后节点“追赶”速度的策略呢?

由此,在特定的场景下,比如 “联盟链+账户模型” 的系统中,数据同步的过程可以引入另一种策略:如果在交易执行的过程中,系统将所有的账本数据(也就是账户空间)修改相关的操作都记录下来,形成一份账本操作日志数据(可以类比 MySQL 中 Binlog 的 row 模式思路);那么在做数据同步时,落后节点可以直接拉取区块数据、账本操作日志数据和交易回执数据,完成后并将拉取账本操作日志数据按序应用到本地账本数据上,完成应用日志的过程后,落后节点便完成了与其他诚实节点一致的“世界状态”的构建。



基于上述思路,存储模块可以引入另一种与世界状态有关的数据——操作日志数据,这些数据与交易的执行过程强相关。在某些网络带宽充裕、智能合约计算逻辑复杂但账本数据修改不频繁的场景下,上述思路是一种有效加速节点间数据同步的解决方案。

【小结与思考】

至此,本文第一个引入的 “区块链存储模块是不是数据库” 的问题,可以做一个简单的总结:笔者认为,数据库和区块链存储是可以区分开来看待的概念,区块链存储模块无论从功能边界、服务对象还是自身的拓展优化思路上,都是和 “区块链系统” 这个场景强相关的。本系列后续的文章会针对区块链系统的内部逻辑和场景,分别讨论各种数据的存储模式存储设计思路。但是 OLTP 数据库系统,其考虑的场景则是一个更宽广、更通用的层面。

最后,可能部分读者会思考这样一个问题:本文一直在强调 “区块链存储系统的专用性和为区块链场景定制” 的思路,那在现实业务场景使用区块链底层平台时,平时常听到的 “通用型平台” 这个概念又该如何理解呢?

其实,“为区块链系统的内部运作机理提供定制化的存储模块功能” ,与 “区块链系统整体对外提供通用的业务支持” ,是两个层面的问题,两者并不矛盾。本文讨论的存储模块的定制化,专用性,是针对区块链系统的内部运行逻辑定制化的设计存储功能,但是区块链系统中账本组织模型的设计和智能合约功能的存在,则允许用户为多种业务场景自定义合约逻辑、自定义账本 “状态数据” 。

作者简介

郭威

趣链科技基础平台部 区块链存储研究小组

参考文献

[1] https://github.com/ethereum/go-ethereum

[2] https://github.com/hyperledger/hyperledger

[3] https://developer.confluxnetwork.org/

[4] 《精通比特币 第二版》

发布于: 4 小时前阅读数: 4
用户头像

趣链科技

关注

QTech! 趣链科技区块链技术分享社区 2020.05.07 加入

QTech! 分享高质量区块链相关前沿技术研发、管理以及创新等资讯

评论

发布
暂无评论
区块链 ≠ 分布式存储