高可用篇

设计高可用的区块链应用程序

使用 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服务提供节点。

  1. channels:
  2. mychannel:
  3. peers:
  4. peer1.org1.aliyunbaas.top:31111:
  5. chaincodeQuery: true
  6. endorsingPeer: true
  7. eventSource: true
  8. ledgerQuery: true
  9. discover: true
  10. peer2.org1.aliyunbaas.top:31121:
  11. chaincodeQuery: true
  12. endorsingPeer: true
  13. eventSource: true
  14. ledgerQuery: true
  15. discover: true
  16. peer1.org2.aliyunbaas.top:31111:
  17. chaincodeQuery: true
  18. endorsingPeer: true
  19. eventSource: true
  20. ledgerQuery: true
  21. discover: true
  22. peer2.org2.aliyunbaas.top:31121:
  23. chaincodeQuery: true
  24. endorsingPeer: true
  25. eventSource: true
  26. ledgerQuery: true
  27. 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 自动进行背书策略检查,不满足抛出异常

示例代码如下:

  1. import org.hyperledger.fabric.sdk.exception.ServiceDiscoveryException;
  2. import org.hyperledger.fabric.sdk.ServiceDiscovery;
  3. import static org.hyperledger.fabric.sdk.Channel.DiscoveryOptions.createDiscoveryOptions;
  4. .....
  5. .....
  6. Channel.DiscoveryOptions discoveryOptions = Channel.DiscoveryOptions.createDiscoveryOptions();
  7. discoveryOptions.setEndorsementSelector(ServiceDiscovery.EndorsementSelector.ENDORSEMENT_SELECTION_RANDOM);
  8. discoveryOptions.setForceDiscovery(false);
  9. discoveryOptions.setInspectResults(true);
  10. try {
  11. transactionPropResp = channel.sendTransactionProposalToEndorsers
  12. (transactionProposalRequest, discoveryOptions);
  13. } catch (ProposalException e) {
  14. System.out.printf("invokeTransactionSync fail,ProposalException:{}", e.getLocalizedMessage());
  15. e.printStackTrace();
  16. } catch (ServiceDiscoveryException e) {
  17. System.out.printf("ServiceDiscoveryException fail:{}", e.getLocalizedMessage());
  18. e.printStackTrace();
  19. } catch (InvalidArgumentException e) {
  20. System.out.printf("InvalidArgumentException fail:{}", e.getLocalizedMessage());
  21. e.printStackTrace();
  22. }
  23. .....
  24. .....

Tips:建议应用将 Service DiscoveryNOfEvents 配合使用,不需要等待每一个peer都发出transactionEvent 才算交易成功,避免因个别 peer 下线引发交易判定失败。

设置合适的超时时间

Java SDK中定义了各种超时的设置变量和默认值,源码地址src\main\java\org\hyperledger\fabric\sdk\helper\Config.java

  1. /**
  2. * Timeout settings
  3. **/
  4. public static final String PROPOSAL_WAIT_TIME = "org.hyperledger.fabric.sdk.proposal.wait.time";
  5. public static final String CHANNEL_CONFIG_WAIT_TIME = "org.hyperledger.fabric.sdk.channelconfig.wait_time";
  6. public static final String TRANSACTION_CLEANUP_UP_TIMEOUT_WAIT_TIME = "org.hyperledger.fabric.sdk.client.transaction_cleanup_up_timeout_wait_time";
  7. public static final String ORDERER_RETRY_WAIT_TIME = "org.hyperledger.fabric.sdk.orderer_retry.wait_time";
  8. public static final String ORDERER_WAIT_TIME = "org.hyperledger.fabric.sdk.orderer.ordererWaitTimeMilliSecs";
  9. public static final String PEER_EVENT_REGISTRATION_WAIT_TIME = "org.hyperledger.fabric.sdk.peer.eventRegistration.wait_time";
  10. public static final String PEER_EVENT_RETRY_WAIT_TIME = "org.hyperledger.fabric.sdk.peer.retry_wait_time";
  11. public static final String PEER_EVENT_RECONNECTION_WARNING_RATE = "org.hyperledger.fabric.sdk.peer.reconnection_warning_rate";
  12. public static final String GENESISBLOCK_WAIT_TIME = "org.hyperledger.fabric.sdk.channel.genesisblock_wait_time";
  13. // Default values
  14. /**
  15. defaultProperty(PROPOSAL_WAIT_TIME, "20000");
  16. defaultProperty(CHANNEL_CONFIG_WAIT_TIME, "15000");
  17. defaultProperty(TRANSACTION_CLEANUP_UP_TIMEOUT_WAIT_TIME, "600000");
  18. defaultProperty(ORDERER_RETRY_WAIT_TIME, "200");
  19. defaultProperty(ORDERER_WAIT_TIME, "10000");
  20. defaultProperty(PEER_EVENT_REGISTRATION_WAIT_TIME, "5000
  21. defaultProperty(PEER_EVENT_RETRY_WAIT_TIME, "500");
  22. defaultProperty(PEER_EVENT_RECONNECTION_WARNING_RATE, "50");
  23. defaultProperty(GENESISBLOCK_WAIT_TIME, "5000");
  24. **/

用户可根据应用特点进行设置。

例如:

  • 应用发送交易到 peer 或者 orderer 时,节点由于故障或升级维护并不在线,SDK 需要等待一定时间直到 timeout 后再尝试其他的 peer 或者 orderer 节点。如果希望应用能够快速切换到健康的节点,在网络连接良好,交易处理速度较快的前提下,用户可适当减小PROPOSAL_WAIT_TIMEORDERER_WAIT_TIME的值。

用户可以设置system属性来覆盖默认值

  1. public static final String PROPOSAL_WAIT_TIME = "org.hyperledger.fabric.sdk.proposal.wait.time";
  2. private static final String PROPOSAL_WAIT_TIME_VALUE = "5000";
  3. public static final String ORDERER_WAIT_TIME = "org.hyperledger.fabric.sdk.orderer.ordererWaitTimeMilliSecs";
  4. private static final String ORDERER_WAIT_TIME_VALUE = "5000";
  5. static {
  6. System.setProperty(PROPOSAL_WAIT_TIME, PROPOSAL_WAIT_TIME_VALUE);
  7. System.setProperty(ORDERER_WAIT_TIME, ORDERER_WAIT_TIME_VALUE);
  8. }

也可以在Java项目的config.properties中设置:

  1. ## The timeout for a proposal requests to endorser in milliseconds.
  2. org.hyperledger.fabric.sdk.proposal.wait.time = 5000
  3. ## The timeout for a transaction sent to orderer in milliseconds.
  4. org.hyperledger.fabric.sdk.orderer.ordererWaitTimeMilliSecs = 5000

配置GRPC消息大小限制

GRPC默认消息大小限制为4M,如果应用端与Fabric网络传输的消息超过4M则会报错.

  1. rpc error: code = ResourceExhausted desc = grpc: received message larger than max (8653851 vs. 4194304)”。

在 peer,orderer 的属性中增加grpc.NettyChannelBuilderOption.maxInboundMessageSize (支持在 connection-profile 中配置 FABJ-480 )

  1. NetworkConfig networkConfig = NetworkConfig.fromYamlFile(f);
  2. // 从 connection-profile 中读取出配置后,插入下面的代码
  3. for (String peerName : networkConfig.getPeerNames()) {
  4. Properties peerProperties = networkConfig.getPeerProperties(peerName);
  5. if (peerProperties == null) {
  6. peerProperties = new Properties();
  7. }
  8. peerProperties.put("grpc.NettyChannelBuilderOption.maxInboundMessageSize", 100*1024*1024);
  9. networkConfig.setPeerProperties(peerName, peerProperties);
  10. }
  11. for (String ordererName : networkConfig.getOrdererNames()) {
  12. Properties ordererProperties = networkConfig.getOrdererProperties(ordererName);
  13. if (ordererProperties == null) {
  14. ordererProperties = new Properties();
  15. }
  16. ordererProperties.put("grpc.NettyChannelBuilderOption.maxInboundMessageSize", 100*1024*1024);
  17. networkConfig.setPeerProperties(ordererName, ordererProperties);
  18. }