私はカフカのトピックからメッセージをデシリアライズに問題があります。メッセージは、ばねクラウドストリームとApacheアブロを使用して直列化されています。私は春のカフカを使用してそれらを読み、それらをdeserialiseしようとしています。私は両方の生産に春-クラウドを使用してメッセージを消費した場合、私は、メッセージの罰金をデシリアライズすることができます。私は春のカフカでそれらを消費して、デシリアライズしようとすると問題があります。
私は(も、両方の開発のためのバネブートスキーマレジストリ、および生産のコンフルエントスキーマ)スキーマレジストリを使用していますが、逆シリアル化の問題は、スキーマレジストリを呼び出すイベントの前に発生しているようです。
そのこの質問に関連するすべてのコードをポストするのは難しいので、私はgitのハブでのレポでそれを掲載している:https://github.com/robjwilkins/avro-example
私は、トピックを介して送信していたオブジェクトは単純なPOJOです。
@Data
public class Request {
private String message;
}
カフカのメッセージを生成したコードは次のようになります。
@EnableBinding(MessageChannels.class)
@Slf4j
@RequiredArgsConstructor
@RestController
public class ProducerController {
private final MessageChannels messageChannels;
@GetMapping("/produce")
public void produceMessage() {
Request request = new Request();
request.setMessage("hello world");
Message<Request> requestMessage = MessageBuilder.withPayload(request).build();
log.debug("sending message");
messageChannels.testRequest().send(requestMessage);
}
}
そして、application.yaml:
spring:
application.name: avro-producer
kafka:
bootstrap-servers: localhost:9092
consumer.group-id: avro-producer
cloud:
stream:
schema-registry-client.endpoint: http://localhost:8071
schema.avro.dynamic-schema-generation-enabled: true
kafka:
binder:
brokers: ${spring.kafka.bootstrap-servers}
bindings:
test-request:
destination: test-request
contentType: application/*+avro
それから私は、消費者を持っています:
@Slf4j
@Component
public class TopicListener {
@KafkaListener(topics = {"test-request"})
public void listenForMessage(ConsumerRecord<String, Request> consumerRecord) {
log.info("listenForMessage. got a message: {}", consumerRecord);
consumerRecord.headers().forEach(header -> log.info("header. key: {}, value: {}", header.key(), asString(header.value())));
}
private String asString(byte[] byteArray) {
return new String(byteArray, Charset.defaultCharset());
}
}
そして、消費プロジェクトはapplication.yaml設定があります。
spring:
application.name: avro-consumer
kafka:
bootstrap-servers: localhost:9092
consumer:
group-id: avro-consumer
value-deserializer: io.confluent.kafka.serializers.KafkaAvroDeserializer
# value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
properties:
schema.registry.url: http://localhost:8071
消費者がメッセージを受け取ると、それは例外が発生します:
2019-01-30 20:01:39.900 ERROR 30876 --- [ntainer#0-0-C-1] o.s.kafka.listener.LoggingErrorHandler : Error while processing: null
org.apache.kafka.common.errors.SerializationException: Error deserializing key/value for partition test-request-0 at offset 43. If needed, please seek past the record to continue consumption.
Caused by: org.apache.kafka.common.errors.SerializationException: Error deserializing Avro message for id -1
Caused by: org.apache.kafka.common.errors.SerializationException: Unknown magic byte!
私は、この例外がスローされたポイントにデシリアライズコードを強化しています
public abstract class AbstractKafkaAvroDeserializer extends AbstractKafkaAvroSerDe {
....
private ByteBuffer getByteBuffer(byte[] payload) {
ByteBuffer buffer = ByteBuffer.wrap(payload);
if (buffer.get() != 0) {
throw new SerializationException("Unknown magic byte!");
} else {
return buffer;
}
}
デシリアライザチェックがしかし、それはない、シリアル化されたオブジェクト(バイト配列)の内容をバイトおよびそれが0であると想定しているため、それが起こっています。したがって、オブジェクトをシリアライズ春-クラウドストリームMessageConverter私はオブジェクトをデシリアライズするために使用していますio.confluentオブジェクトと互換性があるかどうか理由私の質問。そして、彼らは互換性がない場合、私は何をしますか?
任意の助けに感謝します。
この問題の核心は、プロデューサーがカフカにメッセージを投稿するバネ - クラウド・ストリームを使用しているが、消費者は、ばねカカを使用することです。その理由は以下のとおりです。
- 既存のシステムは、すでに十分に確立されており、春・クラウド・ストリームを使用しています
- 新しい消費者は、トピック名のCSVリストにのみバインド、同じ方法を使用して複数のトピックを聞くことが必要です
- その内容をデータベースに一括して書き込むことができますので、個別にではなく、一度にメッセージのコレクションを消費する必要があります。
春クラウドストリームは、現在は消費者が複数のトピックにリスナーをバインドすることはできません。また、一度にメッセージのコレクションを消費する方法はありません(私は誤解だ場合を除きます)。
私はカフカにメッセージを発行するために、スプリング・クラウド・ストリームを使用するプロデューサーコードを変更する必要はありません解決策を発見しました。春クラウドストリームは使用していますMessageConverter
シリアライズとデシリアライゼーションを管理すること。AbstractAvroMessageConverter
方法がある:convertFromInternal
とconvertToInternal
バイト配列から/への変換を処理しています。私の解決策は、(拡張するクラスを作成し、このコードを拡張することでしたAvroSchemaRegistryClientMessageConverter
、私は私の春-カフカからアクセスできるインターフェイスと、春・クラウドストリーム機能の多くを再利用することができるように、) KafkaListener
。私は、変換を行うために、このクラスを使用するために私のTopicListenerを改正しました:
Aコンバータ:
@Component
@Slf4j
public class AvroKafkaMessageConverter extends AvroSchemaRegistryClientMessageConverter {
public AvroKafkaMessageConverter(SchemaRegistryClient schemaRegistryClient) {
super(schemaRegistryClient, new NoOpCacheManager());
}
public <T> T convertFromInternal(ConsumerRecord<?, ?> consumerRecord, Class<T> targetClass,
Object conversionHint) {
T result;
try {
byte[] payload = (byte[]) consumerRecord.value();
Map<String, String> headers = new HashMap<>();
consumerRecord.headers().forEach(header -> headers.put(header.key(), asString(header.value())));
MimeType mimeType = messageMimeType(conversionHint, headers);
if (mimeType == null) {
return null;
}
Schema writerSchema = resolveWriterSchemaForDeserialization(mimeType);
Schema readerSchema = resolveReaderSchemaForDeserialization(targetClass);
@SuppressWarnings("unchecked")
DatumReader<Object> reader = getDatumReader((Class<Object>) targetClass, readerSchema, writerSchema);
Decoder decoder = DecoderFactory.get().binaryDecoder(payload, null);
result = (T) reader.read(null, decoder);
}
catch (IOException e) {
throw new RuntimeException("Failed to read payload", e);
}
return result;
}
private MimeType messageMimeType(Object conversionHint, Map<String, String> headers) {
MimeType mimeType;
try {
String contentType = headers.get(MessageHeaders.CONTENT_TYPE);
log.debug("contentType: {}", contentType);
mimeType = MimeType.valueOf(contentType);
} catch (InvalidMimeTypeException e) {
log.error("Exception getting object MimeType from contentType header", e);
if (conversionHint instanceof MimeType) {
mimeType = (MimeType) conversionHint;
}
else {
return null;
}
}
return mimeType;
}
private String asString(byte[] byteArray) {
String theString = new String(byteArray, Charset.defaultCharset());
return theString.replace("\"", "");
}
}
改正TopicListener
:
@Slf4j
@Component
@RequiredArgsConstructor
public class TopicListener {
private final AvroKafkaMessageConverter messageConverter;
@KafkaListener(topics = {"test-request"})
public void listenForMessage(ConsumerRecord<?, ?> consumerRecord) {
log.info("listenForMessage. got a message: {}", consumerRecord);
Request request = messageConverter.convertFromInternal(
consumerRecord, Request.class, MimeType.valueOf("application/vnd.*+avr"));
log.info("request message: {}", request.getMessage());
}
}
このソリューションは、一度に1つのメッセージを消費しますが、簡単にメッセージのバッチを消費するように変更することができます。
完全なソリューションはここにある:https://github.com/robjwilkins/avro-example/tree/develop