关键要点
- 这项技术只适用于非常少量的场景。
- 对于去中心化的分类账应用程序来说,Hyperledger Fabric 在验证写入批次时使用 MVCC(多版本并发控制)已经足够安全,但对于 B2C 初创公司来说还不够具有吸引力,因为这种方式可伸缩性不足。
- 如果你可以保证所有交易的幂等性,那么就不必使用这项技术。
- 这项技术仍然有些不成熟。
- 尽管这是一个开源项目,但要真正应用到生产环境中,还需要更多云供应商的支持(这种情况可能会发生改变)。
Hyperledger Fabric 是一个具有三年历史的开源项目,大约两年前,它的代码库被迁移到 GitHub 上。我一直在关注这个开源项目。Hyperledger 项目由 Linux 基金会托管,并主要由 IBM 提供赞助。他们致力于推动使用私有或许可的区块链。在公共区块链中,第一个解决加密难题的匿名矿工可以将分类帐的下一个区块提交到链上,而私有区块链使用诸如 Raft 或 Paxos 之类的算法解决共识问题。
在区块链中,你可以使用 CRUD 的方式访问分类帐。你还可以在分类帐上存储被称为智能合约的迷你程序。当事务被提交给智能合约时,在链代码中执行的所有分类帐状态变更操作都具有原子性——要么所有操作都被提交,要么都不提交。如果链代码访问的底层分类帐数据在链代码提交操作时发生变更,事务将被中止。这个过程是自动发生的,并且是智能合约程序的重要组成部分。
发布 Hyperledger Fabric 的人还发布了另一个开源项目 Hyperledger Composer,它让开发人员可以轻松开发 Hyperledger Fabric 和 DApps(去中心化应用程序)的链代码。
为什么是现在?
Thoughtworks 是一家技术咨询公司(被 Apax Partners 收购),它把自己定位为“由一群充满激情的人组成的社区,目的是彻底改变软件的设计、开发和交付”。他们每年发布两次技术雷达报告,为是否暂停、评估、试用或采用某些特定的技术提供建议。第 18 卷技术雷达(Technology Radar)于 2018 年 5 月发布,它将 Hyperledger Composer 放在试用环中。试用环的定义为“值得追求。非常有必要了解如何建立这种能力。企业应该在可以承担风险的项目上尝试这项技术“。
作为软件架构师,我对新兴技术进行了评估,并将 Hyperledger Composer 放在我自己的技术雷达中。在评估一项新兴技术时,我都会用它来实现一个简单的新闻源微服务。这些微服务提供了相同的功能,并且可以通过完全相同的方式进行负载测试。通过这种方式,我就可以将某项技术与其他技术进行对比,从而总结出它们的性能特征。我选择了新闻源服务,因为它在社交网络中普遍存在,人们也非常熟悉,并且它足够复杂,不是简单的方案就能解决,但又足够简单易懂,不至于迷失在实现细节中。我在 GitHub 上发布了这些微服务的源代码,以及用于对它们进行负载测试、收集和分析性能结果所需的源代码。本着同行评审的精神,我希望你们能够拉取这些代码,并自己尝试重现这些结果。
构建测试微服务
有了 Hyperledger Composer,你就可以在服务器端使用 JavaScript 开发智能合约。它提供了一个原生客户端库,Node.js 应用程序可以通过这个库访问分类帐,并将事务提交给智能合约。出于实验的目的,我使用已经开发好的 Node.js 微服务(请参阅代码库中的 server/feed4 )作为统制。我将该微服务的源代码复制到一个新文件夹中(请参阅代码库中的 server/feed7/micro-service ),然后将所有对 MySQL、Redis 和 Cassandra 的引用替换为调用 Hyperledger Composer 客户端 API。 feed7 是这次实验中的测试项目。两个项目都使用了 Elasticsearch,因为新闻订阅服务的基本功能之一是基于关键字的搜索,区块链不适合用来实现这项功能。与这个代码库中的其他大多数微服务一样,feed7 使用 Swagger 来定义 REST API。该规范可以在 server/swagger/news.yaml 文件中找到。
你可以使用 Hyperledger Composer 创建一个由数据模型、操作数据模型的一组事务以及一组供事务访问模型数据的查询组成的业务网络。Hyperledger Composer 可以与 Hyperledger Fabric 配合使用,Hyperledger Fabric 的基本网络包括 CouchDB 、默认对等体、业务网络对等体、证书认证服务和 orderer。feed7 微服务在业务网络中访问 Hyperledger Fabric,你可以在 server/feed7/business-network 文件夹中找到这个业务网络。
图 1:Feed7 组件(测试)
在这个业务网络模型中,broadcaster 是参与者。模型中还有 friendship、inbound 和 outbound 元素。friendship 用于捕获两个 broadcaster 之间的联系。每个 inbound 元素都是相关 broadcaster 的新闻项,而 outbound 元素是由 broadcaster 发出的新闻项。这个业务网络中有两个交易:broadcaster 之间可以交互,也可以向其他 broadcaster 发送新闻项。业务网络内唯一需要的查询是用于访问其他 broadcaster 的广播事务。
async function broadcastParticipants(tx) { const factory = getFactory(); const created = Date.now(); const now = new Date(); const k = tx.sender.participantId + '|' + created + '|'; const outboundRegistry = await getAssetRegistry('info.glennengstrand.Outbound'); const ok = 'Outbound:' + k + Math.random(); const inboundRegistry = await getAssetRegistry('info.glennengstrand.Inbound'); var o = factory.newResource('info.glennengstrand', 'Outbound', ok); o.created = now; o.subject = tx.subject; o.story = tx.story; o.sender = tx.sender; await outboundRegistry.add(o); const friends = await query('broadcasterFriends', { broadcaster: 'resource:info.glennengstrand.Broadcaster#' + tx.sender.participantId }); for (i = 0; i < friends.length; i++) { const friend = friends[i]; const ik = 'Inbound:' + k + Math.random(); var inb = factory.newResource('info.glennengstrand', 'Inbound', ik); inb.created = now; inb.subject = tx.subject; inb.story = tx.story; inb.recipient = friend.to; await inboundRegistry.add(inb); } }
代码示例 1:智能合约
在智能合约中调用的 Hyperledger Composer API 与 Node.js DApp 调用的 API 非常类似,但还是存在一些差别。在智能合约中需要使用 async/await 机制,而在 DApp 中需要使用 promise。智能合约必须使用预定义的查询,而 DApp 可以动态构建和运行查询。从 DApp 中查询或检索参与者或元素时,你必须将常量“PID:”作为密钥的一部分,但通过链代码访问相同的数据时则不需要。
function submitTransaction(bizNetworkConnection, transaction, from, subject, story, callback, retry) { const elastic = require('../repositories/elastic'); bizNetworkConnection.submitTransaction(transaction) .then((result) => { const retVal = { "from": from, "occurred": Date.now(), "subject": subject, "story": story }; elastic.index(from, story); callback(null, retVal); }).catch(() => { setTimeout(() => { submitTransactionRetry(bizNetworkConnection, transaction, from, subject, story, callback, 2 * retry); }, retry + Math.floor(Math.random() * Math.floor(1000))); }); } exports.addOutbound = function(args, callback) { const BusinessNetworkConnection = require('composer-client').BusinessNetworkConnection; const bizNetworkConnection = new BusinessNetworkConnection(); bizNetworkConnection.connect(process.env.CARD_NAME) .then((bizNetworkDefinition) => { const factory = bizNetworkDefinition.getFactory(); var transaction = factory.newTransaction('info.glennengstrand', 'Broadcast'); transaction.sender = factory.newRelationship('info.glennengstrand', 'Broadcaster', 'PID:' + args.body.value.from); transaction.subject = args.body.value.subject; transaction.story = args.body.value.story; submitTransaction(bizNetworkConnection, transaction, args.body.value.from, args.body.value.subject, args.body.value.story, callback, 2000); }); }
代码示例 2:DApp 调用智能合约
你可能已经注意到,在提交事务时使用了重试逻辑。这是因为 Hyperledger Fabric 在验证写批次时使用了MVCC(多版本并发控制),很容易引发读取冲突错误,所以需要sleep 一段时间,然后重试提交事务。
在负载下测试微服务
统制和测试使用了相同的负载测试应用程序,你可以在代码库的 client/load 文件夹中找到它。负载测试在一个循环中创建了 10 个参与者,并为每个参与者提供两到四个朋友。它让每个参与者广播 10 个新闻项,每个新闻项由 150 个随机生成的数字组成。负载测试应用程序会启动三个线程,每个线程将 90%的时间用于生成新闻项,另外 10%用于测试搜索功能。
负载测试应用程序并不会直接调用新闻源微服务,而是调用一个名为 Kong 的开源 API 网关,这个网关将负载测试应用程序的请求代理给新闻源微服务。Kong 使用了 http-log 插件,以便将请求和响应日志发送到另一个微服务,后者又将与性能相关的部分批量发送到 Elasticsearch。你可以在 client/perf4 文件夹中找到 Kong 日志微服务的源代码。
我使用 Kibana 来可视化性能数据,包括吞吐量、平均延迟和百分位延迟。只要有可能,我总是会收集两小时内的性能指标摘要。
图 2:测试(即 Hyperledger Composer 和 Fabric)outbound 请求的每分钟吞吐量
图 3:测试(即 Hyperledger Composer 和 Fabric)outbound 请求的每分钟平均延迟
我进行了两次统制部署,每次都使用了 m4.xlarge 实例。其中有一次 feed4 服务运行在 Docker 容器中,而另一次则没有。使用 Docker 运行时吞吐量降低了 6%,但延迟几乎没有差别。我也进行了两次测试部署,使用 m4.xlarge 实例部署 Kong、Cassandra、Elasticsearch 和负载测试应用程序。第一次测试在 m4.xlarge 上部署了 Hyperledger Fabric、Composer、feed7 业务网络和微服务,第二次测试使用了 m4.2xlarge,以便比较扩展之后的性能差异。
图 4:Feed7 部署(测试)
为了进行有效的比较分析,也因为生产配置不易获得,所以统制和测试都使用了开发配置。AWS CloudFormation 为 Hyperledger Fabric 提供了一个模板,但它只能用于部署基础网络。除了 IBM Cloud 的广告之外,我能够找到的有关生产配置的唯一在线文档是 VMware 的一些人在 Hacker Noon 上发表的博文。博文中提供的生产配置和图表表明,orderer 使用了 Kafka,但 GitHub 代码库中的 configtx.yaml 文件显示的却是独立的 OrdererType,而不是 Kafka。说明那只是开发配置。源代码中有一个注释写道:“独立共识方案非常简单,一个给定的链只需要一个共识者。它接收通过Order/Configure 传递进来的消息,对它们进行排序,然后使用blockcutter 将消息切成块,再写入指定的分类账中”。
性能结果
对于 Hyperledger 来说,在负载测试性能方面,既有好的一面也有不好的一面。不好的一面:Hyperledger 版本的新闻源在吞吐量上减少了 300 多倍,比传统版本慢了三个数量级。好的一面是,增加一倍硬件能力让吞吐量提高了 20%,并将延迟几乎减少了一半。
统制每分钟(RPM)持续发送超过 13,000 个 outbound 请求(即新闻广播),平均延迟为 4 毫秒,99 百分位为 9 毫秒。对于 m4.xlarge,测试平均每分钟有 29 个 outbound 请求,而 m4.2xlarge 则为 38。m4.xlarge 的平均延迟为 4.7 秒,m4.2xlarge 的平均延迟为 3.2 秒。对于 m4.xlarge,99 百分位延迟为 10.2 秒,对于 m4.2xlarge,99 百分位延迟为 4.9 秒。
图 5:outbound 性能比较,延迟以毫秒为单位
我还需要提到其他一些与性能低下有关的问题。统制程序的 CPU 和性能相关的指标很快就进入稳定状态,而测试中的相同指标随着时间的推移变得越来越糟。CPU 的最大使用者是 Fabric 中的默认对等进程。这点令人感到惊讶,因为微服务总是访问新闻源业务网络,但它对应的对等容器并不是 CPU 密集型的。也许默认对等体被用来支持交易?我找不到从配置中删除它的方法。在生产配置中,你将拥有多个对等方,否则分类帐就不是去中心化的了。
对于测试和统制,一旦 SSD 上的数据库可用空间耗尽,微服务也就随之崩溃。对于统制,在发出近 3000 万个 outbound 请求后,Cassandra 数据库出现可用空间不足。对于测试,在发出大约 4,000 个 outbound 请求后,CouchDB 数据库出现可用空间不足。用于统制和测试的 SSD 存储具有相同的容量,即 20GB。显然,存储效率目前不是 Hyperledger Fabric 项目开发人员的主要关注点。
结论
最初,我认为新闻源应用程序将是区块链的一个很好的用例。负载测试应用程序的主要操作是添加新闻项,这看起来非常类似于添加项目到分类帐。然而,现在,我认为这种类比是很肤浅的。区块链的主要问题是防止所谓的双重花费(double-spend)问题——如果区块链不能阻止参与者两次花同一笔钱,那它又有什么用?对于公共区块链,通过未使用的事务输出或 UTXO 来处理这个问题。Hyperledger Fabric 在验证写批次时通过对读取集进行 MVCC 控制来解决这个问题。Fabric 确实有效率问题,但效率问题可以等它发展成熟之后再来解决,不够我认为使用 MVCC 来防止双重花费是造成低吞吐量和高延迟的主要原因。因此,新闻源事务基本上是幂等的。如果两个参与者以不同的顺序或多次与自己交友,或者以不同的顺序或多次向彼此广播项目,并不会造成不良后果。Fabric 分配了大量的 CPU 时间和内存来防止出现会对新闻源产生重大影响的问题。
经过这次评估,我相信软件开发的未来不会被区块链吃掉。只有非常少的场景才需要这种高计算成本的自动化、有保证的分布式并发控制和验证。基本上,你需要一个无法进行幂等交易的市场。现在评估 Hyperledger Composer 还算不错,但以目前的成熟度来看,想要在不久的将来应用到生产中仍然是有问题的。Hyperledger 项目都是开源项目,但在撰写本文时,要想应用到生产环境,云供应商提供的选项仍然很有限。
关于作者
Glenn Engstrand 是 Adobe 公司的软件架构师。他的工作重点是与工程师合作,提供可伸缩且符合 12 factor 标准的服务器端应用程序架构。Glenn 是 2018 年和 2017 年 Adobe 内部广告云开发者大会以及 2012 年波士顿 Lucene Revolution 大会的最受关注演讲者。他专注于将单体应用程序分解为微服务以及与实时通信基础设施的深度集成。
查看英文原文: Evaluating Hyperledger Composer
评论 1 条评论