Spring Cloud【有与无】【SKB2】Kafka Streams Binder

1.用法

要使用Kafka Streams binder程序,只需使用以下Maven依赖将其添加到Spring Cloud Stream应用程序中:

<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-stream-binder-kafka-streams</artifactId>
</dependency>

为Kafka Streams binder引导新项目的快速方法是使用Spring Initializr,然后选择“Spring Cloud Stream”和“ Spring for Kafka Streams”,如下所示

2.总览

Spring Cloud Stream包含一个为Apache Kafka Streams binder 明确设计的绑定器实现。 通过这种本机集成,Spring Cloud Stream“processor”应用程序可以在核心业务逻辑中直接使用Apache Kafka Streams API。

Kafka Streams binder 实现基于Spring为Apache Kafka项目提供的基础。

Kafka Streams binder为Kafka Streams中的三种主要类型(KStream,KTable和GlobalKTable)提供了绑定功能。

Kafka Streams应用程序通常遵循以下模型:从入站主题中读取记录,应用业务逻辑,然后将转换后的记录写入出站主题。 或者,也可以定义没有出站目的地的处理器应用程序。

在以下各节中,我们将详细研究Spring Cloud Stream与Kafka Streams的集成。

3.程式设计模型

当使用Kafka Streams binder程序提供的编程模型时,可以将高级Streams DSL以及高级和低级Processor-API的混合使用。 混合使用较高级别和较低级别的API时,通常可以通过在KStream上调用转换或处理API方法来实现。

3.1.Functional 风格

从Spring Cloud Stream 3.0.0开始,Kafka Streams binder程序允许使用Java 8中可用的功能编程样式来设计和开发应用程序。这意味着应用程序可以简洁地表示为类型为java.util.function.Function或java.util.function.Consumer的lambda表达式。

让我们举一个非常基本的例子。

@SpringBootApplication
public class SimpleConsumerApplication {

    @Bean
    public java.util.function.Consumer<KStream<Object, String>> process() {

        return input ->
                input.foreach((key, value) -> {
                    System.out.println("Key: " + key + " Value: " + value);
                });
    }
}

尽管很简单,但这是一个完整的独立的Spring Boot应用程序,它利用Kafka Streams进行流处理。 这是一个消费者应用程序,没有出站绑定,只有一个入站绑定。 该应用程序使用数据,并且仅将来自KStream键的信息和值记录在标准输出上。 该应用程序包含SpringBootApplication批注和一个标记为Bean的方法。 bean方法的类型为java.util.function.Consumer,使用KStream参数化。 然后在实现中,我们返回一个Consumer对象,该对象本质上是一个lambda表达式。 在lambda表达式中,提供了用于处理数据的代码。

在此应用程序中,存在单个输入绑定,其类型为KStream。 绑定器(binding )使用名称为process-in-0的名称为应用程序创建此绑定,即功能Bean名称的名称,后跟一个破折号(-),文本中的另一个破折号,然后是参数的序号位置。 您使用此绑定名称来设置其他属性,例如目标。 例如,spring.cloud.stream.bindings.process-in-0.destination = my-topic

如果未在绑定上设置destination属性,则会创建一个与绑定名称相同的主题(如果有足够的应用程序特权),或者该主题已经可用。

建立为uber-jar(例如kstream-consumer-app.jar)后,您可以像下面一样运行上述示例。

java -jar kstream-consumer-app.jar --spring.cloud.stream.bindings.process-in-0.destination=my-topic

这是另一个示例,其中它是具有输入和输出绑定的完整处理器。 这是经典的单词计数示例,其中应用程序从主题接收数据,然后在翻滚的时间窗口中计算每个单词的出现次数。

@SpringBootApplication
public class WordCountProcessorApplication {

  @Bean
  public Function<KStream<Object, String>, KStream<?, WordCount>> process() {

    return input -> input
                .flatMapValues(value -> Arrays.asList(value.toLowerCase().split("\\W+")))
                .map((key, value) -> new KeyValue<>(value, value))
                .groupByKey(Serialized.with(Serdes.String(), Serdes.String()))
                .windowedBy(TimeWindows.of(5000))
                .count(Materialized.as("word-counts-state-store"))
                .toStream()
                .map((key, value) -> new KeyValue<>(key.key(), new WordCount(key.key(), value,
                        new Date(key.window().start()), new Date(key.window().end()))));
  }

	public static void main(String[] args) {
		SpringApplication.run(WordCountProcessorApplication.class, args);
	}
}

同样,这是一个完整的Spring Boot应用程序。 与第一个应用程序的区别在于bean方法的类型为java.util.function.Function。 Function的第一个参数化类型用于输入KStream,第二个参数化类型用于输出。 在方法主体中,提供了一个Lambda表达式,其类型为Function,并且作为实现,给出了实际的业务逻辑。 与先前讨论的基于Consumer的应用程序类似,此处的输入绑定默认命名为process-in-0。 对于输出,绑定名称也会自动设置为process-out-0。

建立为uber-jar(例如wordcount-processor.jar)后,您可以像下面一样运行上述示例。

java -jar wordcount-processor.jar --spring.cloud.stream.bindings.process-in-0.destination=words --spring.cloud.stream.bindings.process-out-0.destination=counts

该应用程序将使用来自Kafka主题词的消息,并将计算的结果发布到输出主题计数。

Spring Cloud Stream将确保来自传入和传出主题的消息都自动绑定为KStream对象。 作为开发人员,您可以专注于代码的业务方面,即编写处理器中所需的逻辑。 框架会自动处理设置Kafka Streams基础结构所需的特定配置。

我们在上面看到的两个示例只有一个KStream输入绑定。 在这两种情况下,绑定都从单个主题接收记录。 如果要将多个主题复用到单个KStream绑定中,则可以在下面提供逗号分隔的Kafka主题作为目标。

spring.cloud.stream.bindings.process-in-0.destination=topic-1,topic-2,topic-3

此外,如果您想将主题与常规内容进行匹配,还可以提供主题模式作为目标。

spring.cloud.stream.bindings.process-in-0.destination=input.*

3.1.1.多个输入绑定 Multiple Input Bindings

许多非平凡的Kafka Streams应用程序经常通过多个绑定使用来自多个主题的数据。 例如,一个主题被消费为Kstream,另一主题被消费为KTable或GlobalKTable。 应用程序可能希望以表类型接收数据的原因有很多。 考虑一个用例,其中通过数据库中的更改数据捕获(CDC)机制填充了基础主题,或者该应用程序仅关心下游处理的最新更新。 如果应用程序指定需要将数据绑定为KTable或GlobalKTable,则Kafka Streams绑定程序将正确地将目标绑定到KTable或GlobalKTable,并使它们可用于应用程序进行操作。 我们将研究几种不同的场景,如何在Kafka Streams绑定程序中处理多个输入绑定。

3.1.1.1.BiFunction in Kafka Streams Binder

这是一个有两个输入和一个输出的示例。 在这种情况下,应用程序可以利用java.util.function.BiFunction。

@Bean
public BiFunction<KStream<String, Long>, KTable<String, String>, KStream<String, Long>> process() {
    return (userClicksStream, userRegionsTable) -> (userClicksStream
            .leftJoin(userRegionsTable, (clicks, region) -> new RegionWithClicks(region == null ?
                            "UNKNOWN" : region, clicks),
                    Joined.with(Serdes.String(), Serdes.Long(), null))
            .map((user, regionWithClicks) -> new KeyValue<>(regionWithClicks.getRegion(),
                    regionWithClicks.getClicks()))
            .groupByKey(Grouped.with(Serdes.String(), Serdes.Long()))
            .reduce(Long::sum)
            .toStream());
}

同样,这里的基本主题与前面的示例相同,但是这里有两个输入。 Java的BiFunction支持用于将输入绑定到所需的目的地。 绑定器为输入生成的默认绑定名称分别为process-in-0和process-in-1。 默认的输出绑定是process-out-0。 在此示例中,BiFunction的第一个参数被绑定为第一个输入的KStream,第二个参数被绑定为第二个输入的KTable。

3.1.1.2.BiConsumer in Kafka Streams Binder

如果有两个输入但没有输出,那么在这种情况下,我们可以使用java.util.function.BiConsumer,如下所示。

@Bean
public BiConsumer<KStream<String, Long>, KTable<String, String>> process() {
    return (userClicksStream, userRegionsTable) -> {}
}

3.1.1.3.超越两个输入

如果您有两个以上的输入怎么办? 在某些情况下,您需要两个以上的输入。 在这种情况下,binder 允许您链接部分功能。 在函数式编程术语中,此技术通常称为curring。 通过将功能编程支持作为Java 8的一部分添加,Java现在使您能够编写咖喱函数。 Spring Cloud Stream Kafka Streams绑定程序可以利用此功能来启用多个输入绑定。

我们来看一个例子

@Bean
public Function<KStream<Long, Order>,
        Function<GlobalKTable<Long, Customer>,
                Function<GlobalKTable<Long, Product>, KStream<Long, EnrichedOrder>>>> enrichOrder() {

    return orders -> (
              customers -> (
                    products -> (
                        orders.join(customers,
                            (orderId, order) -> order.getCustomerId(),
                                (order, customer) -> new CustomerOrder(customer, order))
                                .join(products,
                                        (orderId, customerOrder) -> customerOrder
                                                .productId(),
                                        (customerOrder, product) -> {
                                            EnrichedOrder enrichedOrder = new EnrichedOrder();
                                            enrichedOrder.setProduct(product);
                                            enrichedOrder.setCustomer(customerOrder.customer);
                                            enrichedOrder.setOrder(customerOrder.order);
                                            return enrichedOrder;
                                        })
                        )
                )
    );
}

让我们看看上面介绍的绑定模型的详细信息。在此模型中,入站有3个部分应用的函数。我们称它们为f(x),f(y)和f(z)。如果我们从真正的数学函数的意义上扩展这些函数,它将看起来像是:f(x) → (fy) → f(z) → KStream<Long, EnrichedOrder>。 x变量代表KStream<Long, Order>,y变量代表GlobalKTable <Long,Customer>,而z变量代表GlobalKTable <Long,Product>。第一个函数f(x)具有应用程序的第一个输入绑定(KStream <Long,Order>),其输出是函数(fy) 。函数(fy) 具有应用程序的第二个输入绑定(GlobalKTable <Long,Customer>),其输出是另一个函数 f(z) 。函数 f(z) 的输入是应用程序的第三个输入(GlobalKTable <Long,Product>),其输出是KStream <Long,EnrichedOrder>,它是应用程序的最终输出绑定。您可以在方法主体中使用三个部分函数(分别为KStream,GlobalKTable,GlobalKTable)的输入来将业务逻辑实现为lambda表达式的一部分。

输入绑定分别命名为richOrder-in-0,richenrichOrder-in-1和richOrder-in-2。 输出绑定命名为richOrder-out-0。

使用curried函数,您实际上可以有任意数量的输入。 但是,请记住,Java中的上述少量输入和部分应用的功能可能会导致代码无法读取。 因此,如果您的Kafka Streams应用程序需要的输入绑定数量要少得多,并且您想使用此功能模型,那么您可能需要重新考虑设计并适当地分解应用程序。

3.1.1.4.多个输出绑定

Kafka Streams允许将出站数据写入多个主题。 该功能在Kafka Streams中称为分支。 使用多个输出绑定时,需要提供KStream数组(KStream [])作为出站返回类型。

下面是一个例子:

@Bean
public Function<KStream<Object, String>, KStream<?, WordCount>[]> process() {

    Predicate<Object, WordCount> isEnglish = (k, v) -> v.word.equals("english");
    Predicate<Object, WordCount> isFrench = (k, v) -> v.word.equals("french");
    Predicate<Object, WordCount> isSpanish = (k, v) -> v.word.equals("spanish");

    return input -> input
            .flatMapValues(value -> Arrays.asList(value.toLowerCase().split("\\W+")))
            .groupBy((key, value) -> value)
            .windowedBy(TimeWindows.of(5000))
            .count(Materialized.as("WordCounts-branch"))
            .toStream()
            .map((key, value) -> new KeyValue<>(null, new WordCount(key.key(), value,
                    new Date(key.window().start()), new Date(key.window().end()))))
            .branch(isEnglish, isFrench, isSpanish);
}

编程模型保持不变,但是出站参数化类型为KStream []。 默认的输出绑定名称分别为process-out-0,process-out-1,process-out-2。 绑定程序生成三个输出绑定的原因是因为它检测返回的KStream数组的长度。

3.1.2.Kafka流的基于函数的编程样式摘要

总而言之,下表显示了可以在功能范例中使用的各种选项。

Number of InputsNumber of OutputsComponent to use

1

0

java.util.function.Consumer

2

0

java.util.function.BiConsumer

1

1..n

java.util.function.Function

2

1..n

java.util.function.BiFunction

>= 3

0..n

Use curried functions

  • 如果此表中有多个输出,则该类型将简单地变为KStream []

3.2.命令式编程模型。

尽管上面概述的功能编程模型是首选方法,但是如果愿意,您仍然可以使用基于经典StreamListener的方法。

这里有些例子

以下是使用StreamListener的单词计数示例的等效项

@SpringBootApplication
@EnableBinding(KafkaStreamsProcessor.class)
public class WordCountProcessorApplication {

    @StreamListener("input")
    @SendTo("output")
    public KStream<?, WordCount> process(KStream<?, String> input) {
        return input
                .flatMapValues(value -> Arrays.asList(value.toLowerCase().split("\\W+")))
                .groupBy((key, value) -> value)
                .windowedBy(TimeWindows.of(5000))
                .count(Materialized.as("WordCounts-multi"))
                .toStream()
                .map((key, value) -> new KeyValue<>(null, new WordCount(key.key(), value, new Date(key.window().start()), new Date(key.window().end()))));
    }

    public static void main(String[] args) {
        SpringApplication.run(WordCountProcessorApplication.class, args);
    }
}

如您所见,这有点冗长,因为您需要提供EnableBinding以及其他额外的批注(例如StreamListener和SendTo)以使其成为一个完整的应用程序。 EnableBinding是您在其中指定包含绑定的绑定接口的位置。 在这种情况下,我们使用的stock  KafkaStreamsProcessor绑定接口具有以下合同

public interface KafkaStreamsProcessor {

	@Input("input")
	KStream<?, ?> input();

	@Output("output")
	KStream<?, ?> output();

}

Binder将为输入KStream和输出KStream创建绑定,因为您正在使用包含这些声明的绑定接口。

除了在功能风格上提供的编程模型上的明显区别外,这里还需要提到的一件事是绑定名称是您在绑定接口中指定的名称。 例如,在上述应用程序中,由于我们使用的是KafkaStreamsProcessor,因此绑定名称是输入和输出。 绑定属性需要使用这些名称。 例如spring.cloud.stream.bindings.input.destination,spring.cloud.stream.bindings.output.destination等。请记住,这与功能样式从根本上不同,因为绑定器会为应用程序生成绑定名称。 这是因为应用程序在使用EnableBinding的功能模型中未提供任何绑定接口。

这是接收器的另一个示例,其中有两个输入。

@EnableBinding(KStreamKTableBinding.class)
.....
.....
@StreamListener
public void process(@Input("inputStream") KStream<String, PlayEvent> playEvents,
                    @Input("inputTable") KTable<Long, Song> songTable) {
                    ....
                    ....
}

interface KStreamKTableBinding {

    @Input("inputStream")
    KStream<?, ?> inputStream();

    @Input("inputTable")
    KTable<?, ?> inputTable();
}

以下是与我们在上面看到的基于BiFunction的处理器相同的StreamListener。

@EnableBinding(KStreamKTableBinding.class)
....
....

@StreamListener
@SendTo("output")
public KStream<String, Long> process(@Input("input") KStream<String, Long> userClicksStream,
                                     @Input("inputTable") KTable<String, String> userRegionsTable) {
....
....
}

interface KStreamKTableBinding extends KafkaStreamsProcessor {

    @Input("inputX")
    KTable<?, ?> inputTable();
}

最后,这是具有三个输入和curried 函数的应用程序的StreamListener等效项。

@EnableBinding(CustomGlobalKTableProcessor.class)
...
...
    @StreamListener
    @SendTo("output")
    public KStream<Long, EnrichedOrder> process(
            @Input("input-1") KStream<Long, Order> ordersStream,
            @Input("input-"2) GlobalKTable<Long, Customer> customers,
            @Input("input-3") GlobalKTable<Long, Product> products) {

        KStream<Long, CustomerOrder> customerOrdersStream = ordersStream.join(
                customers, (orderId, order) -> order.getCustomerId(),
                (order, customer) -> new CustomerOrder(customer, order));

        return customerOrdersStream.join(products,
                (orderId, customerOrder) -> customerOrder.productId(),
                (customerOrder, product) -> {
                    EnrichedOrder enrichedOrder = new EnrichedOrder();
                    enrichedOrder.setProduct(product);
                    enrichedOrder.setCustomer(customerOrder.customer);
                    enrichedOrder.setOrder(customerOrder.order);
                    return enrichedOrder;
                });
        }

    interface CustomGlobalKTableProcessor {

            @Input("input-1")
            KStream<?, ?> input1();

            @Input("input-2")
            GlobalKTable<?, ?> input2();

            @Input("input-3")
            GlobalKTable<?, ?> input3();

            @Output("output")
            KStream<?, ?> output();
    }

您可能会注意到上面的两个示例更加冗长,因为除了提供EnableBinding之外,您还需要编写自己的自定义绑定接口。 使用功能模型,可以避免所有这些仪式细节

在继续研究Kafka Streams绑定程序提供的通用编程模型之前,这里是多个输出绑定的StreamListener版本。

@EnableBinding(KStreamProcessorWithBranches.class)
public static class WordCountProcessorApplication {

    @Autowired
    private TimeWindows timeWindows;

    @StreamListener("input")
    @SendTo({"output1","output2","output3"})
    public KStream<?, WordCount>[] process(KStream<Object, String> input) {

			Predicate<Object, WordCount> isEnglish = (k, v) -> v.word.equals("english");
			Predicate<Object, WordCount> isFrench =  (k, v) -> v.word.equals("french");
			Predicate<Object, WordCount> isSpanish = (k, v) -> v.word.equals("spanish");

			return input
					.flatMapValues(value -> Arrays.asList(value.toLowerCase().split("\\W+")))
					.groupBy((key, value) -> value)
					.windowedBy(timeWindows)
					.count(Materialized.as("WordCounts-1"))
					.toStream()
					.map((key, value) -> new KeyValue<>(null, new WordCount(key.key(), value, new Date(key.window().start()), new Date(key.window().end()))))
					.branch(isEnglish, isFrench, isSpanish);
    }

    interface KStreamProcessorWithBranches {

    		@Input("input")
    		KStream<?, ?> input();

    		@Output("output1")
    		KStream<?, ?> output1();

    		@Output("output2")
    		KStream<?, ?> output2();

    		@Output("output3")
    		KStream<?, ?> output3();
    	}
}

总结一下,我们已经回顾了使用Kafka Streams绑定程序时的各种编程模型选择。

绑定器为输入上的KStream,KTable和GlobalKTable提供绑定功能。 KTable和GlobalKTable绑定仅在输入上可用。 活页夹支持KStream的输入和输出绑定。

Kafka Streams联编程序编程模型的最终结果是,该联编程序为您提供了使用功能齐全的编程模型或使用基于StreamListener的命令式方法的灵活性。

4.辅助编程模型

4.1.单个应用程序中有多个Kafka Streams处理器

Binder 允许在单个Spring Cloud Stream应用程序中具有多个Kafka Streams处理器。 您可以具有以下应用程序。

@Bean
public java.util.function.Function<KStream<Object, String>, KStream<Object, String>> process() {
   ...
}

@Bean
public java.util.function.Consumer<KStream<Object, String>> anotherProcess() {
  ...
}

@Bean
public java.util.function.BiFunction<KStream<Object, String>, KTable<Integer, String>, KStream<Object, String>> yetAnotherProcess() {
   ...
}

在这种情况下,binder 程序将创建3个具有不同应用程序ID的单独的Kafka Streams对象(更多信息请参见下文)。 但是,如果应用程序中有多个处理器,则必须告诉Spring Cloud Stream,哪些功能需要激活。 这是激活功能的方法。

spring.cloud.stream.function.definition: process;anotherProcess;yetAnotherProcess

如果您不想立即激活某些功能,则可以从列表中删除。

当您在同一应用程序中有一个单独的Kafka Streams处理器和其他类型的Function Bean,并通过不同的绑定程序处理时(例如,基于常规Kafka Message Channel绑定程序的功能Bean),情况也是如此。

4.2.Kafka Streams Application ID

Application id是您需要为Kafka Streams应用程序提供的必需属性。 Spring Cloud Stream Kafka Streams绑定程序允许您以多种方式配置此Application id。

如果应用程序中只有一个处理器或StreamListener,则可以使用以下属性在绑定器(binder )级别进行设置:

spring.cloud.stream.kafka.streams.binder.applicationId

为了方便起见,如果只有一个处理器,则还可以使用spring.application.name作为属性来委派application id。

如果应用程序中有多个Kafka Streams处理器,则需要为每个处理器设置application id。 对于功能模型,可以将其作为属性附加到每个功能。

例如 假设您具有以下功能。

@Bean
public java.util.function.Consumer<KStream<Object, String>> process() {
   ...
}

@Bean
public java.util.function.Consumer<KStream<Object, String>> anotherProcess() {
  ...
}

然后,您可以使用以下binder 级别属性为每个application id

spring.cloud.stream.kafka.streams.binder.functions.process.applicationId

spring.cloud.stream.kafka.streams.binder.functions.anotherProcess.applicationId

对于StreamListener,您需要在处理器的第一个输入绑定上进行设置。

例如 假设您有以下两个基于StreamListener的处理器。

@StreamListener
@SendTo("output")
public KStream<String, String> process(@Input("input") <KStream<Object, String>> input) {
   ...
}

@StreamListener
@SendTo("anotherOutput")
public KStream<String, String> anotherProcess(@Input("anotherInput") <KStream<Object, String>> input) {
   ...
}

然后,您必须使用以下绑定属性为此设置 application id

spring.cloud.stream.kafka.streams.bindings.input.consumer.applicationId

spring.cloud.stream.kafka.streams.bindings.anotherInput.consumer.applicationId

对于基于功能的模型,这种在绑定级别设置 application id的方法也将起作用。但是,如果您使用功能模型,则在绑定器(binding)级别设置每个功能非常容易。

对于生产部署,强烈建议通过配置显式指定application id。如果您要自动扩展应用程序,那么这将特别重要,在这种情况下,您需要确保使用相同的application ID部署每个实例。

如果应用程序不提供application ID,则在这种情况下,binder 将自动为您生成一个静态application id。这在开发方案中很方便,因为它避免了显式提供application id的需要。以这种方式生成的application ID在应用程序重新启动时将是静态的。在功能模型的情况下,生成的application ID将是功能Bean名称,后跟原义的applicationID,例如,如果process是功能Bean名称,则为process-applicationID。对于StreamListener,不是使用功能bean名称,而是使用包含的类名称,方法名称和文字applicationId代替生成的application ID。

4.2.1.设置Application ID的摘要

  • 默认情况下,binder 将为每个函数或StreamListener方法自动生成application ID。
  • 如果只有一个处理器,则可以使用spring.kafka.streams.applicationId,spring.application.name或spring.cloud.stream.kafka.streams.binder.applicationId。
  • 如果您有多个处理器,则可以使用属性spring.cloud.stream.kafka.streams.binder.functions.<function-name>.applicationId为每个功能设置application ID。 对于StreamListener,可以使用spring.cloud.stream.kafka.streams.bindings.input.applicationId来完成,假设输入了绑定名称是input。

4.3.用功能样式覆盖binder生成的默认绑定名称

默认情况下,绑定器(binder)在使用功能样式时使用上述策略生成绑定名称,例如,<function-bean-name>-<in>|<out>-[0..n]。 process-in-0,process-out-0等。如果要覆盖这些绑定名称,可以通过指定以下属性来实现。

spring.cloud.stream.function.bindings.<default binding name>。 默认绑定(binding)名称是binding生成的原始绑定名称。

例如 可以说,您具有此功能。

@Bean
public BiFunction<KStream<String, Long>, KTable<String, String>, KStream<String, Long>> process() {
...
}

Binder将生成名称为process-in-0,process-in-1和process-out-0的绑定。 现在,如果您想将它们完全更改为其他名称,也许是更多特定于域的绑定名称,则可以按照以下步骤进行操作。

springc.cloud.stream.function.bindings.process-in-0=users
springc.cloud.stream.function.bindings.process-in-0=regions

spring.cloud.stream.function.bindings.process-out-0=clicks

之后,必须在这些新的绑定名称上设置所有绑定级别属性。

请记住,在上述功能编程模型中,在大多数情况下,遵循默认绑定名称是有意义的。 您可能仍要进行此覆盖的唯一原因是,当您拥有大量的配置属性并且想要将绑定映射到更友好的域时。

4.4.设置引导服务器配置 Setting up bootstrap server configuration

运行Kafka Streams应用程序时,必须提供Kafka代理服务器信息。 如果您不提供此信息,则binder程序希望您在默认的localhost:9092上运行代理。 如果不是这种情况,那么您需要覆盖它。 有两种方法可以做到这一点。

  • 使用启动属性:spring.kafka.bootstrapServers
  • Binder级别属性:spring.cloud.stream.kafka.streams.binder.brokers

对于binder级别属性,如果使用常规Kafka  binder -spring.cloud.stream.kafka.binder.brokers 来提供的代理(broker)属性都没关系。 Kafka Streams binder程序将首先检查是否设置了Kafka Streams binder程序特定的代理(broker)属性(spring.cloud.stream.kafka.streams.binder.brokers),如果未找到,它将查找spring.cloud.stream.kafka.binder.brokers。

4.5.记录序列化和反序列化

Kafka Streams binder允许您以两种方式对记录进行序列化和反序列化。 一种是Kafka提供的本机序列化和反序列化功能,另一种是Spring Cloud Stream框架的消息转换功能。 让我们看一些细节。

4.5.1.入站反序列化 Inbound deserialization

密钥始终使用本机Serdes反序列化。

对于值,默认情况下,入站反序列化由Kafka本地执行。 请注意,这是对Kafka Streams binder 程序以前版本的默认行为的重大更改,在该版本中,反序列化是由框架完成的。

Kafka Streams binder 将通过查看java.util.function.Function | Consumer或StreamListener的类型签名来尝试推断匹配的Serde类型。 这是与Serdes匹配的顺序。

  • 如果应用程序提供了Serde类型的Bean,并且使用返回键或值类型的实际类型对返回类型进行了参数化,则它将使用该Serde进行入站反序列化。 例如 如果应用程序中具有以下内容,则绑定器检测到KStream的传入值类型与在Serde bean上参数化的类型匹配。 它将用于入站反序列化。
@Bean
public Serde<Foo() customSerde{
 ...
}

@Bean
public Function<KStream<String, Foo>, KStream<String, Foo>> process() {
}
  • 接下来,它查看类型,并查看它们是否是Kafka Streams公开的类型之一。 如果是这样,请使用它们。 这是binder将尝试从Kafka Streams匹配的Serde类型。
Integer, Long, Short, Double, Float, byte[], UUID and String.

如果Kafka Streams提供的所有Serdes都不匹配类型,那么它将使用Spring Kafka提供的JsonSerde。 在这种情况下,binder 程序假定类型是JSON友好的。 如果您有多个值对象作为输入,这将很有用,因为绑定器会在内部推断它们为正确的Java类型。 不过,在退回JsonSerde之前,绑定程序会检查Kafka Streams配置中默认的Serde设置,以查看它是否可以与传入的KStream的类型匹配。

如果以上策略均无效,则应用程序必须通过配置提供“ Serde”。 可以通过两种方式配置-绑定或默认。

首先,绑定器(binder )将查看是否在绑定级别提供Serde。 例如 如果您具有以下处理器,

@Bean
public BiFunction<KStream<CustomKey, AvroIn1>, KTable<CustomKey, AvroIn2>, KStream<CustomKey, AvroOutput>> process() {...}

何时,您可以使用以下方法提供绑定级别Serde:

spring.cloud.stream.kafka.streams.bindings.process-in-0.consumer.keySerde=CustomKeySerde
spring.cloud.stream.kafka.streams.bindings.process-in-0.consumer.valueSerde=io.confluent.kafka.streams.serdes.avro.SpecificAvroSerde

spring.cloud.stream.kafka.streams.bindings.process-in-1.consumer.keySerde=CustomKeySerde
spring.cloud.stream.kafka.streams.bindings.process-in-1.consumer.valueSerde=io.confluent.kafka.streams.serdes.avro.SpecificAvroSerde

如果您为每个输入绑定提供Serde作为更高版本,则将具有更高的优先级,并且绑定器将远离任何Serde推论。

如果希望将默认键/值Serdes用于入站反序列化,则可以在binder 级别执行此操作。

spring.cloud.stream.kafka.streams.binder.configuration.default.key.serde
spring.cloud.stream.kafka.streams.binder.configuration.default.value.serde

如果您不希望Kafka提供本机解码,则可以依靠Spring Cloud Stream提供的消息转换功能。 由于本机解码是默认设置,因此为了让Spring Cloud Stream反序列化入站值对象,您需要显式禁用本机解码。

例如 如果您具有与上述相同的BiFunction处理器,则spring.cloud.stream.bindings.process-in-0.consumer.nativeDecoding:false您需要分别为所有输入禁用本机解码。 否则,本机解码仍将应用于您未禁用的解码器。

默认情况下,Spring Cloud Stream将使用application / json作为内容类型,并使用适当的json消息转换器。 您可以通过使用以下属性和适当的MessageConverter bean来使用自定义消息转换器。

spring.cloud.stream.bindings.process-in-0.contentType

4.5.2.出站序列化 Outbound serialization

出站序列化几乎遵循与上述入站反序列化相同的规则。与入站反序列化一样,Spring Cloud Stream早期版本的一个主要变化是出站序列化由Kafka本地处理。在3.0版的binder之前,这是由框架本身完成的。

Kafka始终使用绑定程序推断出的匹配Serde序列化出站键。如果无法推断出密钥的类型,则需要使用配置进行指定。

使用用于入站反序列化的相同规则来推断值Serdes。首先,它匹配以查看出站类型是否来自应用程序中提供的bean。如果不是,它将检查与Kafka公开的Serde是否匹配,例如 Integer,Long,Short,Double,Float,byte [],UUID和String。如果这不起作用,则应使用Spring Kafka项目提供的JsonSerde,但请先查看默认的Serde配置,以查看是否存在匹配项。请记住,所有这些对于应用程序都是透明发生的。如果这些都不起作用,则用户必须提供Serde才能通过配置使用。

可以说您使用的是与上述相同的BiFunction处理器。然后,您可以按以下方式配置出站键/值Serdes。

spring.cloud.stream.kafka.streams.bindings.process-out-0.producer.keySerde=CustomKeySerde
spring.cloud.stream.kafka.streams.bindings.process-out-0.producer.valueSerde=io.confluent.kafka.streams.serdes.avro.SpecificAvroSerde

如果Serde推理失败,并且没有提供绑定级别Serdes,则绑定器退回到JsonSerde,但请查看默认Serdes以进行匹配。

默认序列号的配置方式与上述反序列化中所述的配置方式相同。

spring.cloud.stream.kafka.streams.binder.configuration.default.key.serde spring.cloud.stream.kafka.streams.binder.configuration.default.value.serde

如果您的应用程序使用分支功能并具有多个输出绑定,则必须为每个绑定配置这些输出绑定。再一次,如果binder能够推断Serde类型,则无需进行此配置。

如果您不希望Kafka提供本机编码,而是想使用框架提供的消息转换功能,则由于本机编码是默认设置,因此您需要显式禁用本机编码。例如如果您具有与上述相同的BiFunction处理器,则spring.cloud.stream.bindings.process-out-0.producer.nativeEncoding: false在分支的情况下,您需要为所有输出分别禁用本机编码。否则,原生编码仍将应用于您未禁用的编码。

当转换由Spring Cloud Stream完成时,默认情况下,它将使用application/json作为内容类型,并使用适当的json消息转换器。通过使用以下属性和相应的MessageConverter的bean,可以使用自定义消息转换器。

spring.cloud.stream.bindings.process-out-0.contentType

禁用本机编码/解码时,binder 程序将不会像本机Serdes那样进行任何推断。应用程序需要显式提供所有配置选项。因此,通常建议您在编写Spring Cloud Stream Kafka Streams应用程序时保留默认的反序列化选项,并坚持使用Kafka Streams提供的本机反序列化。您必须使用框架提供的消息转换功能的一种情况是上游生产者使用特定的序列化策略时。在这种情况下,由于本机机制可能会失败,因此您想使用匹配的反序列化策略。当依赖默认的Serde机制时,应用程序必须确保绑定器有前进的方向,并使用正确的Serde正确映射入站和出站,否则可能会失败。

值得一提的是,上述数据反序列化方法仅适用于处理器的边缘,即入站和出站。您的业​​务逻辑可能仍需要调用明确需要Serde对象的Kafka Streams API。这些仍然是应用程序的责任,必须由开发人员相应地处理。

4.6.错误处理 Error Handling

Apache Kafka Streams提供了本机处理反序列化错误引起的异常的功能。 有关此支持的详细信息,请参阅此。 Apache Kafka Streams开箱即用,提供了两种反序列化异常处理程序-LogAndContinueExceptionHandler和LogAndFailExceptionHandler。 顾名思义,前者将记录错误并继续处理下一条记录,而后者将记录错误并失败。 LogAndFailExceptionHandler是默认的反序列化异常处理程序。

4.6.1.在Binder中处理反序列化异常

Kafka Streams binder 程序允许使用以下属性指定上述反序列化异常处理程序。

spring.cloud.stream.kafka.streams.binder.deserializationExceptionHandler: logAndContinue

或者

spring.cloud.stream.kafka.streams.binder.deserializationExceptionHandler: logAndFail

除了上述两个反序列化异常处理程序之外,绑定程序还提供了第三个用于将错误记录(poison pills)发送到DLQ(死信队列)主题的代理。 这是启用此DLQ异常处理程序的方法。

spring.cloud.stream.kafka.streams.binder.deserializationExceptionHandler: sendToDlq

设置以上属性后,反序列化错误中的所有记录都会自动发送到DLQ主题。

您可以如下设置发布DLQ消息的主题名称。

spring.cloud.stream.kafka.streams.bindings.process-in-0.consumer.dlqName: custom-dlq (Change the binding name accordingly)

如果已设置,则错误记录将发送到主题custom-dlq。 如果未设置,则会创建一个名称为error.<input-topic-name>.<application-id>的DLQ主题。 例如,如果绑定的目标主题为inputTopic,而应用程序ID为process-applicationId,则默认的DLQ主题为error.inputTopic.process-applicationI。 如果您打算启用DLQ,则始终建议为每个输入绑定显式创建DLQ主题。

4.6.2.每个输入消费者绑定的DLQ

属性spring.cloud.stream.kafka.streams.binder.deserializationExceptionHandler适用于整个应用程序。 这意味着如果同一应用程序中有多个函数或StreamListener方法,则此属性将应用于所有这些函数。 但是,如果您在一个处理器中有多个处理器或多个输入绑定,则可以使用绑定程序针对每个输入使用者绑定提供的更细粒度的DLQ控件。

如果您具有以下处理器,

@Bean
public BiFunction<KStream<String, Long>, KTable<String, String>, KStream<String, Long>> process() {
...
}

并且您只想在第一个输入绑定上启用DLQ并在第二个绑定上启用logAndSkip,那么可以对消费者使用以下操作。

spring.cloud.stream.kafka.streams.bindings.process-in-0.consumer.deserializationExceptionHandler: sendToDlq 
spring.cloud.stream.kafka.streams.bindings.process-in-1.consumer.deserializationExceptionHandler: logAndSkip

这种设置反序列化异常处理程序的优先级高于在绑定器级别进行设置。

4.6.3.DLQ分区

默认情况下,记录将使用与原始记录相同的分区发布到Dead-Letter主题。 这意味着Dead-Letter主题必须具有至少与原始记录一样多的分区。

若要更改此行为,请将DlqPartitionFunction实现作为@Bean添加到应用程序上下文中。 只能存在一个这样的bean。 消费者组(在大多数情况下与application ID相同),失败的ConsumerRecord和异常提供了该功能。 例如,如果您始终要路由到分区0,则可以使用:

@Bean
public DlqPartitionFunction partitionFunction() {
    return (group, record, ex) -> 0;
}

如果将使用者绑定的dlqPartitions属性设置为1(并且绑定器的minPartitionCount等于1),则无需提供DlqPartitionFunction; 框架将始终使用分区0。如果将使用者绑定的dlqPartitions属性设置为大于1的值(或者绑定器的minPartitionCount大于1),则即使分区计数与 原始主题的。

在Kafka Streams binder中使用异常处理功能时,需要记住两件事。

  • 属性spring.cloud.stream.kafka.streams.binder.deserializationExceptionHandler适用于整个应用程序。 这意味着如果同一应用程序中有多个函数或StreamListener方法,则此属性将应用于所有这些函数。
  • 反序列化的异常处理与本机反序列化和框架提供的消息转换一致。

4.6.4.在Binder中处理生产异常

与上述对反序列化异常处理程序的支持不同,绑定程序不提供此类用于处理生产异常的一流机制。 但是,您仍然可以使用StreamsBuilderFactoryBean定制程序来配置生产异常处理程序,您可以在下面的后续部分中找到有关其的更多详细信息。

4.7.State Store

当使用高级DSL并进行适当的调用以触发状态存储时,状态存储由Kafka Streams自动创建。

如果要实现将传入的KTable绑定实现为命名状态存储,则可以使用以下策略来实现。

可以说您具有以下功能。

@Bean
public BiFunction<KStream<String, Long>, KTable<String, String>, KStream<String, Long>> process() {
   ...
}

然后通过设置以下属性,将传入的KTable数据具体化到命名状态存储中。

spring.cloud.stream.kafka.streams.bindings.process-in-1.consumer.materializedAs: incoming-store

您可以在应用程序中将定制状态存储定义为bean,并且绑定程序将检测到这些状态并将其添加到Kafka Streams构建器中。 特别是在使用处理器API时,您需要手动注册状态存储。 为此,您可以在应用程序中将StateStore创建为bean。 这是定义此类bean的示例。

@Bean
public StoreBuilder myStore() {
    return Stores.keyValueStoreBuilder(
            Stores.persistentKeyValueStore("my-store"), Serdes.Long(),
            Serdes.Long());
}

@Bean
public StoreBuilder otherStore() {
    return Stores.windowStoreBuilder(
            Stores.persistentWindowStore("other-store",
                    1L, 3, 3L, false), Serdes.Long(),
            Serdes.Long());
}

这些状态存储然后可以由应用程序直接访问。

在引导过程中,上述bean将由binder处理并传递到Streams构建器对象。

访问状态存储:

Processor<Object, Product>() {

    WindowStore<Object, String> state;

    @Override
    public void init(ProcessorContext processorContext) {
        state = (WindowStore)processorContext.getStateStore("mystate");
    }
    ...
}

在注册全局状态存储时,这将不起作用。 为了注册全局状态存储,请参阅以下有关自定义StreamsBuilderFactoryBean的部分

4.8.互动查询

Kafka Streams绑定程序API公开了一个名为InteractiveQueryService的类,以交互方式查询状态存储。 您可以在应用程序中将其作为Spring bean访问。 从您的应用程序访问此bean的一种简单方法是自动装配bean。

@Autowired
private InteractiveQueryService interactiveQueryService;

一旦获得了对该bean的访问权限,就可以查询您感兴趣的特定状态存储。 见下文。

ReadOnlyKeyValueStore<Object, Object> keyValueStore =
						interactiveQueryService.getQueryableStoreType("my-store", QueryableStoreTypes.keyValueStore());

在启动过程中,上述检索存储的方法调用可能会失败。 例如,它可能仍处于初始化状态存储的过程中。 在这种情况下,重试此操作将很有用。 Kafka Streams binder提供了一种简单的重试机制来解决此问题。

以下是可用于控制此重试的两个属性。

  • spring.cloud.stream.kafka.streams.binder.stateStoreRetry.maxAttempts-默认为1。
  • spring.cloud.stream.kafka.streams.binder.stateStoreRetry.backOffInterval-默认为1000毫秒。

如果有多个Kafka Streams应用程序实例正在运行,则在以交互方式查询它们之前,您需要确定哪个应用程序实例承载着您正在查询的特定键。 InteractiveQueryService API提供了用于标识主机信息的方法。

为了使它起作用,必须按如下所示配置属性application.server:

spring.cloud.stream.kafka.streams.binder.configuration.application.server: <server>:<port>

以下是一些代码片段:

org.apache.kafka.streams.state.HostInfo hostInfo = interactiveQueryService.getHostInfo("store-name",
						key, keySerializer);

if (interactiveQueryService.getCurrentHostInfo().equals(hostInfo)) {

    //query from the store that is locally available
}
else {
    //query from the remote host
}

4.9.健康指标

健康指示器需要依赖项spring-boot-starter-actuator。 对于maven使用:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Spring Cloud Stream Kafka Streams Binder提供了一个运行状况指示器来检查基础流线程的状态。 Spring Cloud Stream定义了一个属性management.health.binders.enabled来启用运行状况指示器。 请参阅 Spring Cloud Stream documentation

运行状况指示器为每个流线程的元数据提供以下详细信息:

  • 线程名称
  • 线程状态:CREATEDRUNNINGPARTITIONS_REVOKEDPARTITIONS_ASSIGNEDPENDING_SHUTDOWN,DEAD
  • 活动任务:task ID和分区partitions
  • 备用任务:task ID和分区partitions

默认情况下,仅全局状态可见(UP或DOWN)。 要显示详细信息,必须将property management.endpoint.health.show-details设置为ALWAYS或WHEN_AUTHORIZED。 有关运行状况信息的更多详细信息,请参见Spring Boot Actuator documentation

如果所有注册的Kafka线程都处于RUNNING状态,则运行状况指示器的状态为UP

由于Kafka Streams binder中有三个单独的binder(KStream,KTable和GlobalKTable),因此它们全部将报告运行状况。 启用显示细节时,报告的某些信息可能是多余的。

当同一应用程序中存在多个Kafka Streams处理器时,将为所有应用程序报告运行状况检查,并按Kafka Streams的application ID进行分类。

4.10.访问Kafka Streams指标

Spring Cloud Stream Kafka Streams绑定程序提供了一种基本机制,用于访问通过Micrometer MeterRegistry导出的Kafka Streams指标。通过KafkaStreams#metrics()可用的Kafka Streams度量标准由活页夹导出到此计量表注册表。导出的指标来自使用者,生产者,管理客户端和流本身。

binder导出的度量标准以度量标准组名称的格式导出,后跟一个点,然后是实际的度量标准名称。原始度量标准信息中的所有破折号都替换为点。

例如度量标准组consumer-metrics中的度量标准名称network-io-total可以在micrometer注册表中作为consumer.metrics.network.io.total使用。同样,流指标中的指标提交总数可作为stream.metrics.commit.total获得。

如果在同一应用程序中有多个Kafka Streams处理器,则度量标准名称将以Kafka Streams的相应application ID开头。在这种情况下,application ID将保持不变,即不会将任何破折号转换为点等。例如,如果第一个处理器的application ID是processor-1,则度量标准中的度量名称network-io-total可在micrometer注册表中以processor-1.consumer.metrics.network.io.total的形式获取组消费者度量。

您可以在应用程序中以编程方式访问Micrometer MeterRegistry,然后迭代可用的仪表,或使用Spring Boot执行器通过REST端点访问指标。通过引导执行器端点进行访问时,请确保将指标添加到属性management.endpoints.web.exposure.include。然后,您可以访问/acutator/metrics以获得所有可用度量的列表,然后可以通过相同的URI(/actuator/metrics/<metric-name>>)分别对其进行访问。

通过KafkaStreams#metrics()获得的信息级别度量标准之外的任何信息(例如调试级别度量标准),仍然只有在将metrics.recording.level设置为DEBUG后才能通过JMX使用。默认情况下,Kafka Streams将此级别设置为INFO。请参阅Kafka Streams文档中的此部分以获取更多详细信息。在将来的版本中,binder可能支持通过Micrometer导出这些DEBUG级别度量标准。

4.11.混合高级DSL和低级处理器API

Kafka Streams提供了两种API变体。 它具有更高级别的DSL之类的API,您可以在其中链接许多功能性程序员可能熟悉的各种操作。 Kafka Streams还允许访问低级处理器API。 尽管处理器API非常强大,并且能够在较低的级别上进行控制,但它本质上是必不可少的。 用于Spring Cloud Stream的Kafka Streams binder,允许您使用高级DSL或将DSL和处理器API混合使用。 混合使用这两种变体,可以为您提供很多选择来控制应用程序中的各种用例。 应用程序可以使用transform 或process 方法API调用来访问处理器API。

这是一种如何使用流程API在Spring Cloud Stream应用程序中结合DSL和 process API的方法。

@Bean
public Consumer<KStream<Object, String>> process() {
    return input ->
        input.process(() -> new Processor<Object, String>() {
            @Override
            @SuppressWarnings("unchecked")
            public void init(ProcessorContext context) {
               this.context = context;
            }

            @Override
            public void process(Object key, String value) {
                //business logic
            }

            @Override
            public void close() {

        });
}

这是使用transform API的示例。

@Bean
public Consumer<KStream<Object, String>> process() {
    return (input, a) ->
        input.transform(() -> new Transformer<Object, String, KeyValue<Object, String>>() {
            @Override
            public void init(ProcessorContext context) {

            }

            @Override
            public void close() {

            }

            @Override
            public KeyValue<Object, String> transform(Object key, String value) {
                // business logic - return transformed KStream;
            }
        });
}

流程API方法调用是终端操作,而转换API是非终端操作,它为您提供了潜在的转换KStream,您可以使用该KStream继续使用DSL或处理器API进行进一步处理。

4.12.出站分区支持

Kafka Streams处理器通常将处理后的输出发送到出站Kafka主题。 如果出站主题已分区,并且处理器需要将出站数据发送到特定分区,则应用程序需要提供StreamPartitioner类型的Bean。 有关更多详细信息,请参见StreamPartitioner 。 我们来看一些例子。

这是我们已经多次看到的同一处理器,

@Bean
public Function<KStream<Object, String>, KStream<?, WordCount>> process() {

    ...
}

这是输出绑定(binding)目标:

spring.cloud.stream.bindings.process-out-0.destination: outputTopic

如果主题outputTopic具有4个分区,则如果您不提供分区策略,则Kafka Streams将使用默认的分区策略,根据特定的用例,该策略可能不是您想要的结果。 假设您想将与spring匹配的所有键发送给分区0,将云匹配的键发送给分区1,将流发送给分区2,将所有其他键发送给分区3,这就是您在应用程序中需要做的事情。

@Bean
public StreamPartitioner<String, WordCount> streamPartitioner() {
    return (t, k, v, n) -> {
        if (k.equals("spring")) {
            return 0;
        }
        else if (k.equals("cloud")) {
            return 1;
        }
        else if (k.equals("stream")) {
            return 2;
        }
        else {
            return 3;
        }
    };
}

这是一个基本的实现,但是,您可以访问记录的键/值,主题名称和分区总数。 因此,您可以根据需要实施复杂的分区策略。

您还需要提供此bean名称以及应用程序配置。

spring.cloud.stream.kafka.streams.bindings.process-out-0.producer.streamPartitionerBeanName: streamPartitioner

应用程序中的每个输出主题都需要像这样单独配置。

4.13.StreamsBuilderFactoryBean customizer

通常需要自定义创建KafkaStreams对象的StreamsBuilderFactoryBean。 基于Spring Kafka提供的基础支持,binder程序允许您自定义StreamsBuilderFactoryBean。 您可以使用StreamsBuilderFactoryBeanCustomizer来定制StreamsBuilderFactoryBean本身。 然后,一旦通过此定制程序访问StreamsBuilderFactoryBean,就可以使用KafkaStreamsCustomzier定制相应的KafkaStreams。 这两个定制器都是Spring for Apache Kafka项目的一部分。

这是使用StreamsBuilderFactoryBeanCustomizer的示例

@Bean
public StreamsBuilderFactoryBeanCustomizer streamsBuilderFactoryBeanCustomizer() {
    return sfb -> sfb.setStateListener((newState, oldState) -> {
         //Do some action here!
    });
}

上面显示的是对自定义StreamsBuilderFactoryBean可以执行的操作的说明。 实际上,您可以从StreamsBuilderFactoryBean调用任何可用的变异操作来对其进行自定义。 在启动工厂bean之前,绑定程序将立即调用此定制程序。

获得对StreamsBuilderFactoryBean的访问权限后,您还可以自定义基础的KafkaStreams对象。 这是这样做的蓝图。

@Bean
public StreamsBuilderFactoryBeanCustomizer streamsBuilderFactoryBeanCustomizer() {
    return factoryBean -> {
        factoryBean.setKafkaStreamsCustomizer(new KafkaStreamsCustomizer() {
            @Override
            public void customize(KafkaStreams kafkaStreams) {
                kafkaStreams.setUncaughtExceptionHandler((t, e) -> {

                });
            }
        });
    };
}

在基础KafkaStreams启动之前,StreamsBuilderFactoryBeabn将立即调用KafkaStreamsCustomizer。

整个应用程序中只能有一个StreamsBuilderFactoryBeanCustomizer。 那么,我们如何考虑多个Kafka Streams处理器,因为每个处理器都由单个StreamsBuilderFactoryBean对象备份? 在那种情况下,如果针对那些处理器的自定义需要不同,则应用程序需要基于application ID应用一些过滤器。

例如

@Bean
public StreamsBuilderFactoryBeanCustomizer streamsBuilderFactoryBeanCustomizer() {

    return factoryBean -> {
        if (factoryBean.getStreamsConfiguration().getProperty(StreamsConfig.APPLICATION_ID_CONFIG)
                .equals("processor1-application-id")) {
            factoryBean.setKafkaStreamsCustomizer(new KafkaStreamsCustomizer() {
                @Override
                public void customize(KafkaStreams kafkaStreams) {
                    kafkaStreams.setUncaughtExceptionHandler((t, e) -> {

                    });
                }
            });
        }
    };

4.13.1.使用定制程序注册全局状态存储

如上所述,绑定器(binder)不提供将全局状态存储注册为功能的一流方法。 为此,您需要使用定制程序。 这是可以做到的。

@Bean
public StreamsBuilderFactoryBeanCustomizer customizer() {
    return fb -> {
        try {
            final StreamsBuilder streamsBuilder = fb.getObject();
            streamsBuilder.addGlobalStore(...);
        }
        catch (Exception e) {

        }
    };
}

同样,如果您有多个处理器,则希望通过使用上面概述的application id过滤掉其他StreamsBuilderFactoryBean对象,将全局状态存储附加到正确的StreamsBuilder。

4.13.2.使用定制程序注册生产异常处理程序

在错误处理部分,我们指出了活页夹未提供处理生产异常的一流方法。 尽管是这种情况,您仍然可以使用StreamsBuilderFacotryBean定制程序来注册生产异常处理程序。 见下文。

@Bean
public StreamsBuilderFactoryBeanCustomizer customizer() {
    return fb -> {
        fb.getStreamsConfiguration().put(StreamsConfig.DEFAULT_PRODUCTION_EXCEPTION_HANDLER_CLASS_CONFIG,
                            CustomProductionExceptionHandler.class);
    };
}

再一次,如果您有多个处理器,则可能需要针对正确的StreamsBuilderFactoryBean进行适当设置。 您也可以使用configuration属性添加此类生产异常处理程序(有关更多信息,请参见下文),但是如果您选择使用编程方法,则可以选择此选项。

4.14.时间戳提取器

Kafka Streams允许您基于各种时间戳记来控制消费者记录的处理。 默认情况下,Kafka Streams提取嵌入在使用者记录中的时间戳元数据。 您可以通过为每个输入绑定提供不同的TimestampExtractor实现来更改此默认行为。 以下是有关如何完成此操作的一些详细信息。

@Bean
public Function<KStream<Long, Order>,
        Function<KTable<Long, Customer>,
                Function<GlobalKTable<Long, Product>, KStream<Long, Order>>>> process() {
    return orderStream ->
            customers ->
                products -> orderStream;
}

@Bean
public TimestampExtractor timestampExtractor() {
    return new WallclockTimestampExtractor();
}

然后,您为每个消费者绑定设置上面的TimestampExtractor的bean名称。

spring.cloud.stream.kafka.streams.bindings.process-in-0.consumer.timestampExtractorBeanName=timestampExtractor
spring.cloud.stream.kafka.streams.bindings.process-in-1.consumer.timestampExtractorBeanName=timestampExtractor
spring.cloud.stream.kafka.streams.bindings.process-in-2.consumer.timestampExtractorBeanName=timestampExtractor

如果您跳过用于设置自定义时间戳提取程序的输入使用者绑定,则该消费者将使用默认设置。

4.15.基于Kafka Streams的Binder和常规Kafka Binder的多Binder

您可以拥有一个应用程序,其中既有基于常规Kafka Binde的function/consumer/supplier,又有基于Kafka Streams的处理器。 但是,您不能将它们两者混合在一个函数或使用者中。

这是一个示例,其中在同一应用程序中同时具有两个基于粘合剂的组件。

@Bean
public Function<String, String> process() {
    return s -> s;
}

@Bean
public Function<KStream<Object, String>, KStream<?, WordCount>> kstreamProcess() {

    return input -> input;
}

这是配置中的相关部分:

spring.cloud.stream.function.definition=process;kstreamProcess
spring.cloud.stream.bindings.process-in-0.destination=foo
spring.cloud.stream.bindings.process-out-0.destination=bar
spring.cloud.stream.bindings.kstreamProcess-in-0.destination=bar
spring.cloud.stream.bindings.kstreamProcess-out-0.destination=foobar

如果您具有与上述相同的应用程序,则事情会变得有些复杂,但是要处理两个不同的Kafka集群,例如,常规进程会同时作用于Kafka群集1和群集2(从群集1接收数据并发送到群集2),而Kafka Streams处理器也会作用于Kafka群集2。然后,您必须使用 Spring Cloud Stream提供的多绑定工具。

这是您在这种情况下可能会更改的配置。

# multi binder configuration
spring.cloud.stream.binders.kafka1.type: kafka
spring.cloud.stream.binders.kafka1.environment.spring.cloud.stream.kafka.streams.binder.brokers=${kafkaCluster-1} #Replace kafkaCluster-1 with the approprate IP of the cluster
spring.cloud.stream.binders.kafka2.type: kafka
spring.cloud.stream.binders.kafka2.environment.spring.cloud.stream.kafka.streams.binder.brokers=${kafkaCluster-2} #Replace kafkaCluster-2 with the approprate IP of the cluster
spring.cloud.stream.binders.kafka3.type: kstream
spring.cloud.stream.binders.kafka3.environment.spring.cloud.stream.kafka.streams.binder.brokers=${kafkaCluster-2} #Replace kafkaCluster-2 with the approprate IP of the cluster

spring.cloud.stream.function.definition=process;kstreamProcess

# From cluster 1 to cluster 2 with regular process function
spring.cloud.stream.bindings.process-in-0.destination=foo
spring.cloud.stream.bindings.process-in-0.binder=kafka1 # source from cluster 1
spring.cloud.stream.bindings.process-out-0.destination=bar
spring.cloud.stream.bindings.process-out-0.binder=kafka2 # send to cluster 2

# Kafka Streams processor on cluster 2
spring.cloud.stream.bindings.kstreamProcess-in-0.destination=bar
spring.cloud.stream.bindings.kstreamProcess-in-0.binder=kafka3
spring.cloud.stream.bindings.kstreamProcess-out-0.destination=foobar
spring.cloud.stream.bindings.kstreamProcess-out-0.binder=kafka3

请注意以上配置。 我们有两种binders,但总共有3种binders,第一种是基于群集1的常规Kafka binders(kafka1),然后是基于群集2的另一种Kafka binders(kafka2),最后是kstream一种binders(kafka3)。 该应用程序中的第一个处理器从kafka1接收数据并发布到kafka2,其中两个绑定器均基于常规Kafka绑定器,但集群不同。 第二个处理器是Kafka Streams处理器,它消耗来自kafka3的数据,该数据与kafka2属于同一群集,但绑定器类型不同。

由于Kafka Streams binders家族中提供了三种不同的binders类型-kstream,ktable和globalktable-如果您的应用程序具有基于这些binders中的任何一个的多个绑定,则需要明确提供该binders类型。

例如,如果您有以下处理器,

@Bean
public Function<KStream<Long, Order>,
        Function<KTable<Long, Customer>,
                Function<GlobalKTable<Long, Product>, KStream<Long, EnrichedOrder>>>> enrichOrder() {

    ...
}

然后,必须在以下情况下在多binder方案中进行配置。 请注意,仅当您有一个真正的binder夹方案,即在一个应用程序中有多个处理器处理多个群集时,才需要这样做。 在这种情况下,需要为绑定程序明确提供绑定,以区别于其他处理器的绑定程序类型和群集。

spring.cloud.stream.binders.kafka1.type: kstream
spring.cloud.stream.binders.kafka1.environment.spring.cloud.stream.kafka.streams.binder.brokers=${kafkaCluster-2}
spring.cloud.stream.binders.kafka2.type: ktable
spring.cloud.stream.binders.kafka2.environment.spring.cloud.stream.kafka.streams.binder.brokers=${kafkaCluster-2}
spring.cloud.stream.binders.kafka3.type: globalktable
spring.cloud.stream.binders.kafka3.environment.spring.cloud.stream.kafka.streams.binder.brokers=${kafkaCluster-2}

spring.cloud.stream.bindings.enrichOrder-in-0.binder=kafka1  #kstream
spring.cloud.stream.bindings.enrichOrder-in-1.binder=kafka2  #ktablr
spring.cloud.stream.bindings.enrichOrder-in-2.binder=kafka3  #globalktable
spring.cloud.stream.bindings.enrichOrder-out-0.binder=kafka1 #kstream

# rest of the configuration is omitted.

4.16.状态清理 State Cleanup

默认情况下,绑定停止时调用Kafkastreams.cleanup()方法。 请参阅Spring Kafka文档。 要修改此行为,只需向应用程序上下文中添加一个CleanupConfig @Bean(配置为在启动,停止或不启动时进行清除)。 该bean将被检测到并连接到工厂bean中。

4.17.Kafka Streams拓扑可视化

Kafka Streams binder提供以下执行器端点,用于检索拓扑描述,您可以使用它们使用外部工具可视化拓扑。

/actuator/kafkastreamstopology
/actuator/kafkastreamstopology/<applicaiton-id of the processor>

您需要包括Spring Boot中的执行器和Web依赖项才能访问这些端点。 此外,您还需要将kafkastreamstopology添加到management.endpoints.web.exposure.include属性。 默认情况下,kafkastreamstopology端点处于禁用状态。

4.18.配置选项

本节包含Kafka Streams绑定程序使用的配置选项。

有关与binder有关的常见配置选项和属性,请参阅核心文档

4.18.1.Kafka Streams Binder属性

以下属性在binder级别可用,并且必须带有前缀 spring.cloud.stream.kafka.streams.binder.

configuration

使用包含与Apache Kafka Streams API有关的属性的键/值对进行映射。 该属性必须以spring.cloud.stream.kafka.streams.binder.为前缀。以下是一些使用此属性的示例。

spring.cloud.stream.kafka.streams.binder.configuration.default.key.serde=org.apache.kafka.common.serialization.Serdes$StringSerde
spring.cloud.stream.kafka.streams.binder.configuration.default.value.serde=org.apache.kafka.common.serialization.Serdes$StringSerde
spring.cloud.stream.kafka.streams.binder.configuration.commit.interval.ms=1000

有关可能用于流配置的所有属性的更多信息,请参阅Apache Kafka Streams文档中的StreamsConfig JavaDocs。 您可以通过StreamsConfig设置的所有配置都可以通过此设置。 使用此属性时,由于这是binder级别的属性,因此适用于整个应用程序。 如果应用程序中有多个处理器,则所有处理器都将获取这些属性。 对于诸如application.id之类的属性,这将成为问题,因此您必须仔细检查如何使用此绑定程序级别的配置属性映射StreamsConfig中的属性。

functions.<function-bean-name>.applicationId

仅适用于功能样式处理器。 这可用于为应用程序中的每个功能设置application ID。 对于多种功能,这是设置application ID的便捷方法。

functions.<function-bean-name>.configuration

仅适用于功能样式处理器。 使用包含与Apache Kafka Streams API有关的属性的键/值对进行映射。 这类似于上面描述的绑定程序级别配置属性,但是此配置属性级别仅针对命名函数受到限制。 如果您有多个处理器,并且想基于特定功能限制对配置的访问,则可能要使用它。 此处可以使用所有StreamsConfig属性。

brokers

Broker URL

默认值: localhost

zkNodes

Zookeeper URL

默认值: localhost

deserializationExceptionHandler

反序列化错误处理程序类型。 此处理程序应用于绑定程序级别,因此将其应用于应用程序中的所有输入绑定。 有一种在消费者绑定级别以更细粒度的方式控制它的方法。 可能的值为 logAndContinue,logAndFail或sendToDlq

默认值: logAndFail

applicationId

在binder程序级别方便地全局设置Kafka Streams应用程序的application.id的方法。 如果应用程序包含多个函数或StreamListener方法,则应将application id 设置为不同。 参见上文,其中详细讨论了设置应用程序ID的内容。

默认值:application将生成一个静态application ID。 有关更多详细信息,请参见application ID部分。

stateStoreRetry.maxAttempts

Max尝试尝试连接到状态存储。

默认值:1

stateStoreRetry.backoffPeriod

尝试重试时连接到状态存储的退避期。

默认值:1000毫秒

4.18.2.Kafka Streams Producer属性

以下属性仅适用于Kafka Streams生产者,并且必须以spring.cloud.stream.kafka.streams.bindings.<binding name>.producer.为前缀。 为了方便起见,如果存在多个输出绑定,并且它们都需要一个公共值,则可以使用前缀spring.cloud.stream.kafka.streams.default.producer.进行配置

keySerde

要使用的密钥序列

默认值:请参见上面有关消息反序列化的讨论

valueSerde

要使用的值序列

默认值:请参见上面有关消息反序列化的讨论

useNativeEncoding

标记以启用/禁用本机编码

默认值: true

 

streamPartitionerBeanName:使用者将使用的自定义出站分区器bean名称。 应用程序可以将自定义StreamPartitioner作为Spring bean提供,并且可以将该bean的名称提供给生产者以供使用,而不是使用默认的bean。

+默认值:请参阅上面有关出站分区支持的讨论。

4.18.3.Kafka Streams Consumer属性

以下属性可用于Kafka Streams消费者,并且必须以spring.cloud.stream.kafka.streams.bindings.<binding-name>.consumer.为前缀。 为了方便起见,如果存在多个输入绑定,并且它们都需要一个公共值,则可以使用前缀spring.cloud.stream.kafka.streams.default.consumer.进行配置。

 

applicationId

设置每个输入绑定的application.id。 这仅对于基于StreamListener的处理器是首选,对于基于函数的处理器,请参见上面概述的其他方法。

默认值:请参见上文。

keySerde

要使用的密钥序列

默认值:请参见上面有关消息反序列化的讨论

valueSerde

要使用的值序列

默认值:请参见上面有关消息反序列化的讨论

materializedAs

状态存储在使用传入的KTable类型时实现

默认值:无

useNativeDecoding

标志以 enable/disable 本机解码

默认值:true

dlqName

DLQ主题名称。

默认值:有关错误处理和DLQ的讨论,请参见上文

startOffset

如果没有要使用的已提交偏移,则从其开始的偏移。当消费者第一次从某个主题消费时,通常会使用此功能。 Kafka Streams 使用earliest 默认策略,而binder使用相同的默认策略。 可以使用此属性将其覆盖为latest 。

默认值:earliest 

注意:在消费者上使用resetOffsets对Kafka Streams binder程序没有任何影响。 与基于消息通道的binder不同,Kafka Streams binder不会按需开始或结束

deserializationExceptionHandler

反序列化错误处理程序类型。 该处理程序是针对每个消费者绑定应用的,与之前描述的绑定器(binding)级别属性相反。 可能的值为-logAndContinue,logAndFail或sendToDlq

默认值:logAndFail

timestampExtractorBeanName

使用者要使用的特定时间戳提取器bean名称。 应用程序可以将TimestampExtractor作为Spring Bean提供,并且可以将此Bean的名称提供给消费者以供使用,而不是使用默认名称。

默认值:请参阅上面有关时间戳提取器的讨论。

4.18.4.关于并发的特别说明

在Kafka Streams中,您可以使用num.stream.threads属性控制处理器可以创建的线程数。为此,您可以使用上面在binder,functions,producer或consumer级别下描述的各种配置选项。您也可以为此使用核心Spring Cloud Stream提供的并发属性。使用此功能时,需要在消费者上使用它。如果函数或StreamListener中有多个输入绑定,请在第一个输入绑定上进行设置。例如设置spring.cloud.stream.bindings.process-in-0.consumer.concurrency时,绑定程序会将其转换为num.stream.threads。如果您有多个处理器,并且一个处理器定义了绑定(binding)级别并发,而没有其他处理器,则那些没有绑定(binding)级别并发的处理器将默认回到通过spring.cloud.stream.binder.configuration.num.stream.threads指定的绑定器(binding)范围属性。如果此绑定器配置不可用,则应用程序将使用Kafka Streams设置的默认设置。

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: skb recycle是指射频技术中的信道回收技术,主要用于无线通信系统中的信号处理和网络优化。在无线通信系统中,信道回收技术能够提高系统的频谱效率和用户的数据传输速率。 skb recycle的原理是通过对已经传输的信号进行回收和重新利用,以提高频谱利用率。具体而言,它能够捕获已经传输过的信号,对这些信号进行补偿和修正,然后再次发送给其他需要使用该信号的用户。因此,信道回收技术能够显著减少频谱资源的浪费,并且在减少信号干扰的同时提高了系统的容量。 skb recycle技术在无线通信系统中的应用广泛。例如,在蜂窝网络中,当用户在移动中切换到新的基站时,之前所使用的频率资源可以被回收利用。此外,信道回收技术还可以被应用于多天线系统中,通过捕获和再利用多径信号,提高系统的容量和覆盖范围。 总之,skb recycle是一种利用信道回收技术提高无线通信系统频谱效率和用户数据传输速率的方法。它可以帮助无线通信系统更高效地利用频谱资源,提升用户体验和网络性能。 ### 回答2: skb recycle是指对skb(Socket Buffer)进行回收利用的过程。 在操作系统中,skb是一种用于在内核与网络协议栈之间传输数据的数据结构。它包含有关数据包的各种信息,例如发送方和接收方的IP地址、协议类型、数据长度等。 skb recycle是一种优化技术,旨在提高网络数据传输的效率和性能。在传输数据时,操作系统会创建多个skb对象,用于存储不同的数据包。一旦数据包传输完成后,这些被使用过的skb对象就可以进行回收利用,以避免频繁地创建和销毁对象,从而减少系统开销。 skb recycle的核心思想是在创建skb对象时,使用一个空闲列表来记录可用的回收对象。当需要新的skb对象时,首先从空闲列表中获取,如果列表为空,则会创建新的对象。而当数据包完成传输后,就将该skb对象重新加入到空闲列表中,以供下一次使用。 通过使用skb recycle技术,可以显著提高网络性能和吞吐量。因为减少了频繁地创建和销毁对象,系统的开销大大降低。同时,有效地回收利用已用过的skb对象,也减少了内存资源的浪费。 总之,skb recycle是一种对skb对象进行回收利用的技术,可以提高网络数据传输的效率和性能,减少系统开销和内存浪费。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值