設計高可用的區塊鏈應用程序
使用 Service Discovery
應用在向 peer 節點發送交易 proposal 時,或是連接通道內的所有 peer 節點,或是根據背書策略選擇部分節點。默認情況下,SDK讀取靜態的 connection-profile
配置文件獲取區塊鏈節點信息,如果在應用運行期間網絡發生變更(例如peer節點下線),或者背書策略的變化(例如一個新組織加入channel),應用程序無法感知,會造成連接失敗或是背書驗證失敗。
為了解決這個問題,SDK 可向 Service Discovery 服務發送查詢,動態獲取指定通道和鏈碼需要連接的 peer 節點列表,詳情見 Fabric的服務發現機制。
通過這種方式,SDK 可抓取與當前背書策略對應的 peer 組合,選擇必要的 peer 節點發送 proposal。當某 peer 節點由于停機維護或故障下線時,SDK 也可自動嘗試其他 peer 組合。由于阿里云 BaaS(Fabric)中,每個組織都會有2個 peer 背書節點,與 Service Discovery 相結合,可以保證1個組織中即使有1個peer下線也能正常處理業務交易。
在BaaS的SDK中使用該功能,首先在connection-profile
配置文件目標通道的 peer 節點列表中,增加屬性discover: true
。 SDK將隨機選取一個peer作為Service Discovery
服務提供節點。
channels:
mychannel:
peers:
peer1.org1.aliyunbaas.top:31111:
chaincodeQuery: true
endorsingPeer: true
eventSource: true
ledgerQuery: true
discover: true
peer2.org1.aliyunbaas.top:31121:
chaincodeQuery: true
endorsingPeer: true
eventSource: true
ledgerQuery: true
discover: true
peer1.org2.aliyunbaas.top:31111:
chaincodeQuery: true
endorsingPeer: true
eventSource: true
ledgerQuery: true
discover: true
peer2.org2.aliyunbaas.top:31121:
chaincodeQuery: true
endorsingPeer: true
eventSource: true
ledgerQuery: true
discover: true
在應用程序中,調用sendTransactionProposalToEndorsers
時指定DiscoveryOptions
setEndorsementSelector:
ENDORSEMENT_SELECTION_RANDOM: 隨機選取滿足背書策略的peer節點組合
ENDORSEMENT_SELECTION_LEAST_REQUIRED_BLOCKHEIGHT: 選取滿足背書策略的,狀態最新、塊高最大的peer節點組合
setForceDiscovery:
true: 每一次發送proposal時都調用discovery服務獲取peer列表,會有一定的資源消耗
false: 發送proposal時使用discovery服務緩存的peer列表,默認2分鐘刷新一次
setInspectResults:
true: 關閉SDK 背書策略檢查,由應用邏輯進行判斷
false: SDK 自動進行背書策略檢查,不滿足拋出異常
示例代碼如下。
import org.hyperledger.fabric.sdk.exception.ServiceDiscoveryException;
import org.hyperledger.fabric.sdk.ServiceDiscovery;
import static org.hyperledger.fabric.sdk.Channel.DiscoveryOptions.createDiscoveryOptions;
.....
.....
Channel.DiscoveryOptions discoveryOptions = Channel.DiscoveryOptions.createDiscoveryOptions();
discoveryOptions.setEndorsementSelector(ServiceDiscovery.EndorsementSelector.ENDORSEMENT_SELECTION_RANDOM);
discoveryOptions.setForceDiscovery(false);
discoveryOptions.setInspectResults(true);
try {
transactionPropResp = channel.sendTransactionProposalToEndorsers
(transactionProposalRequest, discoveryOptions);
} catch (ProposalException e) {
System.out.printf("invokeTransactionSync fail,ProposalException:{}", e.getLocalizedMessage());
e.printStackTrace();
} catch (ServiceDiscoveryException e) {
System.out.printf("ServiceDiscoveryException fail:{}", e.getLocalizedMessage());
e.printStackTrace();
} catch (InvalidArgumentException e) {
System.out.printf("InvalidArgumentException fail:{}", e.getLocalizedMessage());
e.printStackTrace();
}
.....
.....
建議應用將 Service Discovery 和 NOfEvents 配合使用,不需要等待每一個peer都發出transactionEvent 才算交易成功,避免因個別 peer 下線引發交易判定失敗。
設置合適的超時時間
Java SDK中定義了各種超時的設置變量和默認值,源碼地址:src\main\java\org\hyperledger\fabric\sdk\helper\Config.java
/**
* Timeout settings
**/
public static final String PROPOSAL_WAIT_TIME = "org.hyperledger.fabric.sdk.proposal.wait.time";
public static final String CHANNEL_CONFIG_WAIT_TIME = "org.hyperledger.fabric.sdk.channelconfig.wait_time";
public static final String TRANSACTION_CLEANUP_UP_TIMEOUT_WAIT_TIME = "org.hyperledger.fabric.sdk.client.transaction_cleanup_up_timeout_wait_time";
public static final String ORDERER_RETRY_WAIT_TIME = "org.hyperledger.fabric.sdk.orderer_retry.wait_time";
public static final String ORDERER_WAIT_TIME = "org.hyperledger.fabric.sdk.orderer.ordererWaitTimeMilliSecs";
public static final String PEER_EVENT_REGISTRATION_WAIT_TIME = "org.hyperledger.fabric.sdk.peer.eventRegistration.wait_time";
public static final String PEER_EVENT_RETRY_WAIT_TIME = "org.hyperledger.fabric.sdk.peer.retry_wait_time";
public static final String PEER_EVENT_RECONNECTION_WARNING_RATE = "org.hyperledger.fabric.sdk.peer.reconnection_warning_rate";
public static final String GENESISBLOCK_WAIT_TIME = "org.hyperledger.fabric.sdk.channel.genesisblock_wait_time";
// Default values
/**
defaultProperty(PROPOSAL_WAIT_TIME, "20000");
defaultProperty(CHANNEL_CONFIG_WAIT_TIME, "15000");
defaultProperty(TRANSACTION_CLEANUP_UP_TIMEOUT_WAIT_TIME, "600000");
defaultProperty(ORDERER_RETRY_WAIT_TIME, "200");
defaultProperty(ORDERER_WAIT_TIME, "10000");
defaultProperty(PEER_EVENT_REGISTRATION_WAIT_TIME, "5000
defaultProperty(PEER_EVENT_RETRY_WAIT_TIME, "500");
defaultProperty(PEER_EVENT_RECONNECTION_WARNING_RATE, "50");
defaultProperty(GENESISBLOCK_WAIT_TIME, "5000");
**/
用戶可根據應用特點進行設置。
例如,應用發送交易到 peer 或者 orderer 時,節點由于故障或升級維護并不在線,SDK 需要等待一定時間直到 timeout 后再嘗試其他的 peer 或者 orderer 節點。如果希望應用能夠快速切換到健康的節點,在網絡連接良好,交易處理速度較快的前提下,用戶可適當減小PROPOSAL_WAIT_TIME
和ORDERER_WAIT_TIME
的值。
用戶可以設置system屬性來覆蓋默認值。
public static final String PROPOSAL_WAIT_TIME = "org.hyperledger.fabric.sdk.proposal.wait.time"; private static final String PROPOSAL_WAIT_TIME_VALUE = "5000"; public static final String ORDERER_WAIT_TIME = "org.hyperledger.fabric.sdk.orderer.ordererWaitTimeMilliSecs"; private static final String ORDERER_WAIT_TIME_VALUE = "5000"; static { System.setProperty(PROPOSAL_WAIT_TIME, PROPOSAL_WAIT_TIME_VALUE); System.setProperty(ORDERER_WAIT_TIME, ORDERER_WAIT_TIME_VALUE); }
也可以在Java項目的
config.properties
中設置。## The timeout for a proposal requests to endorser in milliseconds. org.hyperledger.fabric.sdk.proposal.wait.time = 5000 ## The timeout for a transaction sent to orderer in milliseconds. org.hyperledger.fabric.sdk.orderer.ordererWaitTimeMilliSecs = 5000
配置GRPC消息大小限制
GRPC默認消息大小限制為4M,如果應用端與Fabric網絡傳輸的消息超過4M則會報錯.
rpc error: code = ResourceExhausted desc = grpc: received message larger than max (8653851 vs. 4194304)”。
在 peer,orderer 的屬性中增加grpc.NettyChannelBuilderOption.maxInboundMessageSize
(支持在 connection-profile
中配置 FABJ-480 )
NetworkConfig networkConfig = NetworkConfig.fromYamlFile(f);
// 從 connection-profile 中讀取出配置后,插入下面的代碼
for (String peerName : networkConfig.getPeerNames()) {
Properties peerProperties = networkConfig.getPeerProperties(peerName);
if (peerProperties == null) {
peerProperties = new Properties();
}
peerProperties.put("grpc.NettyChannelBuilderOption.maxInboundMessageSize", 100*1024*1024);
networkConfig.setPeerProperties(peerName, peerProperties);
}
for (String ordererName : networkConfig.getOrdererNames()) {
Properties ordererProperties = networkConfig.getOrdererProperties(ordererName);
if (ordererProperties == null) {
ordererProperties = new Properties();
}
ordererProperties.put("grpc.NettyChannelBuilderOption.maxInboundMessageSize", 100*1024*1024);
networkConfig.setPeerProperties(ordererName, ordererProperties);
}