VM Engine是长安链智能合约引擎的推荐选型,采用Docker容器化架构,容器内部由一个任务调度器和多个合约进程组成,实现了多合约隔离与多进程并发,支持独立部署,目前支持Golang语言合约。
1. 背景说明
自2009年11月以来,Golang语言已经发展了13年,凭借着高并发、高性能、高开发效率迅速发展为热门语言,尤其在区块链领域,Golang语言已然成为底链必不可少的语言选型。在介绍总体架构前,我们需要解释两个具体的技术选型背景:
第一,长安链已经支持轻量级的TinyGo了,我们为什么还要选择原生Go语言?有如下两个原因:
1. 内存管理缺陷:TinyGo对wasm的支持不太完善,对内存逃逸分析、GC等方面有不足之处,容易造成栈溢出。因此对于循环、函数栈深度、内存申请等有较大的限制。
2. 导入包受限:TinyGo对导入包的支持有限(参考https://tinygo.org/lang-support/stdlib/),实际测试支持的并不完整,会发生一些错误,难以满足复杂的合约场景。
相信使用过TinyGo的开发者都有过上述经历。与公链有所不同,联盟链的合约更关注语言的丰富度与自由度,我们完全可以选择原生Go语言,从而使用更完备的类库,实现更稳定的区块链应用。
第二,我们为什么使用Docker容器化服务来支撑VM Engine项目呢?有如下三个原因:
(1) 可移植性:VM Engine启动Docker容器来运行合约虚拟机,调度和执行合约任务。用户能够在任意Linux机器上快速部署一套VM Engine虚拟机容器。
(2) 可扩展性:VM Engine与区块链属于不同的进程,通过gRPC进行通信,这牺牲了一部分性能,但是具有更高的扩展性。VM Engine能够进行独立部署,作为合约计算引擎服务提供给多个区块链。
(3) 安全性:如果要避免进程间/网络通信,需与区块链同进程,要用插件系统(Go Plugin),插件系统目前不成熟,无法主动释放,内存占用大。更重要的是,原生Golang代码自由度很高,用户合约行为难以限制。
基于上面两种技术选型我们依次推出了VM Docker Go和VM Engine核心引擎。
VM Engine是VM Docker Go的重构版本,相比原有版本,VM Engine具有50%的性能提升,支持任意合约语言的跨合约调用,更合理的进程资源限制,更稳定的合约执行环境。
在介绍VM Engine前,我们先介绍VM Docker Go的设计理念与架构。
2. VM Docker Go的架构设计
VM Docker Go使用单容器多进程架构,与区块链通过双向流式gRPC的方式进行通信,具体模块交互架构方案如下:
图1
其中,各个模块的介绍如下:
1. ChainMaker:长安链区块链项目主模块。
2. VM Docker Go:基于Docker的容器化合约交易调度和执行引擎。
2.1 Contract Engine:合约交易调度引擎,负责转发请求/响应,调度请求与合约进程资源
i. ChainMaker RPC:区块链RPC服务端,VM Docker Go与ChainMaker交互的唯一模块,交互内容主要包括交易请求、合约文件请求与响应、合约调用请求与响应和交易执行结果;
ii. Docker Scheduler:容器调度器,负责解析并转发请求/响应;
iii. Process Manager:进程管理器,负责管理请求/合约进程资源;
iv. Process Balance:进程负载均衡器,负责管理同一合约同一版本的所有合约进程资源和交易;
v. Process:进程控制器,负责管理一个合约进程的生命周期;
vi. Process Handler:进程处理器,负责接收并调度合约进程的所有调用交互;
vii. Sandbox RPC:合约进程RPC服务端,Contract Engine与Sandbox交互的唯一模块,交互内容主要包括交易请求、合约调用请求、合约调用响应和合约进程执行结果。
2.2 Sandbox:合约进程,合约二进制代码的运行实体,同一个合约可能有多个合约进程,从而实现交易并发。
通过上述架构设计,我们总结出【VM Docker Go的设计思想】如下:
(1) 合约隔离:每个合约任务用独立进程(Sandbox)来执行,采用CGroup限制进程资源的占用(暂未强制限制),避免合约恶意消耗、非法访问或写入系统资源。
(2) 任务独立:Sandbox中同时只存在一笔交易,这会牺牲较多性能,但能够避免Sandbox出现问题时多笔交易失败,读写集不一致。
(3) 单合约最大进程数并发:为了尽可能避免Sandbox进程关闭与启动,系统会尽量为每个任务创建新Sandbox进程,直到达到用户设定的单合约进程数最大阈值,一定时间空闲后释放进程。但这样也会引入进程数量无法控制的问题。
3. VM Docker Go重构的原因
我们为什么要对VM Docker Go进行重构呢,我们的目标如下:
1. 更高效:VM Docker Go原生Go语言的性能方面已经非常优异。但我们依然想要达到更高的性能,调整的方式如下:
(1) 避免多余的RPC通信,Sandbox与链进行常规交互时,可以不经过Contract Engine;
(2) 总进程数限制,让单个合约能够拥有更多的进程资源并发执行交易;
(3) 其他优化,包括gRPC调优、缓存、分段锁、并发控制等。
2. 更稳定:VM Engine功能支持更加完备,极端情况下表现稳定:
(1) VM Docker Go采用单合约进程数限制方式来限制进程资源,单合约进程数限制设置过大时,在合约数过多时,容易出现大量进程创建的情况,设置过小时,在压力峰值时,资源无法充分利用;如果预设用户数不合理,会导致进程无法分配到用户而超时,重构方案实现了抢占式的合约进程资源动态切换与调度算法,对总进程数进行了限制,充分利用进程资源;
(2) 跨合约调用, VM Docker Go仅支持调用同样的合约,重构支持所有合约相互调用;
(3) VM Engine采用基于流式的任务处理与消息通知机制,吞吐量更高,架构设计更合理。
4. VM Engine架构设计与常见调用流程
4.1 VM Engine架构设计
VM Engine的架构进行了重构设计,具体的系统架构如下:
图2
4.1.1 ChainMaker:长安链区块链项目主模块
(1) Runtime Server:合约交互服务端,直接处理来自Sandbox的交易请求和执行结果.
(2) Contract Engine Client:合约引擎客户端,负责发送交易请求/合约文件给Contract Engine。
(3) VM Runtime Instance:交易运行时处理器,负责处理交易运行时的所有请求与响应。
4.1.2 VM Engine:基于Docker的容器化合约交易调度和执行引擎。
(1) Contract Engine:合约交易调度引擎,负责转发请求/响应,调度请求与合约进程资源。
i. Contract Engine Server:合约引擎服务端,VMEngine与ChainMaker交互的唯一模块,交互内容主要包括交易请求、合约文件请求与响应和交易异常信息;
ii. Request Scheduler:请求调度器,负责解析并转发请求/响应;
iii. Request Group:交易组,负责管理同一合约同一版本的所有交易,并根据交易存数来申请进合约程资源;
iv. Contract Manager:合约管理器,负责管理合约文件/缓存信息;
v. Process Manager:进程管理器,负责管理合约进程资源;
vi. Process:进程控制器,负责管理一个合约进程的生命周期;
vii. User Manager:用户管理器,负责管理合约进程Linux用户;
viii. Security:安全控制器,负责CGroup等系统资源管理。
(2) Sandbox:合约进程,合约二进制代码的运行实体,同一个合约可能有多个合约进程,从而实现交易并发(Java版本尚未推出)
i. Runtime Client:合约交互客户端,直接向ChainMaker进行调用请求;
ii. SDK Instance:合约SDK实例,存储交易相关元信息;
iii. Contract Engine Client:交易传输客户端,接收来自VM Engine的交易并返回完成信号。
4.2 VM Engine常见调用流程
下面我们介绍几种常见的调用流程:
4.2.1 简单合约调用流程
阶段1:发送交易
图3
该阶段由ChainMaker并发调用VM Runtime Instance,多笔交易调用请求会通过Contract Engine Client发送到VM Engine,Contract Engine Server接收到后会直接发送给Request Scheduler进行调度。
阶段2:调度交易
图4
Request Scheduler根据交易中的链ID、合约名、合约版本将其分配到不同的Request Group中,如果交易积攒过多,Request Group会向Process Manager申请进程资源,Process Manager优先分配未启动进程,再尝试切换闲置进程资源。Process监听Tx Channel,并发送给Sandbox执行任务。
阶段3:合约调用
图5
Sandbox在执行过程所有的合约交互都直接与ChainMaker的Runtime Server进行同步交互。由于交易隔离性和安全性考虑,Sandbox内部不支持交易并发执行。
阶段4:返回结果
图6
Sandbox执行完成后,会向ChainMaker的Runtime Server返回执行结果,并向Contract Engine的对应的Process发送完成信号,从而继续处理下一笔交易。
4.2.2 创建合约流程
所有交易都是从创建合约开始的,创建合约的具体流程如下:
图7
事实上,如果Contract Engine合约丢失,它也会向ChainMaker发送请求获取合约文件。
4.3 跨合约调用流程
图8
跨合约调用本质上是调度一笔新的交易执行,执行结果返回给上一个等待的交易,该版本支持不同语言间跨合约调用。
5. VM Engine进程状态机设计与进程资源分配机制
5.1 VM Engine进程状态机设计
VM Engine中进程资源的状态机设计包括Process详细状态和Process Manager总体状态,双方保持强一致性。具体状态机如下:
图9
Process状态机:
(1) created:初始创建状态,进程刚刚创建;
(2) ready:就绪状态,进程创建后握手成功或刚刚执行完成交易或刚从idle状态转换过来;
(3) busy:忙碌状态,进程正在执行交易;
(4) idle:闲置状态,进程就绪一段时间后都没有新交易执行;
(5) changing:进程切换到新合约进程的中间态,闲置后可以被其他合约进程抢占,即将切换合约信息与任务信息;
(6) closing:进程关闭的中间态,进程即将退出并销毁。
Process Manager状态机:
(1) busy,忙碌状态,包括Process中的created、ready与busy状态,该状态下进程资源不可被抢占;
(2) idle,闲置状态,对应Process中的idle状态,该状态下进程资源可以被抢占。
5.2 VM Engine进程资源分配机制
Request Group进程申请机制:每一个Request Group都包含原始交易队列和跨合约交易队列,分别放置第一层交易和>1层子交易,在合约就绪、新交易入队和上次进程返回时触发合约进程资源申请,申请进程数为:总任务数-处于ready/busy状态中的合约进程,最小值为0。
Process Manager进程数据结构:原始进程与跨合约进程由不同的Process Manager管理,内部结构保持一致,主要包括:
(1) 闲置进程,长时间没有执行交易的进程,随时会被抢占;
(2) 忙碌进程:正在处理/准备处理任务的进程;
(3) 等待中的Request Group,有闲置进程时,优先释放给队头元素。
Process Manager进程分配原则:申请新进程时,优先启动进程,如果不够则释放闲置进程,最后将其加入等待队列。(如果申请数为0,说明不需要进程,将其从等待队列移出)
Process Manager进程回收原则:系统会进行进程资源的周期性回收,回收数为配置的回收比例和闲置进程的最小数。
图10
6. VM Engine的架构优势与实际效果
VM Engine的架构设计优势如下:
(1) 性能更高,通过gRPC通信次数的降低、gRPC参数的调优、协程状态机的合理控制、缓存与并发的优化等,VM Engine具备更高的性能;
(2) 稳定性更高,通过合约进程资源总数的最大限制与合理的进程资源动态切换与调度算法,合约进程能够以较优的方式进行动态分配,高效并发地执行交易;
(3) 应用更广泛,支持多语言的跨合约调用,能够满足任意语言的合约之间相互调用;
(4) 可维护性更高,模块分工明确,采用基于流式的任务处理与消息通知机制与进程状态机控制,架构设计更加清晰。
为了验证重构的效果,我们按照下面的测试条件进行测试:
图11
性能测试结果如下:
图12
7. 兼容性问题
VM Engine在v2.3.0版本推出并长期维护,对于VM Docker Go合约/引擎用户来说,如何实现兼容呢?
由于gRPC架构的调整,v2.3.0及后续版本的VM Engine与v2.3.0及后续版本合约SDK(contract-sdk-go)是配套的,而VM Docker Go是与v2.2.X版本的合约SDK是配套的。如果想要使用VM Engine,请使用新版合约SDK,并启用VM Engine,如果想要使用VM Docker Go,请使用旧版合约SDK,并启用VM Docker Go。具体如下:
1. 仅使用VM Engine,请使用对应的新版本合约SDK编译合约,并在chainmaker.yml中启用VM Engine,此时区块链会拉起VM Engine合约容器,安装并调用新合约时只会发送到VM Engine容器中(如果使用了旧版的合约SDK编译合约,会收到VM Docker Go未配置的错误信息);
2. 仅使用VM Docker Go,请使用对应的旧版本合约SDK编译合约,并在chainmaker.yml中启用VM Docker Go,此时区块链会拉起VM Docker Go合约容器,安装并调用旧合约时只会发送到VM Docker Go容器中(如果使用了新版的合约SDK编译合约,会收到VM Engine未配置的错误信息);
3. 同时使用VM Engine与VM Docker Go,分别使用对应的合约SDK编译合约,在chainmaker.yml中同时启用两种容器,此时区块链会拉起两种容器,安装并调用合约会发送到各自的容器中。
另外,cmc与sdk的调用方式不变,新版VM Engine额外支持将方法从调用的params中抽离到外层的methods中(详情请参考长安链官方文档)。
8. 总结
VM Engine在VM Docker Go的基础上进行了全面的重构和优化,包括架构优化设计、多种性能提升手段应用、进程总数资源限制、跨合约调用扩展等等。新的合约引擎将作为核心支撑持续保障长安链项目的稳定高效运行。
长安链ChainMaker
长安链 · ChainMaker是新一代区块链开源底层软件平台,包含区块链核心框架、丰富的组件库和工具集,致力于为用户高效、精准地解决差异化区块链实现需求,构建高性能、高可信、高安全的新型数字基础设施。
38篇原创内容
公众号