mirror of
				https://github.com/nosqlbench/nosqlbench.git
				synced 2025-02-25 18:55:28 -06:00 
			
		
		
		
	Fix Kafka producer transaction issue and add support for subscribing from multiple topics.
NB yaml files and README update
This commit is contained in:
		@@ -30,6 +30,7 @@ import org.apache.logging.log4j.LogManager;
 | 
				
			|||||||
import org.apache.logging.log4j.Logger;
 | 
					import org.apache.logging.log4j.Logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.concurrent.ConcurrentHashMap;
 | 
					import java.util.concurrent.ConcurrentHashMap;
 | 
				
			||||||
 | 
					import java.util.concurrent.atomic.AtomicBoolean;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class KafkaSpace implements  AutoCloseable {
 | 
					public class KafkaSpace implements  AutoCloseable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -61,23 +62,25 @@ public class KafkaSpace implements  AutoCloseable {
 | 
				
			|||||||
    // - For Producer workload, this represents how many total producers to publish messages
 | 
					    // - For Producer workload, this represents how many total producers to publish messages
 | 
				
			||||||
    //   it must be the same value as the NB "threads" parameter
 | 
					    //   it must be the same value as the NB "threads" parameter
 | 
				
			||||||
    // - For Consumer workload, this represents how many total consumers per consumer group to subscribe messages
 | 
					    // - For Consumer workload, this represents how many total consumers per consumer group to subscribe messages
 | 
				
			||||||
    //
 | 
					    private final int kafkaClntNum;
 | 
				
			||||||
    private final int clntNum;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Maximum number of Kafka consumer groups
 | 
					    // Maximum number of Kafka consumer groups
 | 
				
			||||||
    // - This is only relevant for Consumer workload
 | 
					    // - Only relevant for Consumer workload
 | 
				
			||||||
    // - (clntNum * consumerGrpNum) is the total consumer thread number and must be the same
 | 
					    // - (topicPartNum * consumerGrpNum) is the total consumer thread number and must be the same
 | 
				
			||||||
    //   as the NB "threads" parameter
 | 
					    //   as the NB "threads" parameter
 | 
				
			||||||
 | 
					    // - For multi-topic testing, this means one consumer thread may read from multiple topics.
 | 
				
			||||||
    private final int consumerGrpNum;
 | 
					    private final int consumerGrpNum;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private long totalCycleNum;
 | 
					    private long totalCycleNum;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private AtomicBoolean beingShutdown = new AtomicBoolean(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public KafkaSpace(String spaceName, NBConfiguration cfg) {
 | 
					    public KafkaSpace(String spaceName, NBConfiguration cfg) {
 | 
				
			||||||
        this.spaceName = spaceName;
 | 
					        this.spaceName = spaceName;
 | 
				
			||||||
        this.cfg = cfg;
 | 
					        this.cfg = cfg;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.bootstrapSvr = cfg.get("bootstrap_server");
 | 
					        this.bootstrapSvr = cfg.get("bootstrap_server");
 | 
				
			||||||
        this.clntNum =
 | 
					        this.kafkaClntNum =
 | 
				
			||||||
            NumberUtils.toInt(cfg.getOptional("num_clnt").orElse("1"));
 | 
					            NumberUtils.toInt(cfg.getOptional("num_clnt").orElse("1"));
 | 
				
			||||||
        this.consumerGrpNum =
 | 
					        this.consumerGrpNum =
 | 
				
			||||||
            NumberUtils.toInt(cfg.getOptional("num_cons_grp").orElse("1"));
 | 
					            NumberUtils.toInt(cfg.getOptional("num_cons_grp").orElse("1"));
 | 
				
			||||||
@@ -124,7 +127,7 @@ public class KafkaSpace implements  AutoCloseable {
 | 
				
			|||||||
    public String getBootstrapSvr() { return this.bootstrapSvr; }
 | 
					    public String getBootstrapSvr() { return this.bootstrapSvr; }
 | 
				
			||||||
    public KafkaClientConf getKafkaClientConf() { return kafkaClientConf; }
 | 
					    public KafkaClientConf getKafkaClientConf() { return kafkaClientConf; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public int getClntNum() { return this.clntNum; }
 | 
					    public int getKafkaClntNum() { return this.kafkaClntNum; }
 | 
				
			||||||
    public int getConsumerGrpNum() { return this.consumerGrpNum; }
 | 
					    public int getConsumerGrpNum() { return this.consumerGrpNum; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public boolean isStrictMsgErrorHandling() { return  this.strictMsgErrorHandling; }
 | 
					    public boolean isStrictMsgErrorHandling() { return  this.strictMsgErrorHandling; }
 | 
				
			||||||
@@ -132,14 +135,19 @@ public class KafkaSpace implements  AutoCloseable {
 | 
				
			|||||||
    public long getTotalCycleNum() { return totalCycleNum; }
 | 
					    public long getTotalCycleNum() { return totalCycleNum; }
 | 
				
			||||||
    public void setTotalCycleNum(long cycleNum) { totalCycleNum = cycleNum; }
 | 
					    public void setTotalCycleNum(long cycleNum) { totalCycleNum = cycleNum; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public boolean isShuttigDown() {
 | 
				
			||||||
 | 
					        return beingShutdown.get();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    public void shutdownSpace() {
 | 
					    public void shutdownSpace() {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
            // Pause 5 seconds before closing producers/consumers
 | 
					            beingShutdown.set(true);
 | 
				
			||||||
            KafkaAdapterUtil.pauseCurThreadExec(5);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            for (OpTimeTrackKafkaClient client : opTimeTrackKafkaClients.values()) {
 | 
					            for (OpTimeTrackKafkaClient client : opTimeTrackKafkaClients.values()) {
 | 
				
			||||||
                client.close();
 | 
					                client.close();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Pause 5 seconds before closing producers/consumers
 | 
				
			||||||
 | 
					            KafkaAdapterUtil.pauseCurThreadExec(5);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        catch (Exception e) {
 | 
					        catch (Exception e) {
 | 
				
			||||||
            e.printStackTrace();
 | 
					            e.printStackTrace();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,7 +34,6 @@ import org.apache.logging.log4j.Logger;
 | 
				
			|||||||
import java.util.*;
 | 
					import java.util.*;
 | 
				
			||||||
import java.util.function.LongFunction;
 | 
					import java.util.function.LongFunction;
 | 
				
			||||||
import java.util.function.Predicate;
 | 
					import java.util.function.Predicate;
 | 
				
			||||||
import java.util.stream.Collectors;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
public abstract  class KafkaBaseOpDispenser extends BaseOpDispenser<KafkaOp, KafkaSpace> {
 | 
					public abstract  class KafkaBaseOpDispenser extends BaseOpDispenser<KafkaOp, KafkaSpace> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -82,7 +81,7 @@ public abstract  class KafkaBaseOpDispenser extends BaseOpDispenser<KafkaOp, Kaf
 | 
				
			|||||||
        this.totalCycleNum = NumberUtils.toLong(parsedOp.getStaticConfig("cycles", String.class));
 | 
					        this.totalCycleNum = NumberUtils.toLong(parsedOp.getStaticConfig("cycles", String.class));
 | 
				
			||||||
        kafkaSpace.setTotalCycleNum(totalCycleNum);
 | 
					        kafkaSpace.setTotalCycleNum(totalCycleNum);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.kafkaClntCnt = kafkaSpace.getClntNum();
 | 
					        this.kafkaClntCnt = kafkaSpace.getKafkaClntNum();
 | 
				
			||||||
        this.consumerGrpCnt = kafkaSpace.getConsumerGrpNum();
 | 
					        this.consumerGrpCnt = kafkaSpace.getConsumerGrpNum();
 | 
				
			||||||
        this.totalThreadNum = NumberUtils.toInt(parsedOp.getStaticConfig("threads", String.class));
 | 
					        this.totalThreadNum = NumberUtils.toInt(parsedOp.getStaticConfig("threads", String.class));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -91,11 +90,11 @@ public abstract  class KafkaBaseOpDispenser extends BaseOpDispenser<KafkaOp, Kaf
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        boolean validThreadNum =
 | 
					        boolean validThreadNum =
 | 
				
			||||||
            ( ((this instanceof MessageProducerOpDispenser) && (totalThreadNum == kafkaClntCnt)) ||
 | 
					            ( ((this instanceof MessageProducerOpDispenser) && (totalThreadNum == kafkaClntCnt)) ||
 | 
				
			||||||
              ((this instanceof MessageConsumerOpDispenser) && (totalThreadNum == kafkaClntCnt*consumerGrpCnt)) );
 | 
					                ((this instanceof MessageConsumerOpDispenser) && (totalThreadNum == kafkaClntCnt*consumerGrpCnt)) );
 | 
				
			||||||
        if (!validThreadNum) {
 | 
					        if (!validThreadNum) {
 | 
				
			||||||
            throw new KafkaAdapterInvalidParamException(
 | 
					            throw new KafkaAdapterInvalidParamException(
 | 
				
			||||||
                "Incorrect settings of 'threads', 'num_clnt', or 'num_cons_grp' -- "  +
 | 
					                "Incorrect settings of 'threads', 'num_clnt', or 'num_cons_grp' -- "  +
 | 
				
			||||||
                totalThreadNum + ", " + kafkaClntCnt + ", " + consumerGrpCnt);
 | 
					                    totalThreadNum + ", " + kafkaClntCnt + ", " + consumerGrpCnt);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -112,40 +111,6 @@ public abstract  class KafkaBaseOpDispenser extends BaseOpDispenser<KafkaOp, Kaf
 | 
				
			|||||||
        return  booleanLongFunction;
 | 
					        return  booleanLongFunction;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    protected LongFunction<Set<String>> lookupStaticStrSetOpValueFunc(String paramName) {
 | 
					 | 
				
			||||||
        LongFunction<Set<String>> setStringLongFunction;
 | 
					 | 
				
			||||||
        setStringLongFunction = (l) -> parsedOp.getOptionalStaticValue(paramName, String.class)
 | 
					 | 
				
			||||||
            .filter(Predicate.not(String::isEmpty))
 | 
					 | 
				
			||||||
            .map(value -> {
 | 
					 | 
				
			||||||
                Set<String > set = new HashSet<>();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (StringUtils.contains(value,',')) {
 | 
					 | 
				
			||||||
                    set = Arrays.stream(value.split(","))
 | 
					 | 
				
			||||||
                        .map(String::trim)
 | 
					 | 
				
			||||||
                        .filter(Predicate.not(String::isEmpty))
 | 
					 | 
				
			||||||
                        .collect(Collectors.toCollection(LinkedHashSet::new));
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                return set;
 | 
					 | 
				
			||||||
            }).orElse(Collections.emptySet());
 | 
					 | 
				
			||||||
        logger.info("{}: {}", paramName, setStringLongFunction.apply(0));
 | 
					 | 
				
			||||||
        return setStringLongFunction;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // If the corresponding Op parameter is not provided, use the specified default value
 | 
					 | 
				
			||||||
    protected LongFunction<Integer> lookupStaticIntOpValueFunc(String paramName, int defaultValue) {
 | 
					 | 
				
			||||||
        LongFunction<Integer> integerLongFunction;
 | 
					 | 
				
			||||||
        integerLongFunction = (l) -> parsedOp.getOptionalStaticValue(paramName, String.class)
 | 
					 | 
				
			||||||
            .filter(Predicate.not(String::isEmpty))
 | 
					 | 
				
			||||||
            .map(value -> NumberUtils.toInt(value))
 | 
					 | 
				
			||||||
            .map(value -> {
 | 
					 | 
				
			||||||
                if (value < 0) return 0;
 | 
					 | 
				
			||||||
                else return value;
 | 
					 | 
				
			||||||
            }).orElse(defaultValue);
 | 
					 | 
				
			||||||
        logger.info("{}: {}", paramName, integerLongFunction.apply(0));
 | 
					 | 
				
			||||||
        return integerLongFunction;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // If the corresponding Op parameter is not provided, use the specified default value
 | 
					    // If the corresponding Op parameter is not provided, use the specified default value
 | 
				
			||||||
    protected LongFunction<String> lookupOptionalStrOpValueFunc(String paramName, String defaultValue) {
 | 
					    protected LongFunction<String> lookupOptionalStrOpValueFunc(String paramName, String defaultValue) {
 | 
				
			||||||
        LongFunction<String> stringLongFunction;
 | 
					        LongFunction<String> stringLongFunction;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,11 +31,9 @@ import org.apache.kafka.clients.consumer.KafkaConsumer;
 | 
				
			|||||||
import org.apache.logging.log4j.LogManager;
 | 
					import org.apache.logging.log4j.LogManager;
 | 
				
			||||||
import org.apache.logging.log4j.Logger;
 | 
					import org.apache.logging.log4j.Logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.Arrays;
 | 
					import java.util.*;
 | 
				
			||||||
import java.util.HashMap;
 | 
					 | 
				
			||||||
import java.util.Map;
 | 
					 | 
				
			||||||
import java.util.Properties;
 | 
					 | 
				
			||||||
import java.util.function.LongFunction;
 | 
					import java.util.function.LongFunction;
 | 
				
			||||||
 | 
					import java.util.stream.Collectors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class MessageConsumerOpDispenser extends KafkaBaseOpDispenser {
 | 
					public class MessageConsumerOpDispenser extends KafkaBaseOpDispenser {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -82,7 +80,7 @@ public class MessageConsumerOpDispenser extends KafkaBaseOpDispenser {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private String getEffectiveGroupId(long cycle) {
 | 
					    private String getEffectiveGroupId(long cycle) {
 | 
				
			||||||
        int grpIdx = (int) (cycle % consumerGrpCnt);
 | 
					        int grpIdx = (int) (cycle % consumerGrpCnt);
 | 
				
			||||||
        String defaultGrpNamePrefix = "nb-grp";
 | 
					        String defaultGrpNamePrefix = KafkaAdapterUtil.DFT_CONSUMER_GROUP_NAME_PREFIX;
 | 
				
			||||||
        if (consumerClientConfMap.containsKey("group.id")) {
 | 
					        if (consumerClientConfMap.containsKey("group.id")) {
 | 
				
			||||||
            defaultGrpNamePrefix = consumerClientConfMap.get("group.id");
 | 
					            defaultGrpNamePrefix = consumerClientConfMap.get("group.id");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -91,10 +89,16 @@ public class MessageConsumerOpDispenser extends KafkaBaseOpDispenser {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private OpTimeTrackKafkaClient getOrCreateOpTimeTrackKafkaConsumer(
 | 
					    private OpTimeTrackKafkaClient getOrCreateOpTimeTrackKafkaConsumer(
 | 
				
			||||||
        String cacheKey,
 | 
					        long cycle,
 | 
				
			||||||
        String groupId,
 | 
					        List<String> topicNameList,
 | 
				
			||||||
        String topicName)
 | 
					        String groupId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					        String topicNameListStr = topicNameList.stream()
 | 
				
			||||||
 | 
					            .collect(Collectors.joining("::"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        String cacheKey = KafkaAdapterUtil.buildCacheKey(
 | 
				
			||||||
 | 
					            "consumer-" + String.valueOf(cycle % kafkaClntCnt), topicNameListStr, groupId );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        OpTimeTrackKafkaClient opTimeTrackKafkaClient = kafkaSpace.getOpTimeTrackKafkaClient(cacheKey);
 | 
					        OpTimeTrackKafkaClient opTimeTrackKafkaClient = kafkaSpace.getOpTimeTrackKafkaClient(cacheKey);
 | 
				
			||||||
        if (opTimeTrackKafkaClient == null) {
 | 
					        if (opTimeTrackKafkaClient == null) {
 | 
				
			||||||
            Properties consumerConfProps = new Properties();
 | 
					            Properties consumerConfProps = new Properties();
 | 
				
			||||||
@@ -103,10 +107,15 @@ public class MessageConsumerOpDispenser extends KafkaBaseOpDispenser {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            KafkaConsumer<String, String> consumer = new KafkaConsumer<>(consumerConfProps);
 | 
					            KafkaConsumer<String, String> consumer = new KafkaConsumer<>(consumerConfProps);
 | 
				
			||||||
            synchronized (this) {
 | 
					            synchronized (this) {
 | 
				
			||||||
                consumer.subscribe(Arrays.asList(topicName));
 | 
					                consumer.subscribe(topicNameList);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            if (logger.isDebugEnabled()) {
 | 
					            if (logger.isDebugEnabled()) {
 | 
				
			||||||
                logger.debug("Kafka consumer created: {} -- {}", cacheKey, consumer);
 | 
					                logger.debug("Kafka consumer created: {}/{} -- {}, {}, {}",
 | 
				
			||||||
 | 
					                    cacheKey,
 | 
				
			||||||
 | 
					                    consumer,
 | 
				
			||||||
 | 
					                    topicNameList,
 | 
				
			||||||
 | 
					                    autoCommitEnabled,
 | 
				
			||||||
 | 
					                    maxMsgCntPerCommit);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            opTimeTrackKafkaClient = new OpTimeTrackKafkaConsumer(
 | 
					            opTimeTrackKafkaClient = new OpTimeTrackKafkaConsumer(
 | 
				
			||||||
@@ -117,19 +126,27 @@ public class MessageConsumerOpDispenser extends KafkaBaseOpDispenser {
 | 
				
			|||||||
        return opTimeTrackKafkaClient;
 | 
					        return opTimeTrackKafkaClient;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    protected List<String> getEffectiveTopicNameList(long cycle) {
 | 
				
			||||||
 | 
					        String explicitTopicListStr = topicNameStrFunc.apply(cycle);
 | 
				
			||||||
 | 
					        assert (StringUtils.isNotBlank(explicitTopicListStr));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return Arrays.stream(StringUtils.split(explicitTopicListStr, ','))
 | 
				
			||||||
 | 
					            .filter(s -> StringUtils.isNotBlank(s))
 | 
				
			||||||
 | 
					            .toList();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public KafkaOp apply(long cycle) {
 | 
					    public KafkaOp apply(long cycle) {
 | 
				
			||||||
        String topicName = topicNameStrFunc.apply(cycle);
 | 
					        List<String> topicNameList = getEffectiveTopicNameList(cycle);
 | 
				
			||||||
        String groupId = getEffectiveGroupId(cycle);
 | 
					        String groupId = getEffectiveGroupId(cycle);
 | 
				
			||||||
        String cacheKey = KafkaAdapterUtil.buildCacheKey(
 | 
					        if (topicNameList.size() ==0 || StringUtils.isBlank(groupId)) {
 | 
				
			||||||
            "consumer", topicName, groupId, String.valueOf(cycle % kafkaClntCnt));
 | 
					            throw new KafkaAdapterInvalidParamException(
 | 
				
			||||||
 | 
					                "Effective consumer group name and/or topic names  are needed for creating a consumer!");
 | 
				
			||||||
        if (StringUtils.isBlank(groupId)) {
 | 
					 | 
				
			||||||
            throw new KafkaAdapterInvalidParamException("An effective \"group.id\" is needed for a consumer!");
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        OpTimeTrackKafkaClient opTimeTrackKafkaConsumer =
 | 
					        OpTimeTrackKafkaClient opTimeTrackKafkaConsumer =
 | 
				
			||||||
            getOrCreateOpTimeTrackKafkaConsumer(cacheKey, groupId, topicName);
 | 
					            getOrCreateOpTimeTrackKafkaConsumer(cycle, topicNameList, groupId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return new KafkaOp(
 | 
					        return new KafkaOp(
 | 
				
			||||||
            kafkaAdapterMetrics,
 | 
					            kafkaAdapterMetrics,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -59,8 +59,7 @@ public class MessageProducerOpDispenser extends KafkaBaseOpDispenser {
 | 
				
			|||||||
        this.producerClientConfMap.putAll(kafkaSpace.getKafkaClientConf().getProducerConfMap());
 | 
					        this.producerClientConfMap.putAll(kafkaSpace.getKafkaClientConf().getProducerConfMap());
 | 
				
			||||||
        producerClientConfMap.put("bootstrap.servers", kafkaSpace.getBootstrapSvr());
 | 
					        producerClientConfMap.put("bootstrap.servers", kafkaSpace.getBootstrapSvr());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.txnBatchNum =
 | 
					        this.txnBatchNum = parsedOp.getStaticConfigOr("txn_batch_num", Integer.valueOf(0));
 | 
				
			||||||
            parsedOp.getStaticConfigOr(KafkaAdapterUtil.DOC_LEVEL_PARAMS.TXN_BATCH_NUM.label, Integer.valueOf(0));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.msgHeaderJsonStrFunc = lookupOptionalStrOpValueFunc(MSG_HEADER_OP_PARAM);
 | 
					        this.msgHeaderJsonStrFunc = lookupOptionalStrOpValueFunc(MSG_HEADER_OP_PARAM);
 | 
				
			||||||
        this.msgKeyStrFunc = lookupOptionalStrOpValueFunc(MSG_KEY_OP_PARAM);
 | 
					        this.msgKeyStrFunc = lookupOptionalStrOpValueFunc(MSG_KEY_OP_PARAM);
 | 
				
			||||||
@@ -79,38 +78,53 @@ public class MessageProducerOpDispenser extends KafkaBaseOpDispenser {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private OpTimeTrackKafkaClient getOrCreateOpTimeTrackKafkaProducer(
 | 
					    private OpTimeTrackKafkaClient getOrCreateOpTimeTrackKafkaProducer(long cycle,
 | 
				
			||||||
        String cacheKey, String clientId)
 | 
					                                                                       String topicName,
 | 
				
			||||||
 | 
					                                                                       String clientId)
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
 | 
					        String cacheKey = KafkaAdapterUtil.buildCacheKey(
 | 
				
			||||||
 | 
					            "producer-" + String.valueOf(cycle % kafkaClntCnt), topicName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        OpTimeTrackKafkaClient opTimeTrackKafkaClient = kafkaSpace.getOpTimeTrackKafkaClient(cacheKey);
 | 
					        OpTimeTrackKafkaClient opTimeTrackKafkaClient = kafkaSpace.getOpTimeTrackKafkaClient(cacheKey);
 | 
				
			||||||
        if (opTimeTrackKafkaClient == null) {
 | 
					        if (opTimeTrackKafkaClient == null) {
 | 
				
			||||||
            Properties producerConfProps = new Properties();
 | 
					            Properties producerConfProps = new Properties();
 | 
				
			||||||
            producerConfProps.putAll(producerClientConfMap);
 | 
					            producerConfProps.putAll(producerClientConfMap);
 | 
				
			||||||
            producerConfProps.put("client.id", clientId);
 | 
					
 | 
				
			||||||
 | 
					            if (StringUtils.isNotBlank(clientId))
 | 
				
			||||||
 | 
					                producerConfProps.put("client.id", clientId);
 | 
				
			||||||
 | 
					            else
 | 
				
			||||||
 | 
					                producerConfProps.remove("client.id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // When transaction batch number is less than 2, it is treated effectively as no-transaction
 | 
					            // When transaction batch number is less than 2, it is treated effectively as no-transaction
 | 
				
			||||||
            if (txnBatchNum < 2)
 | 
					            if (txnBatchNum < 2)
 | 
				
			||||||
                producerConfProps.remove("transactional.id");
 | 
					                producerConfProps.remove("transactional.id");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            String baseTransactId = "";
 | 
					            String baseTransactId = "";
 | 
				
			||||||
 | 
					            boolean transactionEnabled = false;
 | 
				
			||||||
            if (producerConfProps.containsKey("transactional.id")) {
 | 
					            if (producerConfProps.containsKey("transactional.id")) {
 | 
				
			||||||
                baseTransactId = producerConfProps.get("transactional.id").toString();
 | 
					                baseTransactId = producerConfProps.get("transactional.id").toString();
 | 
				
			||||||
                producerConfProps.put("transactional.id", baseTransactId + "-" + cacheKey);
 | 
					                producerConfProps.put("transactional.id", baseTransactId + "-" + cacheKey);
 | 
				
			||||||
 | 
					                transactionEnabled = StringUtils.isNotBlank(producerConfProps.get("transactional.id").toString());
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            KafkaProducer<String, String> producer = new KafkaProducer<>(producerConfProps);
 | 
					            KafkaProducer<String, String> producer = new KafkaProducer<>(producerConfProps);
 | 
				
			||||||
            if (producerConfProps.containsKey("transactional.id")) {
 | 
					            if (transactionEnabled) {
 | 
				
			||||||
                producer.initTransactions();
 | 
					                producer.initTransactions();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (logger.isDebugEnabled()) {
 | 
					            if (logger.isDebugEnabled()) {
 | 
				
			||||||
                logger.debug("Producer created: {} -- {}", cacheKey, producer);
 | 
					                logger.debug("Producer created: {}/{} -- ({}, {}, {})",
 | 
				
			||||||
 | 
					                    cacheKey,
 | 
				
			||||||
 | 
					                    producer,
 | 
				
			||||||
 | 
					                    topicName,
 | 
				
			||||||
 | 
					                    transactionEnabled,
 | 
				
			||||||
 | 
					                    clientId);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            opTimeTrackKafkaClient = new OpTimeTrackKafkaProducer(
 | 
					            opTimeTrackKafkaClient = new OpTimeTrackKafkaProducer(
 | 
				
			||||||
                kafkaSpace,
 | 
					                kafkaSpace,
 | 
				
			||||||
                asyncAPI,
 | 
					                asyncAPI,
 | 
				
			||||||
                StringUtils.isNotBlank(producerClientConfMap.get("transactional.id")),
 | 
					                transactionEnabled,
 | 
				
			||||||
                txnBatchNum,
 | 
					                txnBatchNum,
 | 
				
			||||||
                producer);
 | 
					                producer);
 | 
				
			||||||
            kafkaSpace.addOpTimeTrackKafkaClient(cacheKey, opTimeTrackKafkaClient);
 | 
					            kafkaSpace.addOpTimeTrackKafkaClient(cacheKey, opTimeTrackKafkaClient);
 | 
				
			||||||
@@ -176,11 +190,9 @@ public class MessageProducerOpDispenser extends KafkaBaseOpDispenser {
 | 
				
			|||||||
    public KafkaOp apply(long cycle) {
 | 
					    public KafkaOp apply(long cycle) {
 | 
				
			||||||
        String topicName = topicNameStrFunc.apply(cycle);
 | 
					        String topicName = topicNameStrFunc.apply(cycle);
 | 
				
			||||||
        String clientId = getEffectiveClientId(cycle);
 | 
					        String clientId = getEffectiveClientId(cycle);
 | 
				
			||||||
        String cacheKey = KafkaAdapterUtil.buildCacheKey(
 | 
					 | 
				
			||||||
            "producer", topicName, String.valueOf(cycle % kafkaClntCnt));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        OpTimeTrackKafkaClient opTimeTrackKafkaProducer =
 | 
					        OpTimeTrackKafkaClient opTimeTrackKafkaProducer =
 | 
				
			||||||
            getOrCreateOpTimeTrackKafkaProducer(cacheKey, clientId);
 | 
					            getOrCreateOpTimeTrackKafkaProducer(cycle, topicName, clientId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ProducerRecord<String, String> message = createKafkaMessage(
 | 
					        ProducerRecord<String, String> message = createKafkaMessage(
 | 
				
			||||||
            cycle,
 | 
					            cycle,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -106,6 +106,10 @@ public class OpTimeTrackKafkaConsumer extends OpTimeTrackKafkaClient {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    void cycleMsgProcess(long cycle, Object cycleObj) {
 | 
					    void cycleMsgProcess(long cycle, Object cycleObj) {
 | 
				
			||||||
 | 
					        if (kafkaSpace.isShuttigDown()) {
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        synchronized (this) {
 | 
					        synchronized (this) {
 | 
				
			||||||
            ConsumerRecords<String, String> records = consumer.poll(msgPoolIntervalInMs);
 | 
					            ConsumerRecords<String, String> records = consumer.poll(msgPoolIntervalInMs);
 | 
				
			||||||
            for (ConsumerRecord<String, String> record : records) {
 | 
					            for (ConsumerRecord<String, String> record : records) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,16 +18,23 @@
 | 
				
			|||||||
package io.nosqlbench.adapter.kafka.ops;
 | 
					package io.nosqlbench.adapter.kafka.ops;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import io.nosqlbench.adapter.kafka.KafkaSpace;
 | 
					import io.nosqlbench.adapter.kafka.KafkaSpace;
 | 
				
			||||||
 | 
					import io.nosqlbench.adapter.kafka.exception.KafkaAdapterUnexpectedException;
 | 
				
			||||||
import io.nosqlbench.adapter.kafka.util.KafkaAdapterUtil;
 | 
					import io.nosqlbench.adapter.kafka.util.KafkaAdapterUtil;
 | 
				
			||||||
import org.apache.kafka.clients.producer.Callback;
 | 
					import org.apache.kafka.clients.producer.Callback;
 | 
				
			||||||
import org.apache.kafka.clients.producer.KafkaProducer;
 | 
					import org.apache.kafka.clients.producer.KafkaProducer;
 | 
				
			||||||
import org.apache.kafka.clients.producer.ProducerRecord;
 | 
					import org.apache.kafka.clients.producer.ProducerRecord;
 | 
				
			||||||
import org.apache.kafka.clients.producer.RecordMetadata;
 | 
					import org.apache.kafka.clients.producer.RecordMetadata;
 | 
				
			||||||
 | 
					import org.apache.kafka.common.KafkaException;
 | 
				
			||||||
 | 
					import org.apache.kafka.common.errors.AuthorizationException;
 | 
				
			||||||
 | 
					import org.apache.kafka.common.errors.OutOfOrderSequenceException;
 | 
				
			||||||
 | 
					import org.apache.kafka.common.errors.ProducerFencedException;
 | 
				
			||||||
import org.apache.logging.log4j.LogManager;
 | 
					import org.apache.logging.log4j.LogManager;
 | 
				
			||||||
import org.apache.logging.log4j.Logger;
 | 
					import org.apache.logging.log4j.Logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.concurrent.ExecutionException;
 | 
					import java.util.concurrent.ExecutionException;
 | 
				
			||||||
import java.util.concurrent.Future;
 | 
					import java.util.concurrent.Future;
 | 
				
			||||||
 | 
					import org.apache.kafka.common.errors.TimeoutException;
 | 
				
			||||||
 | 
					import org.apache.kafka.common.errors.InterruptException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
public class OpTimeTrackKafkaProducer extends OpTimeTrackKafkaClient {
 | 
					public class OpTimeTrackKafkaProducer extends OpTimeTrackKafkaClient {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -39,8 +46,20 @@ public class OpTimeTrackKafkaProducer extends OpTimeTrackKafkaClient {
 | 
				
			|||||||
    private final boolean transactEnabledConfig;
 | 
					    private final boolean transactEnabledConfig;
 | 
				
			||||||
    private final int txnBatchNum;
 | 
					    private final int txnBatchNum;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    enum TxnProcResult {
 | 
				
			||||||
 | 
					        SUCCESS,
 | 
				
			||||||
 | 
					        RECOVERABLE_ERROR,
 | 
				
			||||||
 | 
					        FATAL_ERROR,
 | 
				
			||||||
 | 
					        UNKNOWN_ERROR
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Keep track the transaction count per thread
 | 
					    // Keep track the transaction count per thread
 | 
				
			||||||
    private final ThreadLocal<Integer> txnBatchTrackingCnt = ThreadLocal.withInitial(() -> 0);
 | 
					    private static ThreadLocal<Integer>
 | 
				
			||||||
 | 
					        txnBatchTrackingCntTL = ThreadLocal.withInitial(() -> 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private static ThreadLocal<TxnProcResult>
 | 
				
			||||||
 | 
					        txnProcResultTL = ThreadLocal.withInitial(() -> TxnProcResult.SUCCESS);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private final KafkaProducer<String, String> producer;
 | 
					    private final KafkaProducer<String, String> producer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -57,47 +76,83 @@ public class OpTimeTrackKafkaProducer extends OpTimeTrackKafkaClient {
 | 
				
			|||||||
        this.producer = producer;
 | 
					        this.producer = producer;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public int getTxnBatchTrackingCnt() { return txnBatchTrackingCnt.get(); }
 | 
					    public static int getTxnBatchTrackingCntTL() {
 | 
				
			||||||
    public void incTxnBatchTrackingCnt() {
 | 
					        return txnBatchTrackingCntTL.get();
 | 
				
			||||||
        int curVal = getTxnBatchTrackingCnt();
 | 
					    }
 | 
				
			||||||
        txnBatchTrackingCnt.set(curVal + 1);
 | 
					    public static void incTxnBatchTrackingCnt() {
 | 
				
			||||||
 | 
					        txnBatchTrackingCntTL.set(getTxnBatchTrackingCntTL() + 1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    public static void resetTxnBatchTrackingCnt() {
 | 
				
			||||||
 | 
					        txnBatchTrackingCntTL.set(0);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private boolean commitCurTransactionNeeded(long cycle) {
 | 
					    public static TxnProcResult getTxnProcResultTL() {
 | 
				
			||||||
        // Whether to commit the transaction which happens when:
 | 
					        return txnProcResultTL.get();
 | 
				
			||||||
        // - "txn_batch_num" has been reached since last reset
 | 
					    }
 | 
				
			||||||
        boolean commitCurTrans = transactionEnabled;
 | 
					    public static void setTxnProcResultTL(TxnProcResult result) {
 | 
				
			||||||
 | 
					        txnProcResultTL.set(result);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    public static void resetTxnProcResultTL(TxnProcResult result) {
 | 
				
			||||||
 | 
					        txnProcResultTL.set(TxnProcResult.SUCCESS);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (commitCurTrans) {
 | 
					    private void processMsgTransaction(long cycle, KafkaProducer<String, String> producer) {
 | 
				
			||||||
            int txnBatchTackingCnt = getTxnBatchTrackingCnt();
 | 
					        TxnProcResult result = TxnProcResult.SUCCESS;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if ( ( (txnBatchTackingCnt > 0) && ((txnBatchTackingCnt % txnBatchNum) == 0) ) ||
 | 
					        if (transactionEnabled) {
 | 
				
			||||||
                ( cycle >= (kafkaSpace.getTotalCycleNum() - 1) ) ) {
 | 
					            int txnBatchTackingCnt = getTxnBatchTrackingCntTL();
 | 
				
			||||||
                if (logger.isDebugEnabled()) {
 | 
					
 | 
				
			||||||
                    logger.debug("Commit transaction ({}, {})",
 | 
					            try {
 | 
				
			||||||
                        txnBatchTackingCnt, cycle);
 | 
					                if (txnBatchTackingCnt == 0) {
 | 
				
			||||||
 | 
					                    // Start a new transaction when first starting the processing
 | 
				
			||||||
 | 
					                    producer.beginTransaction();
 | 
				
			||||||
 | 
					                    if (logger.isDebugEnabled()) {
 | 
				
			||||||
 | 
					                        logger.debug("New transaction started ( {}, {}, {}, {}, {} )",
 | 
				
			||||||
 | 
					                            cycle, producer, transactEnabledConfig, txnBatchNum, getTxnBatchTrackingCntTL());
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                } else if ( (txnBatchTackingCnt % (txnBatchNum - 1) == 0) ||
 | 
				
			||||||
 | 
					                            (cycle == (kafkaSpace.getTotalCycleNum() - 1)) ) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    synchronized (this) {
 | 
				
			||||||
 | 
					                        // Commit the current transaction
 | 
				
			||||||
 | 
					                        if (logger.isDebugEnabled()) {
 | 
				
			||||||
 | 
					                            logger.debug("Start committing transaction ... ( {}, {}, {}, {}, {} )",
 | 
				
			||||||
 | 
					                                cycle, producer, transactEnabledConfig, txnBatchNum, getTxnBatchTrackingCntTL());
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                        producer.commitTransaction();
 | 
				
			||||||
 | 
					                        if (logger.isDebugEnabled()) {
 | 
				
			||||||
 | 
					                            logger.debug("Transaction committed ( {}, {}, {}, {}, {} )",
 | 
				
			||||||
 | 
					                                cycle, producer, transactEnabledConfig, txnBatchNum, getTxnBatchTrackingCntTL());
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        // Start a new transaction
 | 
				
			||||||
 | 
					                        producer.beginTransaction();
 | 
				
			||||||
 | 
					                        if (logger.isDebugEnabled()) {
 | 
				
			||||||
 | 
					                            logger.debug("New transaction started ( {}, {}, {}, {}, {} )",
 | 
				
			||||||
 | 
					                                cycle, producer, transactEnabledConfig, txnBatchNum, getTxnBatchTrackingCntTL());
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            else {
 | 
					            catch (Exception e) {
 | 
				
			||||||
                commitCurTrans = false;
 | 
					                e.printStackTrace();
 | 
				
			||||||
 | 
					                if ( (e instanceof IllegalStateException) ||
 | 
				
			||||||
 | 
					                     (e instanceof ProducerFencedException) ||
 | 
				
			||||||
 | 
					                     (e instanceof UnsupportedOperationException) ||
 | 
				
			||||||
 | 
					                     (e instanceof AuthorizationException) ) {
 | 
				
			||||||
 | 
					                    result = TxnProcResult.FATAL_ERROR;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else if ( (e instanceof TimeoutException ) ||
 | 
				
			||||||
 | 
					                          (e instanceof  InterruptException)) {
 | 
				
			||||||
 | 
					                    result = TxnProcResult.RECOVERABLE_ERROR;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                else {
 | 
				
			||||||
 | 
					                    result = TxnProcResult.UNKNOWN_ERROR;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return commitCurTrans;
 | 
					        setTxnProcResultTL(result);
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private boolean startNewTransactionNeeded(long cycle) {
 | 
					 | 
				
			||||||
        boolean startNewTransact = transactionEnabled;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (startNewTransact) {
 | 
					 | 
				
			||||||
            if ( (cycle > 0) && (cycle < (kafkaSpace.getTotalCycleNum() - 1)) ) {
 | 
					 | 
				
			||||||
                startNewTransact = commitCurTransactionNeeded(cycle);
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                startNewTransact = false;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return startNewTransact;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
@@ -105,55 +160,88 @@ public class OpTimeTrackKafkaProducer extends OpTimeTrackKafkaClient {
 | 
				
			|||||||
        // For producer, cycleObj represents a "message" (ProducerRecord)
 | 
					        // For producer, cycleObj represents a "message" (ProducerRecord)
 | 
				
			||||||
        assert (cycleObj != null);
 | 
					        assert (cycleObj != null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        if (kafkaSpace.isShuttigDown()) {
 | 
				
			||||||
            ProducerRecord<String, String> message = (ProducerRecord<String, String>) cycleObj;
 | 
					            if (transactionEnabled) {
 | 
				
			||||||
            boolean startNewTransNeeded = startNewTransactionNeeded(cycle);
 | 
					                try {
 | 
				
			||||||
            boolean commitCurTransNeeded = commitCurTransactionNeeded(cycle);
 | 
					                    producer.abortTransaction();
 | 
				
			||||||
 | 
					                    if (logger.isDebugEnabled()) {
 | 
				
			||||||
 | 
					                        logger.debug("Abort open transaction while shutting down ( {}, {}, {}, {}, {} )",
 | 
				
			||||||
 | 
					                            cycle, producer, transactEnabledConfig, txnBatchNum, getTxnBatchTrackingCntTL());
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                catch (Exception e) {
 | 
				
			||||||
 | 
					                    e.printStackTrace();
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (commitCurTransNeeded) {
 | 
					        processMsgTransaction(cycle, producer);
 | 
				
			||||||
                producer.commitTransaction();
 | 
					        TxnProcResult result = getTxnProcResultTL();
 | 
				
			||||||
                if (logger.isDebugEnabled()) {
 | 
					
 | 
				
			||||||
                    logger.debug("Transaction committed ( {}, {}, {}, {} )",
 | 
					        if (result == TxnProcResult.RECOVERABLE_ERROR) {
 | 
				
			||||||
                        cycle, transactEnabledConfig, txnBatchNum, getTxnBatchTrackingCnt());
 | 
					            try {
 | 
				
			||||||
 | 
					                producer.abortTransaction();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (Exception e) {
 | 
				
			||||||
 | 
					                throw new KafkaAdapterUnexpectedException("Aborting transaction failed!");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else if (result == TxnProcResult.FATAL_ERROR) {
 | 
				
			||||||
 | 
					            throw new KafkaAdapterUnexpectedException("Fatal error when initializing or committing transactions!");
 | 
				
			||||||
 | 
					        } else if (result == TxnProcResult.UNKNOWN_ERROR) {
 | 
				
			||||||
 | 
					            logger.debug("Unexpected error when initializing or committing transactions!");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ProducerRecord<String, String> message = (ProducerRecord<String, String>) cycleObj;
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            if (result == TxnProcResult.SUCCESS) {
 | 
				
			||||||
 | 
					                Future<RecordMetadata> responseFuture = producer.send(message, new Callback() {
 | 
				
			||||||
 | 
					                    @Override
 | 
				
			||||||
 | 
					                    public void onCompletion(RecordMetadata recordMetadata, Exception e) {
 | 
				
			||||||
 | 
					                        if (asyncMsgAck) {
 | 
				
			||||||
 | 
					                            if (logger.isDebugEnabled()) {
 | 
				
			||||||
 | 
					                                logger.debug("Message sending with async ack. is successful ({}) - {}, {}",
 | 
				
			||||||
 | 
					                                    cycle, producer, recordMetadata);
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (!asyncMsgAck) {
 | 
				
			||||||
 | 
					                    try {
 | 
				
			||||||
 | 
					                        RecordMetadata recordMetadata = responseFuture.get();
 | 
				
			||||||
 | 
					                        if (logger.isDebugEnabled()) {
 | 
				
			||||||
 | 
					                            logger.debug("Message sending with sync ack. is successful ({}) - {}, {}",
 | 
				
			||||||
 | 
					                                cycle, producer, recordMetadata);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    } catch (InterruptedException | ExecutionException e) {
 | 
				
			||||||
 | 
					                        KafkaAdapterUtil.messageErrorHandling(
 | 
				
			||||||
 | 
					                            e,
 | 
				
			||||||
 | 
					                            kafkaSpace.isStrictMsgErrorHandling(),
 | 
				
			||||||
 | 
					                            "Unexpected error when waiting to receive message-send ack from the Kafka cluster." +
 | 
				
			||||||
 | 
					                                "\n-----\n" + e);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                incTxnBatchTrackingCnt();
 | 
					                incTxnBatchTrackingCnt();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (startNewTransNeeded) {
 | 
					        }
 | 
				
			||||||
                producer.beginTransaction();
 | 
					        catch ( ProducerFencedException | OutOfOrderSequenceException |
 | 
				
			||||||
 | 
					                UnsupportedOperationException | AuthorizationException e) {
 | 
				
			||||||
 | 
					            if (logger.isDebugEnabled()) {
 | 
				
			||||||
 | 
					                logger.debug("Fatal error when sending a message ({}) - {}, {}",
 | 
				
			||||||
 | 
					                    cycle, producer, message);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            throw new KafkaAdapterUnexpectedException(e);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        catch (IllegalStateException | KafkaException e) {
 | 
				
			||||||
 | 
					            if (transactionEnabled) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            Future<RecordMetadata> responseFuture = producer.send(message, new Callback() {
 | 
					 | 
				
			||||||
                @Override
 | 
					 | 
				
			||||||
                public void onCompletion(RecordMetadata recordMetadata, Exception e) {
 | 
					 | 
				
			||||||
                    if (asyncMsgAck) {
 | 
					 | 
				
			||||||
                        if (logger.isDebugEnabled()) {
 | 
					 | 
				
			||||||
                            logger.debug("Message sending with async ack. is successful ( {} ) - {}",
 | 
					 | 
				
			||||||
                                cycle, recordMetadata);
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!asyncMsgAck) {
 | 
					 | 
				
			||||||
                try {
 | 
					 | 
				
			||||||
                    RecordMetadata recordMetadata = responseFuture.get();
 | 
					 | 
				
			||||||
                    if (logger.isDebugEnabled()) {
 | 
					 | 
				
			||||||
                        logger.debug("Message sending with sync ack. is successful ( {} ) - {}",
 | 
					 | 
				
			||||||
                            cycle, recordMetadata);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                } catch (InterruptedException | ExecutionException e) {
 | 
					 | 
				
			||||||
                    KafkaAdapterUtil.messageErrorHandling(
 | 
					 | 
				
			||||||
                        e,
 | 
					 | 
				
			||||||
                        kafkaSpace.isStrictMsgErrorHandling(),
 | 
					 | 
				
			||||||
                        "Unexpected error when waiting to receive message-send ack from the Kafka cluster." +
 | 
					 | 
				
			||||||
                            "\n-----\n" + e);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        catch (Exception e) {
 | 
					        catch (Exception e) {
 | 
				
			||||||
            e.printStackTrace();
 | 
					            throw new KafkaAdapterUnexpectedException(e);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -164,7 +252,7 @@ public class OpTimeTrackKafkaProducer extends OpTimeTrackKafkaClient {
 | 
				
			|||||||
                producer.close();
 | 
					                producer.close();
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.txnBatchTrackingCnt.remove();
 | 
					            this.txnBatchTrackingCntTL.remove();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        catch (IllegalStateException ise) {
 | 
					        catch (IllegalStateException ise) {
 | 
				
			||||||
            // If a producer is already closed, that's fine.
 | 
					            // If a producer is already closed, that's fine.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,14 +34,14 @@ public class KafkaAdapterUtil {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private final static Logger logger = LogManager.getLogger(KafkaAdapterUtil.class);
 | 
					    private final static Logger logger = LogManager.getLogger(KafkaAdapterUtil.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    public static String DFT_CONSUMER_GROUP_NAME_PREFIX = "nbKafkaGrp";
 | 
				
			||||||
 | 
					    public static String DFT_TOPIC_NAME_PREFIX = "nbKafkaTopic";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ///////
 | 
					    ///////
 | 
				
			||||||
    // Valid document level parameters for JMS NB yaml file
 | 
					    // Valid document level parameters for JMS NB yaml file
 | 
				
			||||||
    public enum DOC_LEVEL_PARAMS {
 | 
					    public enum DOC_LEVEL_PARAMS {
 | 
				
			||||||
        // Blocking message producing or consuming
 | 
					        // Blocking message producing or consuming
 | 
				
			||||||
        ASYNC_API("async_api"),
 | 
					        ASYNC_API("async_api");
 | 
				
			||||||
        // Transaction batch size
 | 
					 | 
				
			||||||
        TXN_BATCH_NUM("txn_batch_num");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public final String label;
 | 
					        public final String label;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        DOC_LEVEL_PARAMS(String label) {
 | 
					        DOC_LEVEL_PARAMS(String label) {
 | 
				
			||||||
@@ -80,7 +80,9 @@ public class KafkaAdapterUtil {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    public static String buildCacheKey(String... keyParts) {
 | 
					    public static String buildCacheKey(String... keyParts) {
 | 
				
			||||||
        String combinedStr = String.join("::", keyParts);
 | 
					        String combinedStr = Arrays.stream(keyParts)
 | 
				
			||||||
 | 
					            .filter(StringUtils::isNotBlank)
 | 
				
			||||||
 | 
					            .collect(Collectors.joining("::"));
 | 
				
			||||||
        return Base64.encodeAsString(combinedStr.getBytes());
 | 
					        return Base64.encodeAsString(combinedStr.getBytes());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,41 @@
 | 
				
			|||||||
 | 
					# Overview
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					This NB Kafka driver allows publishing messages to or consuming messages from
 | 
				
			||||||
 | 
					* a Kafka cluster, or
 | 
				
			||||||
 | 
					* a Pulsar cluster with [S4K](https://github.com/datastax/starlight-for-kafka) or [KoP](https://github.com/streamnative/kop) Kafka Protocol handler for Pulsar.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					At high level, this driver supports the following Kafka functionalities
 | 
				
			||||||
 | 
					* Publishing messages to one Kafka topic with sync. or async. message-send acknowledgements (from brokers)
 | 
				
			||||||
 | 
					* Subscribing messages from one or multiple Kafka topics with sync. or async. message-recv acknowlegements (to brokers) (aka, message commits)
 | 
				
			||||||
 | 
					    * auto message commit
 | 
				
			||||||
 | 
					    * manual message commit with a configurable number of message commits in one batch
 | 
				
			||||||
 | 
					* Kafka Transaction support
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Example NB Yaml
 | 
				
			||||||
 | 
					* [kafka_producer.yaml](./kafka_producer.yaml)
 | 
				
			||||||
 | 
					*
 | 
				
			||||||
 | 
					* [kafka_consumer.yaml](./kafka_consumer.yaml)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Usage
 | 
					# Usage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
## Kafka Producer
 | 
					## Kafka Producer
 | 
				
			||||||
$ <nb_cmd> run driver=kafka -vv cycles=100 threads=2 num_clnt=2 yaml=kafka_producer.yaml config=kafka_config.properties bootstrap_server=PLAINTEXT://10.166.90.94:9092
 | 
					$ <nb_cmd> run driver=kafka -vv cycles=100 threads=2 num_clnt=2 yaml=kafka_producer.yaml config=kafka_config.properties bootstrap_server=PLAINTEXT://localhost:9092
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Kafka Consumer
 | 
					## Kafka Consumer
 | 
				
			||||||
$ <nb_cmd> run driver=kafka -vv cycles=100 threads=4 num_clnt=2 num_cons_grp=2 yaml=kafka_producer.yaml config=kafka_config.properties bootstrap_server=PLAINTEXT://10.166.90.94:9092
 | 
					$ <nb_cmd> run driver=kafka -vv cycles=100 threads=4 num_clnt=2 num_cons_grp=2 yaml=kafka_producer.yaml config=kafka_config.properties bootstrap_server=PLAINTEXT://localhost:9092
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Example NB Yaml
 | 
					## NB Kafka driver specific CLI parameters
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[kafka_producer.yaml](./kafka_producer.yaml)
 | 
					* `num_clnt`: the number of Kafka clients to publish messages to or to receive messages from
 | 
				
			||||||
 | 
					    * For producer workload, this is the number of the producer threads to publish messages to the same topic
 | 
				
			||||||
 | 
					        * Can have multiple producer threads for one topic/partition (`KafkaProducer` is thread-safe)
 | 
				
			||||||
 | 
					        * `threads` and `num_clnt` values MUST be the same.
 | 
				
			||||||
 | 
					    * For consumer workload, this is the partition number of a topic
 | 
				
			||||||
 | 
					        * Consumer workload supports to subscribe from multiple topics. If so, it requires all topics having the same partition number.
 | 
				
			||||||
 | 
					        * Only one consumer thread for one topic/partition (`KafkaConsumer` is NOT thread-safe)
 | 
				
			||||||
 | 
					        * `threads` MUST be equal to `num_clnt`*`num_cons_grp`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[kafka_consumer.yaml](./kafka_consumer.yaml)
 | 
					* `num_cons_grp`: the number of consumer groups
 | 
				
			||||||
 | 
					    * Only relevant for consumer workload
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,7 +15,7 @@ topic.flush.messages=2
 | 
				
			|||||||
producer.key.serializer=org.apache.kafka.common.serialization.StringSerializer
 | 
					producer.key.serializer=org.apache.kafka.common.serialization.StringSerializer
 | 
				
			||||||
producer.value.serializer=org.apache.kafka.common.serialization.StringSerializer
 | 
					producer.value.serializer=org.apache.kafka.common.serialization.StringSerializer
 | 
				
			||||||
#producer.client.id=nbDftClient
 | 
					#producer.client.id=nbDftClient
 | 
				
			||||||
#producer.transactional.id=nbDftTxn
 | 
					producer.transactional.id=nbDftTxn
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#####
 | 
					#####
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,9 +9,9 @@ blocks:
 | 
				
			|||||||
  msg-consume-block:
 | 
					  msg-consume-block:
 | 
				
			||||||
    ops:
 | 
					    ops:
 | 
				
			||||||
      op1:
 | 
					      op1:
 | 
				
			||||||
        ## TODO: can make this as a list of topics
 | 
					        ## The value represents the topic names
 | 
				
			||||||
        ## The value represents the topic name
 | 
					        #  - for consumer, a list of topics (separated by comma) are supported
 | 
				
			||||||
        MessageConsume: "nbktest1"
 | 
					        MessageConsume: "nbktest1,nbktest2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # The timeout value to poll messages (unit: milli-seconds)
 | 
					        # The timeout value to poll messages (unit: milli-seconds)
 | 
				
			||||||
        # - default: 0
 | 
					        # - default: 0
 | 
				
			||||||
@@ -19,5 +19,6 @@ blocks:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        # The number of messages to receive before doing a manual commit
 | 
					        # The number of messages to receive before doing a manual commit
 | 
				
			||||||
        # - default: 0
 | 
					        # - default: 0
 | 
				
			||||||
        # When setting to 0, it could mean doing auto commit (determined by "enable.auto.commit" parameter)
 | 
					        # - If 0, it could mean doing auto commit or not, which is determined
 | 
				
			||||||
 | 
					        #   by "enable.auto.commit" consumer config value
 | 
				
			||||||
        manual_commit_batch_num: "0"
 | 
					        manual_commit_batch_num: "0"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,18 +10,19 @@ params:
 | 
				
			|||||||
  # - default: true
 | 
					  # - default: true
 | 
				
			||||||
  async_api: "true"
 | 
					  async_api: "true"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # The number of messages to put in one transaction
 | 
					 | 
				
			||||||
  # - default: 0
 | 
					 | 
				
			||||||
  # - value 0 or 1 means no transaction
 | 
					 | 
				
			||||||
  # - it also requires "transactional.id" parameter is set
 | 
					 | 
				
			||||||
  txn_batch_num: 5
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
blocks:
 | 
					blocks:
 | 
				
			||||||
  msg-produce-block:
 | 
					  msg-produce-block:
 | 
				
			||||||
    ops:
 | 
					    ops:
 | 
				
			||||||
      op1:
 | 
					      op1:
 | 
				
			||||||
        ## The value represents a topic name
 | 
					        ## The value represents a topic name
 | 
				
			||||||
        MessageProduce: "nbktest1"
 | 
					        #  - for producer, only ONE topic is supported
 | 
				
			||||||
 | 
					        MessageProduce: "nbktest"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # The number of messages to put in one transaction
 | 
				
			||||||
 | 
					        # - default: 0
 | 
				
			||||||
 | 
					        # - value 0 or 1 means no transaction
 | 
				
			||||||
 | 
					        # - it also requires "transactional.id" parameter is set
 | 
				
			||||||
 | 
					        txn_batch_num: 8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ## (Optional) Kafka message headers (in JSON format).
 | 
					        ## (Optional) Kafka message headers (in JSON format).
 | 
				
			||||||
        msg_header: |
 | 
					        msg_header: |
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user