Spring Cloud Stream Kafka应用程序无法使用正确的Avro架构生成消息

问题描述 投票:0回答:1

我有一个带有KStream的应用程序(spring-boot-shipping-service),它获取由外部生产者(spring-boot-order-service)生成的OrderCreatedEvent消息。该生产者使用以下模式:

为了创建-event.avsc

{
  "namespace" : "com.codependent.statetransfer.order",
  "type" : "record",
  "name" : "OrderCreatedEvent",
  "fields" : [
    {"name":"id","type":"int"},
    {"name":"productId","type":"int"},
    {"name":"customerId","type":"int"}
  ]
}

我的KStream<Int, OrderCreatedEvent>加入了KTable<Int, Customer>,并在订单主题上发布了一种新的消息:OrderShippedEvent。

订单出货,event.avsc

{
  "namespace" : "com.codependent.statetransfer.order",
  "type" : "record",
  "name" : "OrderShippedEvent",
  "fields" : [
    {"name":"id","type":"int"},
    {"name":"productId","type":"int"},
    {"name":"customerName","type":"string"},
    {"name":"customerAddress","type":"string"}
  ]
}

由于某种原因,新的OrderShippedEvent消息不是使用标头application/vnd.ordershippedevent.v1+avro生成的,而是使用application/vnd.ordercreatedevent.v1+avro生成的。

这是订单主题中的原始OrderCreatedEvent:

Key (4 bytes): +
  Value (4 bytes): V?
  Timestamp: 1555943926163
  Partition: 0
  Offset: 34
  Headers: contentType="application/vnd.ordercreatedevent.v1+avro",spring_json_header_types={"contentType":"java.lang.String"}

生成的OrderShippedEvent具有不正确的架构:

Key (4 bytes): +
  Value (26 bytes): V?
JamesHill Street
  Timestamp: 1555943926163
  Partition: 0
  Offset: 35
  Headers: contentType="application/vnd.ordercreatedevent.v1+avro",spring_json_header_types={"contentType":"java.lang.String"}

我检查了Confluent Schema Registry的内容,order-shipped-event.avsc架构在那里:

enter image description here

为什么不在生成的消息中使用正确的shema?

下面你可以看到这个例子的完整配置和代码,它也可以在Github上找到(https://github.com/codependent/event-carried-state-transfer/tree/avro

为了测试它,只需启动Confluent Platform(v5.2.1),spring-boot-customer-service,spring-boot-order-service,spring-boot-shipping-service并执行以下curl命令:

curl -X POST http://localhost:8080/customers -d '{"id":1,"name":"James","address":"Hill Street"}' -H "content-type: application/json"

curl -X POST http://localhost:8084/orders -H "content-type: application/json" -d '{"id":1,"productId":1001,"/customerId":1}'

application.yml

server:
  port: 8085

spring:
  application:
    name: spring-boot-shipping-service
  cloud:
    stream:
      kafka:
        streams:
          binder:
            configuration:
              default:
                key:
                  serde: org.apache.kafka.common.serialization.Serdes$IntegerSerde
      bindings:
        input:
          destination: customer
          contentType: application/*+avro
        order:
          destination: order
          contentType: application/*+avro
        output:
          destination: order
          contentType: application/*+avro
      schema-registry-client:
        endpoint: http://localhost:8081

ShippingKStreamProcessor

interface ShippingKStreamProcessor {

    @Input("input")
    fun input(): KStream<Int, Customer>

    @Input("order")
    fun order(): KStream<String, OrderCreatedEvent>

    @Output("output")
    fun output(): KStream<String, OrderShippedEvent>

ShippingKStreamConfiguration

    @StreamListener
    @SendTo("output")
    fun process(@Input("input") input: KStream<Int, Customer>, @Input("order") orderEvent: KStream<Int, OrderCreatedEvent>): KStream<Int, OrderShippedEvent> {

        val serdeConfig = mapOf(
                AbstractKafkaAvroSerDeConfig.SCHEMA_REGISTRY_URL_CONFIG to "http://localhost:8081")

        val intSerde = Serdes.IntegerSerde()
        val customerSerde = SpecificAvroSerde<Customer>()
        customerSerde.configure(serdeConfig, true)
        val orderCreatedSerde = SpecificAvroSerde<OrderCreatedEvent>()
        orderCreatedSerde.configure(serdeConfig, true)
        val orderShippedSerde = SpecificAvroSerde<OrderShippedEvent>()
        orderShippedSerde.configure(serdeConfig, true)


        val stateStore: Materialized<Int, Customer, KeyValueStore<Bytes, ByteArray>> =
                Materialized.`as`<Int, Customer, KeyValueStore<Bytes, ByteArray>>("customer-store")
                        .withKeySerde(intSerde)
                        .withValueSerde(customerSerde)

        val customerTable: KTable<Int, Customer> = input.groupByKey(Serialized.with(intSerde, customerSerde))
                .reduce({ _, y -> y }, stateStore)

        return (orderEvent.filter { _, value -> value is OrderCreatedEvent && value.id != 0 }
                .selectKey { _, value -> value.customerId } as KStream<Int, OrderCreatedEvent>)
                .join(customerTable, { orderIt, customer ->
                    OrderShippedEvent(orderIt.id, orderIt.productId, customer.name, customer.address)
                }, Joined.with(intSerde, orderCreatedSerde, customerSerde))
                .selectKey { _, value -> value.id }
    }

更新:我已经为org.springframework.messaging设置了跟踪日志记录级别,显然它看起来没问题:

2019-04-22 23:40:39.953 DEBUG 46039 --- [-StreamThread-1] o.s.web.client.RestTemplate              : HTTP GET http://localhost:8081/subjects/ordercreatedevent/versions/1
2019-04-22 23:40:39.971 DEBUG 46039 --- [-StreamThread-1] o.s.web.client.RestTemplate              : Accept=[application/json, application/*+json]
2019-04-22 23:40:39.972 DEBUG 46039 --- [-StreamThread-1] o.s.web.client.RestTemplate              : Writing [] as "application/vnd.schemaregistry.v1+json"
2019-04-22 23:40:39.984 DEBUG 46039 --- [-StreamThread-1] o.s.web.client.RestTemplate              : Response 200 OK
2019-04-22 23:40:39.985 DEBUG 46039 --- [-StreamThread-1] o.s.web.client.RestTemplate              : Reading to [java.util.Map<?, ?>]
2019-04-22 23:40:40.186  INFO 46039 --- [read-1-producer] org.apache.kafka.clients.Metadata        : Cluster ID: 5Sw6sBD0TFOaximF3Or-dQ
2019-04-22 23:40:40.318 DEBUG 46039 --- [-StreamThread-1] AvroSchemaRegistryClientMessageConverter : Obtaining schema for class class com.codependent.statetransfer.order.OrderShippedEvent
2019-04-22 23:40:40.318 DEBUG 46039 --- [-StreamThread-1] AvroSchemaRegistryClientMessageConverter : Avro type detected, using schema from object
2019-04-22 23:40:40.342 DEBUG 46039 --- [-StreamThread-1] o.s.web.client.RestTemplate              : HTTP POST http://localhost:8081/subjects/ordershippedevent/versions
2019-04-22 23:40:40.342 DEBUG 46039 --- [-StreamThread-1] o.s.web.client.RestTemplate              : Accept=[application/json, application/*+json]
2019-04-22 23:40:40.342 DEBUG 46039 --- [-StreamThread-1] o.s.web.client.RestTemplate              : Writing [{"schema":"{\"type\":\"record\",\"name\":\"OrderShippedEvent\",\"namespace\":\"com.codependent.statetransfer.order\",\"fields\":[{\"name\":\"id\",\"type\":\"int\"},{\"name\":\"productId\",\"type\":\"int\"},{\"name\":\"customerName\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"customerAddress\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}}]}"}] as "application/json"
2019-04-22 23:40:40.348 DEBUG 46039 --- [-StreamThread-1] o.s.web.client.RestTemplate              : Response 200 OK
2019-04-22 23:40:40.348 DEBUG 46039 --- [-StreamThread-1] o.s.web.client.RestTemplate              : Reading to [java.util.Map<?, ?>]
2019-04-22 23:40:40.349 DEBUG 46039 --- [-StreamThread-1] o.s.web.client.RestTemplate              : HTTP POST http://localhost:8081/subjects/ordershippedevent
2019-04-22 23:40:40.349 DEBUG 46039 --- [-StreamThread-1] o.s.web.client.RestTemplate              : Accept=[application/json, application/*+json]
2019-04-22 23:40:40.349 DEBUG 46039 --- [-StreamThread-1] o.s.web.client.RestTemplate              : Writing [{"schema":"{\"type\":\"record\",\"name\":\"OrderShippedEvent\",\"namespace\":\"com.codependent.statetransfer.order\",\"fields\":[{\"name\":\"id\",\"type\":\"int\"},{\"name\":\"productId\",\"type\":\"int\"},{\"name\":\"customerName\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}},{\"name\":\"customerAddress\",\"type\":{\"type\":\"string\",\"avro.java.string\":\"String\"}}]}"}] as "application/json"
2019-04-22 23:40:40.361 DEBUG 46039 --- [-StreamThread-1] o.s.web.client.RestTemplate              : Response 200 OK
2019-04-22 23:40:40.362 DEBUG 46039 --- [-StreamThread-1] o.s.web.client.RestTemplate              : Reading to [java.util.Map<?, ?>]
2019-04-22 23:40:40.362 DEBUG 46039 --- [-StreamThread-1] AvroSchemaRegistryClientMessageConverter : Finding correct DatumWriter for type com.codependent.statetransfer.order.OrderShippedEvent

为什么用不正确的内容类型标题写入消息呢?

更新2:

我一直在深入研究源代码并发现:

  1. KafkaStreamsMessageConversionDelegate正确转换并确定正确的标头值,如上面的日志中所示。
  2. 但是在serializeOnOutbound方法中,我们可以发现它仅返回Kafka API的有效负载,因此不考虑标头:
return
                    messageConverter.toMessage(message.getPayload(),
                            messageHeaders).getPayload();
  1. 在记录处理中向前移动org.apache.kafka.streams.processor.internals.SinkNode.process()访问上下文中存在的头文件,它不正确地包含application/vnd.ordercreatedevent.v1+avro而不是application/vnd.ordershippedevent.v1+avro(?):
collector.send(topic, key, value, context.headers(), timestamp, keySerializer, valSerializer, partitioner);

更新3:

重现步骤:

  1. 下载并启动Confluent 5.2.1 confluent start
  2. 启动应用程序spring-boot-order-service,spring-boot-customer-service,spring-boot-shipping-service
  3. 创建客户curl -X POST http://localhost:8080/customers -d '{"id":1,"name":"John","address":"Some Street"}' -H "content-type: application/json"
  4. 创建一个将与客户联系的订单:curl -X POST http://localhost:8084/orders -H "content-type: application/json" -d '{"id":1,"productId":1,"customerId":1}'
  5. ShippingKStreamConfiguration的process()将为客户和州商店(客户商店)创建KTable。此外,它将与客户KTable合并订单流,将OrderCreatedEvent转换为OrderShippedEvent。
  6. 您可以检查添加到订单主题的新创建的OrderShippedEvent消息是否具有不正确的标头。这可以在汇流控制中心(localhost:9092 -> topics -> order)或运行kafkacat中看到:
$> kafkacat -b localhost:9092 -t order -C \
  -f '\nKey (%K bytes): %k   
  Value (%S bytes): %s
  Timestamp: %T
  Partition: %p
  Offset: %o
  Headers: %h\n'

apache-kafka apache-kafka-streams spring-cloud-stream confluent confluent-schema-registry
1个回答
0
投票

@codependent这确实是一个我们需要在我们即将修复的活页夹中解决的问题。与此同时,作为一种解决方法,您可以让您的处理器不返回KStream,而是在方法本身中进行发送。你可以在当前返回的to(TopicNameExtractor)上调用KStreamTopicNameExtractor将允许您访问记录上下文,您可以使用该记录上下文手动设置内容类型。

© www.soinside.com 2019 - 2024. All rights reserved.