使用 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();
}
.....
.....
Tips:建议应用将
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);
}