Workaround for KAFKA-4090
This commit is contained in:
parent
150ba375de
commit
24829c29f7
@ -0,0 +1,185 @@
|
|||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||||
|
* (the "License"); you may not use this file except in compliance with
|
||||||
|
* the License. You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Content of this file was modified to addresses the issue https://issues.apache.org/jira/browse/KAFKA-4090
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package org.apache.kafka.common.network;
|
||||||
|
|
||||||
|
import org.apache.kafka.common.memory.MemoryPool;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.thingsboard.server.common.data.exception.ThingsboardKafkaClientError;
|
||||||
|
|
||||||
|
import java.io.EOFException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.ScatteringByteChannel;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A size delimited Receive that consists of a 4 byte network-ordered size N followed by N bytes of content
|
||||||
|
*/
|
||||||
|
public class NetworkReceive implements Receive {
|
||||||
|
|
||||||
|
public final static String UNKNOWN_SOURCE = "";
|
||||||
|
public final static int UNLIMITED = -1;
|
||||||
|
public final static int TB_MAX_REQUESTED_BUFFER_SIZE = 100 * 1024 * 1024;
|
||||||
|
public final static int TB_LOG_REQUESTED_BUFFER_SIZE = 10 * 1024 * 1024;
|
||||||
|
private static final Logger log = LoggerFactory.getLogger(NetworkReceive.class);
|
||||||
|
private static final ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
|
||||||
|
|
||||||
|
private final String source;
|
||||||
|
private final ByteBuffer size;
|
||||||
|
private final int maxSize;
|
||||||
|
private final MemoryPool memoryPool;
|
||||||
|
private int requestedBufferSize = -1;
|
||||||
|
private ByteBuffer buffer;
|
||||||
|
|
||||||
|
|
||||||
|
public NetworkReceive(String source, ByteBuffer buffer) {
|
||||||
|
this.source = source;
|
||||||
|
this.buffer = buffer;
|
||||||
|
this.size = null;
|
||||||
|
this.maxSize = TB_MAX_REQUESTED_BUFFER_SIZE;
|
||||||
|
this.memoryPool = MemoryPool.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NetworkReceive(String source) {
|
||||||
|
this.source = source;
|
||||||
|
this.size = ByteBuffer.allocate(4);
|
||||||
|
this.buffer = null;
|
||||||
|
this.maxSize = TB_MAX_REQUESTED_BUFFER_SIZE;
|
||||||
|
this.memoryPool = MemoryPool.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NetworkReceive(int maxSize, String source) {
|
||||||
|
this.source = source;
|
||||||
|
this.size = ByteBuffer.allocate(4);
|
||||||
|
this.buffer = null;
|
||||||
|
this.maxSize = getMaxSize(maxSize);
|
||||||
|
this.memoryPool = MemoryPool.NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NetworkReceive(int maxSize, String source, MemoryPool memoryPool) {
|
||||||
|
this.source = source;
|
||||||
|
this.size = ByteBuffer.allocate(4);
|
||||||
|
this.buffer = null;
|
||||||
|
this.maxSize = getMaxSize(maxSize);
|
||||||
|
this.memoryPool = memoryPool;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NetworkReceive() {
|
||||||
|
this(UNKNOWN_SOURCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String source() {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean complete() {
|
||||||
|
return !size.hasRemaining() && buffer != null && !buffer.hasRemaining();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long readFrom(ScatteringByteChannel channel) throws IOException {
|
||||||
|
int read = 0;
|
||||||
|
if (size.hasRemaining()) {
|
||||||
|
int bytesRead = channel.read(size);
|
||||||
|
if (bytesRead < 0)
|
||||||
|
throw new EOFException();
|
||||||
|
read += bytesRead;
|
||||||
|
if (!size.hasRemaining()) {
|
||||||
|
size.rewind();
|
||||||
|
int receiveSize = size.getInt();
|
||||||
|
if (receiveSize < 0)
|
||||||
|
throw new InvalidReceiveException("Invalid receive (size = " + receiveSize + ")");
|
||||||
|
if (maxSize != UNLIMITED && receiveSize > maxSize) {
|
||||||
|
var t = Thread.currentThread();
|
||||||
|
throw new ThingsboardKafkaClientError("Invalid receive (size = " + receiveSize + " larger than " + maxSize + ")");
|
||||||
|
}
|
||||||
|
requestedBufferSize = receiveSize; //may be 0 for some payloads (SASL)
|
||||||
|
if (receiveSize == 0) {
|
||||||
|
buffer = EMPTY_BUFFER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (buffer == null && requestedBufferSize != -1) { //we know the size we want but havent been able to allocate it yet
|
||||||
|
if (requestedBufferSize > TB_LOG_REQUESTED_BUFFER_SIZE) {
|
||||||
|
String stackTrace = Arrays.stream(Thread.currentThread().getStackTrace()).map(StackTraceElement::toString).collect(Collectors.joining("|"));
|
||||||
|
log.error("Allocating buffer of size {} for source {}", requestedBufferSize, source);
|
||||||
|
log.error("Stack Trace: {}", stackTrace);
|
||||||
|
}
|
||||||
|
buffer = memoryPool.tryAllocate(requestedBufferSize);
|
||||||
|
if (buffer == null)
|
||||||
|
log.trace("Broker low on memory - could not allocate buffer of size {} for source {}", requestedBufferSize, source);
|
||||||
|
}
|
||||||
|
if (buffer != null) {
|
||||||
|
int bytesRead = channel.read(buffer);
|
||||||
|
if (bytesRead < 0)
|
||||||
|
throw new EOFException();
|
||||||
|
read += bytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean requiredMemoryAmountKnown() {
|
||||||
|
return requestedBufferSize != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean memoryAllocated() {
|
||||||
|
return buffer != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
if (buffer != null && buffer != EMPTY_BUFFER) {
|
||||||
|
memoryPool.release(buffer);
|
||||||
|
buffer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteBuffer payload() {
|
||||||
|
return this.buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int bytesRead() {
|
||||||
|
if (buffer == null)
|
||||||
|
return size.position();
|
||||||
|
return buffer.position() + size.position();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the total size of the receive including payload and size buffer
|
||||||
|
* for use in metrics. This is consistent with {@link NetworkSend#size()}
|
||||||
|
*/
|
||||||
|
public int size() {
|
||||||
|
return payload().limit() + size.limit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getMaxSize(int maxSize) {
|
||||||
|
return maxSize == UNLIMITED ? TB_MAX_REQUESTED_BUFFER_SIZE : Math.min(maxSize, TB_MAX_REQUESTED_BUFFER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
/**
|
||||||
|
* Copyright © 2016-2022 The Thingsboard Authors
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.thingsboard.server.common.data.exception;
|
||||||
|
|
||||||
|
public class ThingsboardKafkaClientError extends Error {
|
||||||
|
|
||||||
|
public ThingsboardKafkaClientError(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
3
pom.xml
3
pom.xml
@ -97,6 +97,8 @@
|
|||||||
</sonar.exclusions>
|
</sonar.exclusions>
|
||||||
<elasticsearch.version>5.0.2</elasticsearch.version>
|
<elasticsearch.version>5.0.2</elasticsearch.version>
|
||||||
<delight-nashorn-sandbox.version>0.2.1</delight-nashorn-sandbox.version>
|
<delight-nashorn-sandbox.version>0.2.1</delight-nashorn-sandbox.version>
|
||||||
|
<!-- IMPORTANT: If you change the version of the kafka client, make sure to synchronize our overwritten implementation of the
|
||||||
|
org.apache.kafka.common.network.NetworkReceive class in the application module. It addresses the issue https://issues.apache.org/jira/browse/KAFKA-4090. -->
|
||||||
<kafka.version>2.8.0</kafka.version>
|
<kafka.version>2.8.0</kafka.version>
|
||||||
<bucket4j.version>4.1.1</bucket4j.version>
|
<bucket4j.version>4.1.1</bucket4j.version>
|
||||||
<fst.version>2.57</fst.version>
|
<fst.version>2.57</fst.version>
|
||||||
@ -807,6 +809,7 @@
|
|||||||
<exclude>**/*.patch</exclude>
|
<exclude>**/*.patch</exclude>
|
||||||
<exclude>**/apache/cassandra/io/**</exclude>
|
<exclude>**/apache/cassandra/io/**</exclude>
|
||||||
<exclude>.run/**</exclude>
|
<exclude>.run/**</exclude>
|
||||||
|
<exclude>**/NetworkReceive.java</exclude>
|
||||||
</excludes>
|
</excludes>
|
||||||
<mapping>
|
<mapping>
|
||||||
<proto>JAVADOC_STYLE</proto>
|
<proto>JAVADOC_STYLE</proto>
|
||||||
|
|||||||
@ -23,10 +23,12 @@ import org.apache.kafka.clients.producer.Producer;
|
|||||||
import org.apache.kafka.clients.producer.ProducerConfig;
|
import org.apache.kafka.clients.producer.ProducerConfig;
|
||||||
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.errors.TimeoutException;
|
||||||
import org.apache.kafka.common.serialization.StringSerializer;
|
import org.apache.kafka.common.serialization.StringSerializer;
|
||||||
import org.apache.kafka.common.header.Headers;
|
import org.apache.kafka.common.header.Headers;
|
||||||
import org.apache.kafka.common.header.internals.RecordHeader;
|
import org.apache.kafka.common.header.internals.RecordHeader;
|
||||||
import org.apache.kafka.common.header.internals.RecordHeaders;
|
import org.apache.kafka.common.header.internals.RecordHeaders;
|
||||||
|
import org.springframework.util.ReflectionUtils;
|
||||||
import org.thingsboard.rule.engine.api.RuleNode;
|
import org.thingsboard.rule.engine.api.RuleNode;
|
||||||
import org.thingsboard.rule.engine.api.TbContext;
|
import org.thingsboard.rule.engine.api.TbContext;
|
||||||
import org.thingsboard.rule.engine.api.TbNode;
|
import org.thingsboard.rule.engine.api.TbNode;
|
||||||
@ -34,10 +36,12 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration;
|
|||||||
import org.thingsboard.rule.engine.api.TbNodeException;
|
import org.thingsboard.rule.engine.api.TbNodeException;
|
||||||
import org.thingsboard.rule.engine.api.TbRelationTypes;
|
import org.thingsboard.rule.engine.api.TbRelationTypes;
|
||||||
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
|
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
|
||||||
|
import org.thingsboard.server.common.data.exception.ThingsboardKafkaClientError;
|
||||||
import org.thingsboard.server.common.data.plugin.ComponentType;
|
import org.thingsboard.server.common.data.plugin.ComponentType;
|
||||||
import org.thingsboard.server.common.msg.TbMsg;
|
import org.thingsboard.server.common.msg.TbMsg;
|
||||||
import org.thingsboard.server.common.msg.TbMsgMetaData;
|
import org.thingsboard.server.common.msg.TbMsgMetaData;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
@ -62,16 +66,23 @@ public class TbKafkaNode implements TbNode {
|
|||||||
private static final String TOPIC = "topic";
|
private static final String TOPIC = "topic";
|
||||||
private static final String ERROR = "error";
|
private static final String ERROR = "error";
|
||||||
public static final String TB_MSG_MD_PREFIX = "tb_msg_md_";
|
public static final String TB_MSG_MD_PREFIX = "tb_msg_md_";
|
||||||
|
private static final Field IO_THREAD_FIELD = ReflectionUtils.findField(KafkaProducer.class, "ioThread");
|
||||||
|
|
||||||
|
static {
|
||||||
|
IO_THREAD_FIELD.setAccessible(true);
|
||||||
|
}
|
||||||
|
|
||||||
private TbKafkaNodeConfiguration config;
|
private TbKafkaNodeConfiguration config;
|
||||||
private boolean addMetadataKeyValuesAsKafkaHeaders;
|
private boolean addMetadataKeyValuesAsKafkaHeaders;
|
||||||
private Charset toBytesCharset;
|
private Charset toBytesCharset;
|
||||||
|
|
||||||
private Producer<?, String> producer;
|
private Producer<?, String> producer;
|
||||||
|
private Throwable initError;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
|
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
|
||||||
this.config = TbNodeUtils.convert(configuration, TbKafkaNodeConfiguration.class);
|
this.config = TbNodeUtils.convert(configuration, TbKafkaNodeConfiguration.class);
|
||||||
|
this.initError = null;
|
||||||
Properties properties = new Properties();
|
Properties properties = new Properties();
|
||||||
properties.put(ProducerConfig.CLIENT_ID_CONFIG, "producer-tb-kafka-node-" + ctx.getSelfId().getId().toString() + "-" + ctx.getServiceId());
|
properties.put(ProducerConfig.CLIENT_ID_CONFIG, "producer-tb-kafka-node-" + ctx.getSelfId().getId().toString() + "-" + ctx.getServiceId());
|
||||||
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, config.getBootstrapServers());
|
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, config.getBootstrapServers());
|
||||||
@ -89,6 +100,13 @@ public class TbKafkaNode implements TbNode {
|
|||||||
toBytesCharset = config.getKafkaHeadersCharset() != null ? Charset.forName(config.getKafkaHeadersCharset()) : StandardCharsets.UTF_8;
|
toBytesCharset = config.getKafkaHeadersCharset() != null ? Charset.forName(config.getKafkaHeadersCharset()) : StandardCharsets.UTF_8;
|
||||||
try {
|
try {
|
||||||
this.producer = new KafkaProducer<>(properties);
|
this.producer = new KafkaProducer<>(properties);
|
||||||
|
Thread ioThread = (Thread) ReflectionUtils.getField(IO_THREAD_FIELD, producer);
|
||||||
|
ioThread.setUncaughtExceptionHandler((thread, throwable) -> {
|
||||||
|
if (throwable instanceof ThingsboardKafkaClientError) {
|
||||||
|
initError = throwable;
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new TbNodeException(e);
|
throw new TbNodeException(e);
|
||||||
}
|
}
|
||||||
@ -98,10 +116,14 @@ public class TbKafkaNode implements TbNode {
|
|||||||
public void onMsg(TbContext ctx, TbMsg msg) {
|
public void onMsg(TbContext ctx, TbMsg msg) {
|
||||||
String topic = TbNodeUtils.processPattern(config.getTopicPattern(), msg);
|
String topic = TbNodeUtils.processPattern(config.getTopicPattern(), msg);
|
||||||
try {
|
try {
|
||||||
|
if (initError != null) {
|
||||||
|
ctx.tellFailure(msg, new RuntimeException("Failed to initialize Kafka rule node producer: " + initError.getMessage()));
|
||||||
|
} else {
|
||||||
ctx.getExternalCallExecutor().executeAsync(() -> {
|
ctx.getExternalCallExecutor().executeAsync(() -> {
|
||||||
publish(ctx, msg, topic);
|
publish(ctx, msg, topic);
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
ctx.tellFailure(msg, e);
|
ctx.tellFailure(msg, e);
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user