构建在区块链上的互联网计算机

好久不见

好久不见!印象中,已经半年多没写技术相关的文章了!

一个主要原因是平时太忙了,没那么多闲心闲时。而且现在比较少遇到令人眼前一亮的新鲜事物,所听所闻无非是炒冷饭,或者说是新瓶装旧酒。

最近区块链界的天王级项目 DFINITY 主网上线,其创始人 Dominic Williams (人送外号大裁缝)经常在推特「碰瓷」以太坊,让人忍不住想多了解下 DFINITY 的底层技术 —— 凭什么它是以太坊的疯狂姐妹,又凭什么它可以自诩为下一代互联网。

所以博主在此翻译博文 A Technical Overview of the Internet Computer,并加些个人的解读,分享给大家。

网络神经系统(Network Nervous System)

DFINITY 提到的互联网计算机(Internet Computer)基于被称为互联网计算机协议(Internet Computer Protocol,简称 ICP)的区块链计算机协议。

其网络具有明确的层次结构:数以千计的数据中心提供了百万级的专用硬件节点,这些节点被划分为一个个子网,子网之上则运行着网络的计算单元 —— 软件容器(简称容器),它们由用户上传和创建,每个软件容器包含运行代码(WebAssembly 字节码)和运行状态。

个人解读:

一般我们讲到容器,都会联想到 Docker 和无状态服务(相比于数据存储等有状态服务)。

而 ICP 的每个容器都是有状态的,可以将业务数据作为容器的状态持久化存储,相关说明请往下阅读。

hierarchy

ICP 独有的网络神经系统(NNS)负责控制、配置和管理网络,从某种程度上来讲,NNS 就相当于目前互联网上的 ICANN。不过 NNS 是自治的,用户提交议案,并由网络上的神经元投票表决。

PS:提交议案和投票都需要质押代币,具体是个什么样的过程,我们下文再分解!

NNS 对网络的管辖范围非常广,包括新数据中心的引入、节点健康状态的监测,以及代币的管理等等。

在 ICP 的经济模型中,NNS 负责生成新的 ICP 代币(以前称为 DFN 代币),用来奖励数据中心和参与投票的神经元,这是代币的通货膨胀模式。

economics

另一方面,容器正常运行的前提是拥有足够的 Cycles(类似以太坊的 Gas),而 Cycles 目前只能通过 ICP 代币转换而来(这种转换是单向的)。

容器所有者/管理者向数据中心、神经元的所有者兑换 ICP 代币,并转换为 Cycles 为容器充值,容器执行计算或使用存储时,将直接消耗 Cycles,此时代币是通货紧缩的。

个人解读:

NNS 会根据 ICP 价格动态调用 ICP 与 Cycles 的兑换比例,从而保证 Cycles 的稳定。

代币的增发和销毁机制,在一定程度上保证了代币的流通量,但由于总量一定,终会有一天没有足够的 ICP 用于兑换 Cycles 以支持网络持续运行。

据了解,在互联网计算机运行后期,可能推出一种稳定币,用于兑换 Cycles。当然,这应该是非常后期的规划了。

子网(Subnet)

要了解互联网计算机,则必须了解子网的概念,因为子网肩负着托管容器的重任,是整个网络的基本组成部分。

NNS 将不同数据中心的节点聚焦在一起来创建子网,子网中节点机器通过 ICP 协议进行通信和协作。

subnet overview

子网包含了来自不同数据中心的节点,再加上 DFINITY 开发的拜占庭容错技术和密码技术,ICP 协议可以确保子网不被恶意篡改,并持续健康运行。

尽管子网是整个互联网计算机网络的基本组成部分,但它们对用户和软件是透明的,这种透明性实质是遵循了互联网的基本设计原则。

在现有互联网体系下,如果用户要连接到某些软件,只需要知道运行该软件的计算机的 IP 地址和监听端口;在互联网计算机上,如果用户希望调用某功能,则只需要知道容器的身份和功能签名即可(前提:获得被调方的许可)。

互联网计算机的子网透明性还体现在,NNS 可以在上层无感知的情况下,根据网络的负载情况,拆分和合并子网。

subnet split by nns

例如,我们有一个虚拟的子网 ABC,该子网上运行着 11 个容器,NNS 现在需要拆分子网:子网 ABC 继续使用容器 1–6,并生成了一个新子网,子网 XYZ,其继续使用容器 7–11。而拆分过程中,所有容器都不会遇到服务中断的情况。

当用户将容器上传到互联网计算机网络时,必须指明其子网类型。除了 Data、System、Fiduciary 三种类型外,还有一个托管 NNS 的特殊子网,但是用户无法上传容器到该子网。

subnet type

每种子网类型的容器的属性和功能有所不同:

  • 如果容器托管在 Data 类型的子网中,这个容器可以被第三方调用,但本身无法调用其它容器的服务
  • 如果希望容器能够持有 ICP 代币或将 Cycles 发送给其它容器,则需要一个 Fiduciary 类型的容器

不同类型的子网具有的容错能力也稍有不同,除此之外,NNS 还具备自动修复子网的能力。

subnet tolerance

容器(Canister)

子网托管着不计其数的容器,每个容器有专门的守护程序,容器间通过公有 API 交互。容器内部由两部分组件:可在 WebAssembly 虚拟机上运行的字节码和其中运行的内存页面。

开发可以使用 Rust 或 Motoko 之类的编程语言,开发并编译成对应的 WebAssembly 字节码。编译得到的字节码将包含一个运行时,使得开发人员可以轻松地与 API 进行交互。

canister query and update call

每个容器的调用分为更新调用和查询调用,其本质区别在于:当作为更新调用时,程序执行过程中对容器内存数据的任何改动都得以保留,而若是查询调用则会在运行后丢弃所有更改。

对于更新调用,除了持久化变更之外,ICP 协议保证子网上的每个节点都会执行调用过程,以此来实现防篡改。

canister update call

对于查询调用,它们对内存的任何更改在运行结束后都会被丢弃,不涉及到「区块链记账出块」环节,因此是非常高效而且低成本的,通常只需要几毫秒就可以完成。但由于并未在子网的所有节点上运行,也意味着安全级别较低。

canister query call

个人解读:

ICP 利用区块链的防篡改特性来保证容器更新调用的防篡改,因此即使更新调用的执行速度很快,通常也需要等待一段时间。

容器采用的 Actor 模型,每个容器有自己的「信箱」,按顺序从「信箱」中取 Message,并在处理之后,把执行结果放到调用者的「信箱」中,以此实现异步调用但顺序执行。

正交持久性(Orthogonal Persistence)

互联网计算机中,最有意思的应该是它的正交持久性了。开发人员不必再像现在一样,考虑数据的持久存储(尤其是分布式存储),而只需要编写代码,数据的持久化过程静默进行。

正交持久性主要通过保留容器的内存页实现,对于更新调用而言,容器的单执行线程设计,保证了任一时刻都只有一个线程在修改内存数据,这也大大降低了正交持久性的实现难度。

interleaved update calls

尽管容器内只有一个执行线程,但这并不意味着容器要等上一个调用逻辑处理完,再执行下一个。举个例子,当更新调用执行过程中,涉及到跨容器调用时,该调用会阻塞,执行线程转而去处理新的更新调用。

concurrent query calls

相比之下,查询调用不会对内存进行永久更改。这样一来,在任意时刻,容器内可以有任意数量的并发线程来处理查询调用,这些查询调用基于此前的内存快照进行。

容器执行过程中,可以创建新的容器或者是执行分叉操作,两者区别在于:创建的新容器,其内存页初始为空,而分叉后的副本的内存页,则和当前容器相同(对于创建可扩展的互联网服务至关重要)。

个人解读:

虽然更新调用要 1-2 秒出结果,但实质上由于所有操作都是对内存操作,速度是非常快的。

一次更新调用过程会涉及多次跨容器调用,等待跨容器调用结果的同时,可以处理其它的更新调用,可以充分利用到 CPU 的性能。如此可以保证一个容器对每个调用的响应时间。

可扩展性(Scalability)

容器在各项硬件资源上都有一定的限制,例如:由于 WebAssembly 的限制,一个容器只能存储 4GB 的内存页面。因此,当我们需要创建一个能服务于十亿用户的互联网服务时,我们必须使用多容器架构。

scaling-out internet sevices

针对这种场景,我们可能很自然地想到这种方案:创建一个特殊的容器,然后创建出若干容器的副本,并将用户内容分片到不同的容器中,以此实现可扩展的互联网服务。

然而,这个方案还是太粗糙了些!

尽管通过容器副本增加了整体的内存容量、更新调用和查询调用的吞吐量,但却无法针对特定用户内容的查询调用进行扩展。

每当我们希望添加更多的容器分片来增加系统容量时,我们都需要重新分配用户内容,也就是说这并不是真正的边缘架构。除此之外,也无法根据用户的地理位置,就近调用容器并返回结果。

在这里,我们尝试给出一个可操作的、可扩展的、真正的边缘架构方案!

互联网计算机提供了一些非常有趣的能力,用于将终端用户连接到前端容器,例如允许域名通过 NNS 映射到多个前端容器。

当终端用户进行解析域名时,互联网计算机将查看前端容器所在节点的子网情况,并返回最接近的副本节点的 IP 地址。如此一来,用户便可以在附近的副本执行查询调用,从而减少网络等待时间,用户体验也得到改善。

而且,我们并没有使用任何第三方的内容分发网络,就享受到了边缘计算的优势!

domain name mapping

为了充分利用此功能,我们通常需要一个包含前端容器和后端数据桶容器的经典架构。下面以 Web 浏览器希望加载个人资料图片为例,说明整个调用过程。

首先,Web 浏览器查询到附近运行有前端容器的子网信息。

query call step 0

然后,Web 浏览器对该节点的容器提交查询调用请求。

query call step 1

接下来,前端容器向保存照片的数据桶容器发出跨容器查询调用请求。

query call step 2

如果数据存储桶容器返回的查询调用响应涉及静态内容(例如照片),则可以将数据存储在缓存中,比如本例。

query call step 3

当然,查询调用缓存机制对于前端容器代码是完全透明的,一旦用户调用的前端容器收集了所有必要的信息,它就可以通过查询调用响应或 HTTP 端点返回内容。

query call step 4

随着时间的流逝,节点的查询缓存会累积静态内容并生成附近用户感兴趣的数据,从而为他们提供更快、更好的用户体验。

这样,互联网计算机的本机边缘体系架构提供了内容分发网络的优势,但无需开发人员做任何特殊的事情,也无需借助其它的专有服务(如:CDN)。

query call step 5

对于更新调用,经典做法则是通过散列用户名,把用户映射到特定的前端容器中。除此之外,为了防止诸如更新丢失之类的问题,需要将更新序列化为用户内容和数据。

update call step 0

一旦在 Web 浏览器或智能手机上运行的 UX/UI 确定了由哪个前端容器负责执行调用,便可以通过向其发起更新调用来修改内容。

update call step 1

然后,此前端容器通常会进行更多的跨容器更新调用,以实现所需的更改。

update call step 2

个人解读:

ICP 提供的网络架构天然具备边缘计算架构的特性,开发不再需要配置繁琐的 DNS 解析,也不需要专用的 CDN 服务,即可享受边缘计算、就近分发的特性。

开放式互联网服务(Open Internet Services)

以带有前端容器和后端数据桶容器的两级体系架构为例,在设计开放式互联网服务时,使用名为 BigMap 的类库可以大大减少前端容器的开发工作量。

design example

BigMap 可以存储 EB 级数据,仅使用一行代码就可以向其中写入对象。通过前端容器和数据桶容器分叉,可以非常方便地进行横向扩展。

要创建真正的开放互联网服务,你可能还需要一个开放的代币治理容器。如果你是一个企业家,还可以通过早期出售一些「治理代币」来筹集发展资金。除此之外,还可以设计方案,通过治理代币来激发互联网服务的早期参与者,从而获得更好的效果。

小结

相比于 BTC、ETH 等区块链,DFINITY 在出块速度上完胜,几秒的出块速度,让它具备更多的可能性,比如大裁缝口口声声说的「第三代互联网」。

其中 NNS 的设计颇有民主自治的味道,可以说是一个挺有意思的创新点。容器拥抱 WebAssembly 技术,可以吸引到各种语言栈的开发者(希望尽快支持更多语言)。容器提供的正交持久性让开发者可以更少关注数据存储的问题,但因此带来的查询/更新调用分离(为了实现 Web Speed)对开发者的开发方式也有影响:意味着一个查询调用必须只包含纯粹的查询逻辑。

总的来说,DFINITY 的技术创新点很硬核,相当于革了互联网的分布式存储的命,进而改变现有互联网的运行方式,也改变开发者的思维习惯。

但这一切的前提是,DFINITY 的生态要跟上!