您是否需要非常高吞吐量的Corda网络? 网络的吞吐量是否稳定? 您是否已经从其他领域挤出了所有可能的表现? 如果您对这些问题的回答是“是”,那么我可能会为您提供一些有用的信息。 我列出了这些问题,以减少您过早优化Corda网络/应用程序的机会。 如果它是处理请求/事务中最慢的部分之一,则切换到使用多个公证人只会对性能产生显着影响。 在考虑使用多个公证人之前,很可能需要改进其他方面。
在我继续之前。 我真的需要这么说。 在本文中,我并不是在谈论使用公证集群,该公证集群由相互沟通以就是否使用了州达成共识的公证组成。 我说的是拥有多个公证人,每个公证人都有自己的身份,这些公证人仅与向其发送交易以进行验证的节点进行交互。 这种区别必须加以区分,并且应该消除对我将在本文中准确描述的任何混淆。
在撰写本文时,Corda的当前版本为:
- 开源3.3
- 企业3.2
我为什么要这样做?
好吧 让我们真正深入探讨为什么要使用多个公证人。 图表最能做到这一点,所以让我们使用一个:
这种情况看起来不太好。 但是,实际上可能并不那么糟糕。 如果网络的吞吐量不是很高,则此体系结构应该能够处理通过公证人的事务。
如引言中所述。 当发送到公证人的交易率变得很高时,这成为一个问题。 一旦达到这一点,公证人将开始落后。 因为它不能足够快地验证事务中的状态。 如果性能对网络很重要,那么这是检查的好地方。
从代码角度来看,这是您可能已经在编写CorDapps的标准格式。 您可以根据特定条件挑选公证人,然后在其中发送交易。 您所处理的整个网络中甚至可能只有一个公证人。 例如,在编写类似于以下代码的代码之前,在我编写的所有代码示例中,它们仅依赖于网络中的单个公证人,每次都盲目地使用那个公证人。
private fun notary(): Party = serviceHub.networkMapCache.notaryIdentities.first()
切换到多个公证人
从依赖一个公证人的网络过渡到一个由许多公证人组成的设计,从根本上讲,需要两件事:
- 网络中有多个公证人。
- 一种选择向哪个公证人发送交易的算法。
此外,如果使用状态,则将来的交易会参考为交易选择的公证人。 如果最终导致消耗了来自不同公证人的输入状态的情况,则必须执行公证人变更事务。 稍后我将讨论这个主题。
下面是如何更改先前的设计以使用一些公证人的方法:
关于此图的最好之处在于,它说明了向网络添加另一个公证人并在其中重新分配负载是多么简单。 没有什么可以阻止我们向网络中添加越来越多的公证人。 但是,在某些情况下添加更多内容不会导致性能提高。 这一直回到我之前提到的内容。 添加更多的公证人只会在公证人本身达到饱和时增加吞吐量。
为发行交易选择公证人
以下是选择使用哪种公证人的可能算法:
private fun transaction(): TransactionBuilder =TransactionBuilder(notary()).apply {addOutputState(message, MessageContract.CONTRACT_ID)addCommand(Send(), message.participants.map(Party::owningKey))}private fun notary(): Party {val index = message.type.hashCode() % serviceHub.networkMapCache.notaryIdentities.sizereturn serviceHub.networkMapCache.notaryIdentities.single { it.name.organisation == "Notary-$index" }
}
在此示例中,事务根据输入状态的属性之一的hashCode
和网络中的公证人数来选择要使用的公证人。
选择公证人的方式可以根据需要简单或复杂。 这将取决于要求,例如,仅一部分公证人的提议交易受信任,或者网络变化中对公证人的弹性。
从同一公证人消费状态时选择公证人
这是很好而且很简单的…如果所有输入状态都引用同一个公证人。 下面是它的外观(此示例仅使用一个输入…,因为我懒于编写另一个版本):
private fun transaction(response: MessageState): TransactionBuilder =TransactionBuilder(notary()).apply {addInputState(message)addOutputState(response, MessageContract.CONTRACT_ID)addCommand(Reply(), response.participants.map(Party::owningKey))}private fun notary(): Party = message.state.notary
如您所见,所有事务要做的就是检索与输入状态相关的公证人,并将其用于自身。 可以提取此信息,因为message
是StateAndRef
,访问其state
属性将返回TransactionState
。 遵循这种格式。 创建消耗状态并产生大量输出的新事务非常简单。 此格式对于多个输入状态也有效。 当且仅当它们都引用同一个公证人。
因此……讨论所有带有不同公证人的输入状态。 我可能应该进一步讨论。
消费来自不同公证人的状态时选择公证人
在这里我们必须要小心,否则我们将看到类似以下的错误:
java.lang.IllegalArgumentException: Input state requires notary "O=Notary-1, L=London, C=GB" which does not match the transaction notary "O=Notary-0, L=London, C=GB".
该错误表明输入状态与包含它的事务没有相同的公证人。
要解决此错误,我们需要使用公证更改交易。 根据文档:
“用于更改州公证人的流程。 这是必需的,因为事务的所有输入状态必须指向同一公证人。”
我想把它放在那里,以防万一您以为我是个更厉害的人!
执行公证变更交易的代码如下所示:
@Suspendable
private fun notaryChange(message: StateAndRef<MessageState>,notary: Party
): StateAndRef<MessageState> =if (message.state.notary != notary) {subFlow(NotaryChangeFlow(message,notary))} else {message}
我相信您可以弄清楚自己的状况,但是要使自己变得更加聪明……我将告诉您。 message
表示输入状态, notary
是新交易将使用的公证人。 如果公证人相同,则可以返回状态,因为无需对其进行任何操作。 如果确实不同,则调用NotaryChangeFlow
,它接受传递给原始函数的两个参数。 这将返回一个新的StateAndRef
,然后从函数中返回它。
然后可以将从此函数返回的StateAndRef
放入事务中。
如果您不确定传递到事务中的状态是否来自同一公证人,那么建议您坚持使用本节中的代码。 选择事务将使用的公证人(无论是特定的公证人还是从输入状态中获取的公证人),然后对任何需要它的公证人进行变更。 例如,我认为类似于下面的代码将提供一个通用且健壮的解决方案:
@Suspendable
private fun transaction(): TransactionBuilder {val messages = getMessageStates()val notary = notary()return TransactionBuilder(notary).apply {messages.forEach {addInputState(notaryChange(it, notary))}addCommand(Delete(),(messages.flatMap { it.state.data.participants }.toSet() + ourIdentity).map(Party::owningKey))}
}@Suspendable
private fun notaryChange(message: StateAndRef<MessageState>,notary: Party
): StateAndRef<MessageState> =if (message.state.notary != notary) {subFlow(NotaryChangeFlow(message,notary))} else {message}// however you want to choose your specific Notary
private fun notary(): Party =serviceHub.networkMapCache.notaryIdentities.single { it.name.organisation == "Notary-1" }
在这里,为交易选择了一个特定的公证人,如果需要,每个输入的公证人都将更改为所选公证人,并且签名者包括消费状态的所有参与者。 这可能不适合您自己的用例。 很好。 但是,这在为不断变化的公证人服务时(主要是为了提高性能)应该是一个很好的起点。
稍稍更改此解决方案,我们可以改为根据输入状态参考的“公证人”来选择“公证人”。 由于只有notary
功能确实需要更改,因此我从示例中排除了其余代码。
private fun notary(messages: List<StateAndRef<MessageState>>): Party =messages.map { it.state.notary }.groupingBy { it }.eachCount().maxBy { (_, size) -> size }?.key ?: throw IllegalStateException("No Notary found")
该功能选择的公证人是根据输入状态共享的最常见的公证人来决定的。 这样一来,所需的公证变更交易就更少了,因为绝大多数输入已经引用了所选的公证。 如果您不知道输入参考的是哪个公证人,这应该提供最佳性能。
结论
在Corda网络中实现高性能取决于消除系统中的瓶颈和其他常规性能调整。 公证人就是这样的瓶颈之一。 在通过公证人的吞吐量非常高的情况下,网络的性能将开始达到平稳状态。 公证人不能以传入的速率足够快地处理请求。移动使用共享请求负载的多个公证人将提高网络的性能。 这在确定使用哪个公证人方面带来了额外的复杂性,并且可能需要公证人变更事务。 但是,如果您的网络确实需要实现高吞吐量。 这将是一个值得研究的领域。
我将在这里发表最后的评论。 随着公证人内部性能的提高,对这种架构的需求将减少。 甚至有一个公证人能够完全处理大量传入请求的情况。 随着Corda不断提高整体性能,这是一个值得关注的领域。
这篇文章中使用的代码可以在我的GitHub上找到 。
翻译自: https://www.javacodegeeks.com/2018/11/increasing-network-multiple-notaries.html