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 Inputs Number of Outputs Component 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設置的默認設置。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章