9月7日-8日,相约 2023 腾讯全球数字生态大会!聚焦产业未来发展新趋势! 了解详情
写点什么

区块链性能突破(七):并行合约开发框架

  • 2019-08-30
  • 本文字数:6314 字

    阅读完需:约 21 分钟

区块链性能突破(七):并行合约开发框架

“性能”是区块链领域最受关注的话题之一,如果没有高性能表现作为支撑,就无法运行快速的、执行复杂的智能合约逻辑,从而快速完成交易事务。本系列文章将以 FISCO BCOS 开源社区对区块链性能提升的研究成果为例,阐述区块链性能优化之路。


本专题系列文章追到现在,也许你会想问,FISCO BCOS 的并行到底怎么用?作为专题的完结篇,本文就来揭晓“庐山真面目”,并教你上手使用 FISCO BCOS 的并行特性!


FISCO BCOS 提供了可并行合约开发框架,开发者按照框架规范编写的合约,能够被 FISCO BCOS 节点并行地执行。


并行合约的优势有:


  • 高吞吐:多笔独立交易同时被执行,能最大限度利用机器的 CPU 资源,从而拥有较高的 TPS

  • 可拓展:可以通过提高机器的配置来提升交易执行的性能,以支持不断扩大业务规模


接下来,我将介绍如何编写 FISCO BCOS 并行合约,以及如何部署和执行并行合约。

预备知识

并行互斥

两笔交易是否能被并行执行,依赖于这两笔交易是否存在互斥。互斥,是指两笔交易各自操作合约存储变量的集合存在交集。


例如,在转账场景中,交易是用户间的转账操作。用 transfer(X, Y) 表示从 X 用户转到 Y 用户的转账接口。互斥情况如下:


此处给出更具体的定义:


互斥参数:合约接口中,与合约存储变量的“读/写”操作相关的参数。例如转账的接口 transfer(X, Y),X 和 Y 都是互斥参数。


互斥对象:一笔交易中,根据互斥参数提取出来的、具体的互斥内容。例如转账的接口 transfer(X, Y), 一笔调用此接口的交易中,具体的参数是 transfer(A, B),则这笔操作的互斥对象是[A, B];另外一笔交易,调用的参数是 transfer(A, C),则这笔操作的互斥对象是[A, C]。


判断同一时刻两笔交易是否能并行执行,就是判断两笔交易的互斥对象是否有交集。相互之间交集为空的交易可并行执行。

编写并行合约

FISCO BCOS 提供了可并行合约开发框架,开发者只需按照框架的规范开发合约,定义好每个合约接口的互斥参数,即可实现能被并行执行的合约。当合约被部署后,FISCO BCOS 会在执行交易前,自动解析互斥对象,在同一时刻尽可能让无依赖关系的交易并行执行。


目前,FISCO BCOS 提供了 solidity 与预编译合约(点击可查看预编译合约架构设计)两种可并行合约开发框架。

solidity 合约的并行框架

编写并行的 solidity 合约,开发流程与开发普通 solidity 合约流程相同。在此基础上,只需将 ParallelContract 作为需要并行的合约基类,并调用 registerParallelFunction(),注册可以并行的接口即可。


先给出完整的举例。例子中的 ParallelOk 合约实现了并行转账的功能:


pragma solidity ^0.4.25;
import "./ParallelContract.sol"; // 引入ParallelContract.sol
contract ParallelOk is ParallelContract // 将ParallelContract 作为基类{ // 合约实现 mapping (string => uint256) _balance; function transfer(string from, string to, uint256 num) public{ // 此处为简单举例,实际生产中请用SafeMath代替直接加减 _balance[from] -= num; _balance[to] += num; }
function set(string name, uint256 num) public{ _balance[name] = num; }
function balanceOf(string name) public view returns (uint256){ return _balance[name]; } // 注册可以并行的合约接口 function enableParallel() public{ // 函数定义字符串(注意","后不能有空格),参数的前几个是互斥参数(设计函数时互斥参数必须放在前面 registerParallelFunction("transfer(string,string,uint256)", 2); // 冲突参数: string string registerParallelFunction("set(string,uint256)", 1); // 冲突参数: string }
// 注销并行合约接口 function disableParallel() public{ unregisterParallelFunction("transfer(string,string,uint256)"); unregisterParallelFunction("set(string,uint256)"); } }

复制代码


具体步骤如下:


step1 将 ParallelContract 作为合约的基类


pragma solidity ^0.4.25;
import "./ParallelContract.sol"; // 引入ParallelContract.sol
contract ParallelOk is ParallelContract // 将ParallelContract 作为基类{ // 合约实现 // 注册可以并行的合约接口 function enableParallel() public; // 注销并行合约接口 function disableParallel() public;}
复制代码


step2 编写可并行的合约接口


合约中的 public 函数,是合约的接口。编写可并行的合约接口,是根据一定的规则,实现一个合约中的 public 函数。


确定接口是否可并行


可并行的合约接口,必须满足:


  • 无调用外部合约

  • 无调用其它函数接口


确定互斥参数


在编写接口前,先确定接口的互斥参数,接口的互斥即是对全局变量的互斥,互斥参数的确定规则为:


  • 接口访问了全局 mapping,mapping 的 key 是互斥参数

  • 接口访问了全局数组,数组的下标是互斥参数

  • 接口访问了简单类型的全局变量,所有简单类型的全局变量共用一个互斥参数,用不同的变量名作为互斥对象


确定参数类型和顺序


确定互斥参数后,根据规则确定参数类型和顺序,规则为:


  • 接口参数仅限:string、address、uint256、int256(未来会支持更多类型)

  • 互斥参数必须全部出现在接口参数中

  • 所有互斥参数排列在接口参数的最前


mapping (string => uint256) _balance; // 全局mapping
// 互斥变量from、to排在最前,作为transfer()开头的两个参数function transfer(string from, string to, uint256 num) public{ _balance[from] -= num; // from 是全局mapping的key,是互斥参数 _balance[to] += num; // to 是全局mapping的key,是互斥参数 }
// 互斥变量name排在最前,作为set()开头的参数function set(string name, uint256 num) public{ _balance[name] = num;}
复制代码


step3 在框架中注册可并行的合约接口


在合约中实现 enableParallel() 函数,调用 registerParallelFunction()注册可并行的合约接口。同时也需要实现 disableParallel()函数,使合约具备取消并行执行的能力。


// 注册可以并行的合约接口function enableParallel() public{    // 函数定义字符串(注意","后不能有空格),参数的前几个是互斥参数    registerParallelFunction("transfer(string,string,uint256)", 2); // transfer接口,前2个是互斥参数    registerParallelFunction("set(string,uint256)", 1); // transfer接口,前1个四互斥参数}  
// 注销并行合约接口function disableParallel() public{ unregisterParallelFunction("transfer(string,string,uint256)"); unregisterParallelFunction("set(string,uint256)"); }

复制代码


step4 部署/执行并行合约


用控制台或 Web3SDK 编译和部署合约,此处以控制台为例:


部署合约


[group:1]> deploy ParallelOk.sol
复制代码


调用 enableParallel()接口,让 ParallelOk 能并行执行


[group:1]> call ParallelOk.sol 0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744 enableParallel
复制代码


发送并行交易 set()


[group:1]> call ParallelOk.sol 0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744 set "jimmyshi" 100000
复制代码


发送并行交易 transfer()


[group:1]> call ParallelOk.sol 0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744 transfer "jimmyshi" "jinny" 80000]

复制代码


查看交易执行结果 balanceOf()


[group:1]> call ParallelOk.sol 0x8c17cf316c1063ab6c89df875e96c9f0f5b2f744 balanceOf "jinny"80000

复制代码


用 SDK 发送大量交易的例子,将在下文的例子中给出。

预编译合约的并行框架

编写并行的预编译合约,开发流程与开发普通预编译合约流程相同。普通的预编译合约以 Precompile 为基类,在这之上实现合约逻辑。基于此,Precompile 的基类还为并行提供了两个虚函数,继续实现这两个函数,即可实现并行的预编译合约。


step1 将合约定义成支持并行


bool isParallelPrecompiled() override { return true; }
复制代码


step2 定义并行接口和互斥参数


注意,一旦定义成支持并行,所有的接口都需要进行定义。若返回空,表示此接口无任何互斥对象。互斥参数与预编译合约的实现相关,此处涉及对 FISCO BCOS 存储的理解,具体的实现可直接阅读代码或询问相关有经验的程序员。


// 根据并行接口,从参数中取出互斥对象,返回互斥对象std::vector<std::string> getParallelTag(bytesConstRef param) override{    // 获取被调用的函数名(func)和参数(data)    uint32_t func = getParamFunc(param);    bytesConstRef data = getParamData(param);
std::vector<std::string> results; if (func == name2Selector[DAG_TRANSFER_METHOD_TRS_STR2_UINT]) // 函数是并行接口 { // 接口为:userTransfer(string,string,uint256) // 从data中取出互斥对象 std::string fromUser, toUser; dev::u256 amount; abi.abiOut(data, fromUser, toUser, amount); if (!invalidUserName(fromUser) && !invalidUserName(toUser)) { // 将互斥对象写到results中 results.push_back(fromUser); results.push_back(toUser); } } else if ... // 所有的接口都需要给出互斥对象,返回空表示无任何互斥对象 return results; //返回互斥}

复制代码


step3 编译,重启节点


手动编译节点的方法,参考FISCO BCOS技术文档


编译之后,关闭节点,替换掉原来的节点二进制文件,再重启节点即可。

举例:并行转账

此处分别给出 solidity 合约和预编译合约的并行举例。


配置环境


该举例需要以下执行环境:


  • Web3SDK 客户端

  • 一条 FISCO BCOS 链


若需要压测最大的性能,至少需要:


  • 3 个 Web3SDK,才能产生足够多的交易

  • 4 个节点,且所有 Web3SDK 都配置了链上所有的节点信息,让交易均匀发送到每个节点上,才能让链接收足够多的交易

并行 Solidity 合约:ParallelOk

基于账户模型的转账,是一种典型的业务操作。ParallelOk 合约,是账户模型的一个举例,能实现并行的转账功能。ParallelOk 合约已在上文中给出。


FISCO BCOS 在 Web3SDK 中内置了 ParallelOk 合约,此处给出用 Web3SDK 来发送大量并行交易的操作方法。


step1 用 SDK 部署合约、新建用户、开启合约的并行能力


# 参数:<groupID> add <创建的用户数量> <此创建操作请求的TPS> <生成的用户信息文件名>java -cp conf/:lib/*:apps/* org.fisco.bcos.channel.test.parallel.parallelok.PerformanceDT 1 add 10000 2500 user# 在group1上创建了 10000个用户,创建操作以2500TPS发送的,生成的用户信息保存在user中
复制代码


执行成功后,ParallelOk 被部署到区块链上,创建的用户信息保存在 user 文件中,同时开启了 ParallelOk 的并行能力。


step2 批量发送并行转账交易


注意:在批量发送前,请将 SDK 的日志等级调整为 ERROR,才能有足够的发送能力。


# 参数:<groupID> transfer <总交易数量> <此转账操作请求的TPS上限> <需要的用户信息文件> <交易互斥百分比:0~10>java -cp conf/:lib/*:apps/* org.fisco.bcos.channel.test.parallel.parallelok.PerformanceDT 1 transfer 100000 4000 user 2
# 向group1发送了 100000比交易,发送的TPS上限是4000,用的之前创建的user文件里的用户,发送的交易间有20%的互斥。
复制代码


step3 验证并行正确性


并行交易执行完成后,Web3SDK 会打印出执行结果。TPS 是此 SDK 发送的交易在节点上执行的 TPS。validation 是转账交易执行结果的检查。


Total transactions:  100000Total time: 34412msTPS: 2905.9630361501804Avg time cost: 4027msError rate: 0%Return Error rate: 0%Time area:0    < time <  50ms   : 0  : 0.0%50   < time <  100ms  : 44  : 0.044000000000000004%100  < time <  200ms  : 2617  : 2.617%200  < time <  400ms  : 6214  : 6.214%400  < time <  1000ms : 14190  : 14.19%1000 < time <  2000ms : 9224  : 9.224%2000 < time           : 67711  : 67.711%validation:   user count is 10000   verify_success count is 10000   verify_failed count is 0
复制代码


可以看出,本次交易执行的 TPS 是 2905。执行结果校验后,无任何错误(verify_failed count is 0)。


step4 计算总 TPS


单个 Web3SDK 无法发送足够多的交易以达到节点并行执行能力的上限。需要多个 Web3SDK 同时发送交易。在多个 Web3SDK 同时发送交易后,单纯将结果中的 TPS 加和得到的 TPS 不够准确,需要直接从节点处获取 TPS。


用脚本从日志文件中计算 TPS


cd toolssh get_tps.sh log/log_2019031821.00.log 21:26:24 21:26:59 # 参数:<日志文件> <计算开始时间> <计算结束时间>
复制代码


得到 TPS(3 SDK、4 节点,8 核,16G 内存)


statistic_end = 21:26:58.631195statistic_start = 21:26:24.051715total transactions = 193332, execute_time = 34580ms, tps = 5590 (tx/s)
复制代码

并行预编译合约:DagTransferPrecompiled

与 ParallelOk 合约的功能一样,FISCO BCOS 内置了一个并行预编译合约的例子(DagTransferPrecompiled),实现了简单的基于账户模型的转账功能。该合约能够管理多个用户的存款,并提供一个支持并行的 transfer 接口,实现对用户间转账操作的并行处理。


注意:DagTransferPrecompiled 仅做示例使用,请勿直接运用于生产环境。


step1 生成用户


用 Web3SDK 发送创建用户的操作,创建的用户信息保存在 user 文件中。命令参数与 parallelOk 相同,不同的仅仅是命令所调用的对象是 precompile。


java -cp conf/:lib/*:apps/* org.fisco.bcos.channel.test.parallel.precompile.PerformanceDT 1 add 10000 2500 user
复制代码


step2 批量发送并行转账交易


用 Web3SDK 发送并行转账交易。


注意:在批量发送前,请将 SDK 的日志等级请调整为 ERROR,才能有足够的发送能力。


java -cp conf/:lib/*:apps/* org.fisco.bcos.channel.test.parallel.precompile.PerformanceDT 1 transfer 100000 4000 user 2

复制代码


step3 验证并行正确性


并行交易执行完成后,Web3SDK 会打印出执行结果。TPS 是此 SDK 发送的交易在节点上执行的 TPS。validation 是转账交易执行结果的检查。


Total transactions:  80000Total time: 25451msTPS: 3143.2949589407094Avg time cost: 5203msError rate: 0%Return Error rate: 0%Time area:0    < time <  50ms   : 0  : 0.0%50   < time <  100ms  : 0  : 0.0%100  < time <  200ms  : 0  : 0.0%200  < time <  400ms  : 0  : 0.0%400  < time <  1000ms : 403  : 0.50375%1000 < time <  2000ms : 5274  : 6.592499999999999%2000 < time           : 74323  : 92.90375%validation:    user count is 10000    verify_success count is 10000    verify_failed count is 0
复制代码


可以看出,本次交易执行的 TPS 是 3143。执行结果校验后,无任何错误(verify_failed count is 0)。


step4 计算总 TPS


单个 Web3SDK 无法发送足够多的交易以达到节点并行执行能力的上限。需要多个 Web3SDK 同时发送交易。在多个 Web3SDK 同时发送交易后,单纯将结果中的 TPS 加和得到的 TPS 不够准确,需要直接从节点处获取 TPS。


用脚本从日志文件中计算 TPS


cd toolssh get_tps.sh log/log_2019031311.17.log 11:25 11:30 # 参数:<日志文件> <计算开始时间> <计算结束时间>

复制代码


得到 TPS(3 SDK、4 节点,8 核,16G 内存)


statistic_end = 11:29:59.587145statistic_start = 11:25:00.642866total transactions = 3340000, execute_time = 298945ms, tps = 11172 (tx/s)
复制代码

结果说明

本文举例中的性能结果,是在 3SDK、4 节点、8 核、16G 内存、1G 网络下测得。每个 SDK 和节点都部署在不同的 VPS 中,硬盘为云硬盘。实际 TPS 会根据你的硬件配置、操作系统和网络带宽有所变化。


活动推荐:

2023年9月3-5日,「QCon全球软件开发大会·北京站」 将在北京•富力万丽酒店举办。此次大会以「启航·AIGC软件工程变革」为主题,策划了大前端融合提效、大模型应用落地、面向 AI 的存储、AIGC 浪潮下的研发效能提升、LLMOps、异构算力、微服务架构治理、业务安全技术、构建未来软件的编程语言、FinOps 等近30个精彩专题。咨询购票可联系票务经理 18514549229(微信同手机号)。

2019-08-30 16:0710051

评论

发布
暂无评论
发现更多内容

JVM有哪些类加载机制?

源字节1号

软件开发

web前端培训Docker入门指南

@零度

Docker 前端开发

焱融看 | 混合云时代下,如何制定多云策略

焱融科技

存储 文件存储 混合云 多云

孔松(信通院)-数字化时代云安全能力建设及趋势

火线安全

云安全 云安全技术 云安全研究

leetcode 322. Coin Change 零钱兑换(中等)

okokabcd

LeetCode 动态规划 算法与数据结构

数据湖系列之一 | 你一定爱读的极简数据平台史,从数据仓库、数据湖到湖仓一体

Baidu AICLOUD

大数据 数据仓库 数据湖 对象存储 湖仓一体

学会使用LiveData和ViewModel,我相信会让你在写业务时变得轻松🌞

编程的平行世界

JetPack Andriod

使用强大的DBPack处理分布式事务(PHP使用教程)

峨嵋闲散人

分布式事务 分库分表 读写分离 seata dbmesh

SpringBoot工程创建Swagger文档并自动生成调用代码

百家饭隐私计算平台创业者

JavaScript Spring Boot swagger

SpringSecurity的初始化流程

急需上岸的小谢

7月月更

开源者的自我修养|为 ShardingSphere 贡献了千万行代码的程序员,后来当了 CEO

SphereEx

开源 代码 ShardingSphere

2022上半年英特尔有哪些“硬核创新”?看这张图就知道了!

科技之家

Linux设备驱动1:硬件基础

贾献华

7月月更

向Spring框架学习设计模式

慕枫技术笔记

设计模式 spring框架 7月月更

当你真的学会DataBinding后,你会发现“这玩意真香”!

编程的平行世界

JetPack andiod

介绍一种对 SAP GUI 里的收藏夹事务码管理工具增强的实现方案

Jerry Wang

SAP abap SAPGUI 企业管理软件 7月月更

大数据培训 | Scala语言知识分享,直击面试

@零度

scala 大数据开发

户外LED显示屏应该考虑哪些问题?

Dylan

LED显示屏 户外LED显示屏

研发效能度量框架解读

思码逸研发效能

研发效能 效能度量

60 个前端 Web 开发流行语你都知道哪些?

海拥(haiyong.site)

前端 Web 7月月更

如何看待国企纷纷卸载微软Office改用金山WPS?

优秀

wps office办公软件

一文读懂TDengine的窗口查询功能

TDengine

tdengine 时序数据库

龙蜥社区开源 coolbpf,BPF 程序开发效率提升百倍

OpenAnolis小助手

Linux 开源 内核 龙蜥技术 BPF

刘对(火线安全)-多云环境的风险发现

火线安全

云安全 云安全技术 云安全研究

SAP 智能机器人流程自动化(iRPA)解决方案分享

Jerry Wang

SAP 业务流程自动化 7月月更 企业自动化 iRPA

直播带货系统软件开发,Android和iOS的区别在哪里?

开源直播系统源码

ios开发 Android开发 直播带货系统 原生开发 混合开发

Java培训 | 详解 Linux 中的权限,这一篇就够了

@零度

Linux JAVA开发

进入前六!博云在中国云管理软件市场销量排行持续上升

BoCloud博云

云原生 cmp 云管理

陈宇(Aqua)-安全->云安全->多云安全

火线安全

云安全 云安全技术 云安全研究

洞态在某互联⽹⾦融科技企业的最佳落地实践

火线安全

漏洞检测 IAST

[Ljava.lang.Object;是什么?

okokabcd

Java

  • 扫码添加小助手
    领取最新资料包
区块链性能突破(七):并行合约开发框架_架构_石翔_InfoQ精选文章