hyperledger fabric java sdk官方给的调用链码的方式是通过CompletableFuture.get()方法异步转同步来获取交易信息。但这样就需要等待每个块打包完成后才能返回需要的transactionId和blockNumber等数据。公司联盟链的出块时间设置的是2s出一个块,这样远达不到我们高密集型的业务需求,并且这样做每个区块上只打包了一个交易,也太浪费了(本身fabric设置的是batch out time到了或者交易数量达到一定数量了就会产生区块)。
使用CompletableFuture.thenAcceptAsync方法取代get方法并修改业务逻辑后, sdk接口调用速度大大提高,但使用消息队列并发调用时每隔几条会出现下面这个报错信息导致上链失败
java.util.concurrent.ExecutionException: org.hyperledger.fabric.sdk.exception.TransactionEventException: Received invalid transaction event. Transaction ID 872a9f157ddf0516cb2c2ffe7a39a9b9bc3c4b8685e1912d1aec05bca9fc64b1 status 10
上peer节点所在的服务器查询docker日志后发现链上报了MVCC_READ_CONFLICT的错,在查阅资料后发现是链码的并发问题,官方对于这个问题的解释是这样的: the read-set version will no longer match the version in the orderer, and a large number of parallel transactions will fail.
因为没有一个全局锁来防止同时更新一个key(APIstub.PutState(key, value)),如果同一个区块内有两个交易保存的key相同,read-set的版本没有按顺序排列,就会产生回滚,也就报了这个错。官方原文在此
为了解决这个问题,频繁更新的值被存储为一系列增量,这些增量在必须检索值时进行聚合。这样,就不会频繁地读取和更新任何单个的row,而是考虑把row作为一个存储增量的集合,即使用一个compositeKey取代原本单一的key(本来只是一个键值对,现在是一系列特殊的键值对,通过特殊的格式,将这个key从创建到所有的更新都保存下来,查询的时候也可以查询到它所有更新的历史)。
官方的示例代码如下(源码地址):
/*
* Demonstrates how to handle data in an application with a high transaction volume where the transactions
* all attempt to change the same key-value pair in the ledger. Such an application will have trouble
* as multiple transactions may read a value at a certain version, which will then be invalid when the first
* transaction updates the value to a new version, thus rejecting all other transactions until they're
* re-executed.
* Rather than relying on serialization of the transactions, which is slow, this application initializes
* a value and then accepts deltas of that value which are added as rows to the ledger. The actual value
* is then an aggregate of the initial value combined with all of the deltas. Additionally, a pruning
* function is provided which aggregates and deletes the deltas to update the initial value. This should
* be done during a maintenance window or when there is a lowered transaction volume, to avoid the proliferation
* of millions of rows of data.
*
* @author Alexandre Pauwels for IBM
* @created 17 Aug 2017
*/
package main
/* Imports
* 4 utility libraries for formatting, handling bytes, reading and writing JSON, and string manipulation
* 2 specific Hyperledger Fabric specific libraries for Smart Contracts
*/
import (
"fmt"
"strconv"
"github.com/hyperledger/fabric/core/chaincode/shim"
sc "github.com/hyperledger/fabric/protos/peer"
)
//SmartContract is the data structure which represents this contract and on which various contract lifecycle functions are attached
type SmartContract struct {
}
// Define Status codes for the response
const (
OK = 200
ERROR = 500
)
// Init is called when the smart contract is instantiated
func (s *SmartContract) Init(APIstub shim.ChaincodeStubInterface) sc.Response {
return shim.Success(nil)
}
// Invoke routes invocations to the appropriate function in chaincode
// Current supported invocations are:
// - update, adds a delta to an aggregate variable in the ledger, all variables are assumed to start at 0
// - get, retrieves the aggregate value of a variable in the ledger
// - pruneFast, deletes all rows associated with the variable and replaces them with a single row containing the aggregate value
// - pruneSafe, same as pruneFast except it pre-computed the value and backs it up before performing any destructive operations
// - delete, removes all rows associated with the variable
func (s *SmartContract) Invoke(APIstub shim.ChaincodeStubInterface) sc.Response {
// Retrieve the requested Smart Contract function and arguments
function, args := APIstub.GetFunctionAndParameters()
// Route to the appropriate handler function to interact with the ledger appropriately
if function == "update" {
return s.update(APIstub, args)
} else if function == "get" {
return s.get(APIstub, args)
} else if function == "prunefast" {
return s.pruneFast(APIstub, args)
} else if function == "prunesafe" {
return s.pruneSafe(APIstub, args)
} else if function == "delete" {
return s.delete(APIstub, args)
} else if function == "putstandard" {
return s.putStandard(APIstub, args)
} else if function == "getstandard" {
return s.getStandard(APIstub, args)
}
return shim.Error("Invalid Smart Contract function name.")
}
/**
* Updates the ledger to include a new delta for a particular variable. If this is the first time
* this variable is being added to the ledger, then its initial value is assumed to be 0. The arguments
* to give in the args array are as follows:
* - args[0] -> name of the variable
* - args[1] -> new delta (float)
* - args[2] -> operation (currently supported are addition "+" and subtraction "-")
*
* @param APIstub The chaincode shim
* @param args The arguments array for the update invocation
*
* @return A response structure indicating success or failure with a message
*/
func (s *SmartContract) update(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {
// Check we have a valid number of args
if len(args) != 3 {
return shim.Error("Incorrect number of arguments, expecting 3")
}
// Extract the args
name := args[0]
op := args[2]
_, err := strconv.ParseFloat(args[1], 64)
if err != nil {
return shim.Error("Provided value was not a number")
}
// Make sure a valid operator is provided
if op != "+" && op != "-" {
return shim.Error(fmt.Sprintf("Operator %s is unrecognized", op))
}
// Retrieve info needed for the update procedure
txid := APIstub