设计高可用的区块链应用程序
使用 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);
}