高パフォーマンス BaaS アプリケーションの設計
このトピックでは、チェーンコードと SDK アプリケーションを最適化して、Fabric ブロックチェーンアプリケーションのスループットを向上させる方法について説明します。 このトピックでは、ネットワーク帯域幅の消費とエンドースメントの遅延の削減に焦点を当てています。 このトピックでは、Fabric におけるトランザクションの基本的な概念を紹介し、チェーンコードの設計や SDK アプリケーションの使用など、アプリケーションのパフォーマンスを向上させるための重要なポイントを分析します。
トランザクションフロー
SDK はトランザクション提案を生成します。 提案とは、特定の入力パラメーターを使用してチェーンコード関数を呼び出すリクエストです。
SDK は、複数のピアノードに提案を送信します。
ピアノードは、提案によって提供された情報に従って、ユーザーがアップロードしたチェーンコードを呼び出します。
チェーンコードが実行され、応答値、読み取りセット、書き込みセットを含むトランザクション結果が生成されます。
これらの値のセットは、エンドースメントピアの署名とともに、提案応答として SDK に返されます。
SDK は、複数のピアから提案応答を受信し、読み取りセット、書き込みセット、および異なるノードの署名をパッケージ化してエンベロープを形成します。
SDK は、エンベロープを注文者ノードに送信し、ピアノードのブロックイベントをリッスンします。
注文者ノードは、十分なエンベロープを受信すると、新しいブロックを生成し、すべてのピアにブロックをブロードキャストします。
各ピアは、受信したブロックを検証し、新しいブロックの検証結果を SDK に送信します。
SDK は、イベントの検証結果に基づいて、トランザクションが正常にチェーンされているかどうかを判断します。
チェーンコードの最適化
チェーンコードの最適化により、チェーンコードがトランザクションを処理するために使用する時間を短縮し、チェーンコードがより多くのトランザクションを同時に処理できるようにします。
キーの競合の回避
Fabric ブロックチェーン台帳では、データはキーと値のペアで格納され、チェーンコードは GetState、PutState などのメソッドを呼び出すことによって台帳データを操作できます。 各キーにはバージョン番号があります。 同じブロックの 2 つの異なるトランザクションが同じキーバージョンを更新する場合、キーの競合が原因で一方のトランザクションは失敗します。 トランザクションの順序は、注文者ノードがブロックを生成するときに決定されます。 最初のトランザクションはキーバージョンを更新しているため、2 番目のトランザクションがキーを更新すると、キーの競合が原因で失敗します。
他のブロックチェーンとは異なり、Fabric のブロックには無効なトランザクションを含めることができます。 キーの競合が原因で多数の無効なトランザクションが生成された場合、これらのトランザクションは各ノードの台帳に記録され、ノードのストレージ容量を消費します。 キーの競合により、多くの並列トランザクションが失敗し、1 秒あたりの成功トランザクション数が大幅に減少します。 同時に、無効なトランザクションは依然としてネットワークスループットを消費します。
チェーンコードロジックを改善することで、異なるトランザクションによって同じキーが更新される頻度を減らすことができます。 たとえば、トランザクションが同じキーを更新する時間間隔を長くして、頻繁な更新を減らします。以前のトランザクションがキーを更新し、情報が台帳にコミットされた後にのみ、トランザクションがキーを更新できるようにすることをお勧めします。
スタブ読み取りと台帳更新の回数を減らす
SDK とブロックチェーンノード間の通信と同様に、チェーンコードとピア間の通信は gRPC を介して行われます。 チェーンコードを使用すると、GetState API や PutState API など、API を使用して台帳をクエリおよび更新できます。 チェーンコードは、gRPC リクエストをピアノードに送信します。 ピアノードは結果を返し、チェーンコードロジックに送信します。 これらの API が Query/Invoke で複数回呼び出されると、通信コストが増加します。 これにより、ネットワーク通信が遅延し、スループットに影響します。 理由の詳細については、「チェーンコードの計算ワークロードを削減する」を参照してください。
アプリケーションを設計する際には、1 つの Query/Invoke で台帳に対するクエリおよび更新操作を減らす必要があります。 高スループットが必要な特定のシナリオでは、ビジネスレイヤーで複数のキーと値のペアを組み合わせて、複数の読み取りおよび書き込み操作を 1 つの操作に変えることができます。
チェーンコードの計算を削減する
チェーンコードが呼び出されると、読み取りクエリのみが許可されます。 これにより、呼び出されたチェーンコードが後続のコミットフェーズの状態検証チェックに参加しないことが保証されます。 新しいブロックが生成されると、ピアは台帳が更新されるまで台帳をロックしたままにします。 チェーンコードがトランザクションの処理に時間をかけすぎると、ピアでの検証はより長い時間待機する必要があり、全体的なスループットが低下します。
単純なロジックや検証など、必要な計算のみを含めるのが最善です。
SDK の最適化
Java SDK
このトピックでは、バージョン fabric-sdk-java-1.4.0 に焦点を当てています。 Java SDK は柔軟に使用できますが、アプリケーションのパフォーマンスに深刻な影響を与える可能性のある欠点があります。
1. チャンネルオブジェクトとクライアントオブジェクトを再利用する
SDK は、チャンネルオブジェクトの初期化プロセス中に一定量の risorse と時間を消費します。 同時に、各チャンネルオブジェクトはイベントリスニング用に独自の接続を確立し、ピアから最新のブロックと検証結果を取得します。これには大量のネットワーク帯域幅が消費されます。 アプリケーションがビジネスチャンネルで操作するときに過剰な数のチャンネルオブジェクトを作成すると、ビジネスの応答時間に影響を与え、TCP 接続が多すぎるためにビジネスコードのブロックが発生することさえあります。
トランザクションがチャンネルに頻繁に送信される場合は、最初のチャンネルオブジェクトを再利用してリソース消費を削減するのが最善です。 channel.shutdown(true) を使用して、アイドル状態のチャンネルオブジェクトを解放できます。
HFCAClient を介してローカルユーザーを生成するには、ユーザーの秘密鍵の生成と Enroll 操作が含まれます。これにも一定の時間がかかります。 Enrollment オブジェクトは再利用できます。
サンプルコード:
public class HttpHandler {
private HFClient client = null;
private Channel channel = null;
HttpHandler(user FabricUser) {
client = HFClient.createNewInstance();
client.setCryptoSuite(CryptoSuite.Factory.getCryptoSuite());
client.setUserContext(user);
NetworkConfig networkConfig = NetworkConfig.fromYamlFile("connection-profile.yaml");
channel = client.loadChannelFromConfig("mychannel", networkConfig);
channel.initialize();
}
}2. 必要なピアにトランザクションを送信してエンドースメントを行う
Alibaba Cloud BaaS (Fabric) では、各組織に 2 つのエンドースメントピアがあります。 ビジネスチャンネルに N 個の組織がある場合、SDK を使用して提案を送信すると、デフォルトですべての (2 * N) エンドースメントピアに送信されます。 各ピアは提案を受信して処理する必要があります。 これには多くの時間がかかり、全体的なスループットに影響します。 一部のピアの処理速度が遅い場合、トランザクションの応答は遅くなります。
ユーザーがアプリケーション側で各ピアから返された読み取り/書き込みセットを検証する必要がなく、チェーンコードのエンドースメントポリシーに従って必要なピアに提案を選択的に送信できる場合、計算リソースを大幅に節約し、パフォーマンスを向上させることができます。例:
チェーンコードのエンドースメントポリシーが
OR ('org1MSP.peer' , 'org2MSP.peer' , 'org3MSP.peer')の場合、アプリケーションは 6 つのピアから 1 つのピアを選択し、提案を送信して、エンドースメント応答を待つことができます。チェーンコードのエンドースメントポリシーが
OutOf(2 , 'org1MSP.peer' , 'org2MSP.peer' , 'org3MSP.peer')の場合、アプリケーションは 6 つのピアから 2 つのピアを選択し、提案を送信して、エンドースメント応答を待つことができます。
サンプルコード:
//エンドースメントポリシーは OR ('org1MSP.peer' , 'org2MSP.peer' , 'org3MSP.peer') です。
//1 つのピアに提案を送信してエンドースメントを行います
Collection<Peer> peers = channel.getPeers(EnumSet.of(Peer.PeerRole.ENDORSING_PEER));
int size = peers.size();
int index = random.nextInt(size);
Peer[] endorsingPeers = new Peer[size];
peers.toArray(endorsingPeers);
Set partialPeers = new HashSet();
partialPeers.add(endorsingPeers[index]);
try {
transactionPropResp = channel.sendTransactionProposal(transactionProposalRequest, partialPeers);
} catch (ProposalException e) {
System.out.printf("invokeTransactionSync fail,ProposalException:{}", e.getLocalizedMessage());
e.printStackTrace();
} catch (InvalidArgumentException e) {
System.out.printf("InvalidArgumentException fail:{}", e.getLocalizedMessage());
e.printStackTrace();
}Fabric によって提供される検出機能を使用して、必要なピアに提案を送信できます。
検出機能を使用するためのサンプルコード:
Channel.DiscoveryOptions discoveryOptions = Channel.DiscoveryOptions.createDiscoveryOptions();
discoveryOptions.setEndorsementSelector(ServiceDiscovery.EndorsementSelector.ENDORSEMENT_SELECTION_RANDOM); //エンドースメントポリシーを満たすピアをランダムに選択します。
discoveryOptions.setForceDiscovery(false); //検出機能のキャッシュを使用します。 キャッシュはデフォルトで 2 分ごとにリフレッシュされます。
discoveryOptions.setInspectResults(true); //SDK のエンドースメントポリシーチェックを無効にし、ロジックチェックを使用します。
Collection<ProposalResponse> transactionPropResp = channel.sendTransactionProposalToEndorsers(transactionProposalRequest, discoveryOptions);3. 必要なノードイベントを非同期的に待機する
アプリケーションがピアから返された提案の読み取り/書き込みセットを注文者ノードに送信した後、Fabric はトランザクションの順序付け、ブロック化、検証、コミットなど、一連の操作を実行します。 トランザクションのコミットは遅延する可能性があり、これはチャンネルのブロック生成の構成によって異なります。
デフォルトでは、Java SDK は eventSource が true のすべてのノードイベントを待機し、すべてのノード検証に合格すると成功を返します。 このプロセスは、特定のビジネスシナリオで最適化できます。 ほとんどのシナリオでは、組織内の 1 つのピアを選択してイベントを受信すれば、ビジネス要件を満たすことができます。
Java SDK の
channel.sendTransactionメソッドは、CompletableFuture<TransactionEvent>を返します。 アプリケーションは、複数のスレッドで同時操作を実行できます。 スレッドがトランザクション (sendTransaction) を注文者ノードに送信した後、アプリケーションは他のトランザクションの処理を続行できます。 別のスレッドは、TransactionEventをリッスンした後に、関連する操作を実行できます。Java SDK は、
NOfEventsクラスも提供して、イベントを受信するために使用されるポリシーを制御し、注文者ノードに送信されたトランザクションが成功したかどうかを確認します。NOfEventsの値を 1 に設定することをお勧めします。これは、ランダムなノードがイベントを返すことを許可することを意味します。 アプリケーションは、各ピアがTransactionEventを送信してトランザクションを成功と見なすのを待つ必要はありません。アプリケーションがトランザクションイベントを処理する必要がない場合は、Channel.NOfEvents.createNoEvents () を使用して
nofNoEventsオブジェクトを作成できます。 この場合、注文者ノードはトランザクションを受信するとすぐにCompletableFuture<TransactionEvent>を返しますが、TransactionEventは null に設定されます。
任意のノードが検証に合格したイベントを受信したときに成功を返すように NOfEvents を構成できます。
Channel.TransactionOptions opts = new Channel.TransactionOptions();
Channel.NOfEvents nOfEvents = Channel.NOfEvents.createNofEvents();
Collection<EventHub> eventHubs = channel.getEventHubs();
if (!eventHubs.isEmpty()) {
nOfEvents.addEventHubs(eventHubs);
}
nOfEvents.addPeers(channel.getPeers(EnumSet.of(Peer.PeerRole.EVENT_SOURCE)));
nOfEvents.setN(1);
opts.nOfEvents(nOfEvents);
channel.sendTransaction(successful, opts).thenApply(transactionEvent -> {
logger.info("Orderer response: txid" + transactionEvent.getTransactionID());
logger.info("Orderer response: block number: " + transactionEvent.getBlockEvent().getBlockNumber());
return null;
}).exceptionally(e -> {
logger.error("Orderer exception happened: ", e);
return null;
}).get(60, TimeUnit.SECONDS);NOfEvents 以外にも、connection-profile.yaml で特定のピアの eventSource を指定することもできます。 次の例では、アプリケーションは peer1 ノードからの eventSource イベントのみを受信します。
channels:
mychannel:
peers:
peer1.org1.aliyunbaas.top:31111:
chaincodeQuery: true
endorsingPeer: true
eventSource: true
ledgerQuery: true
discover: true
peer2.org2.aliyunbaas.top:31111:
chaincodeQuery: true
endorsingPeer: true
ledgerQuery: true
discover: true
orderers:
- orderer1
- orderer2
- orderer3Go SDK
このトピックは、バージョン Go SDK v1.0.0-alpha5 に基づいています。 Go SDK はユーザーフレンドリーです。 デフォルトでキャッシュと負荷分散ロジックを実装しており、ユーザーは必要なエンドースメントノードで署名を自動的に収集できます。 トランザクションの合法性を判断するために、Go SDK はポリシーに基づいてピアノードをランダムに選択してイベントをリッスンします。
1. SDK インスタンスをグローバルに再利用する
Go SDK では、すべてのキャッシュは fabsdk.FabricSDK オブジェクトに基づいており、異なるキャッシュとリンクは異なるオブジェクトで個別に維持されます。 多すぎる fabsdk.FabricSDK オブジェクトを作成することは避ける必要があります。 Java SDK と同様に、オブジェクトは初期化プロセス中にピアに複数のリクエストを送信し、リソースと時間を消費します。