Apache kafka 如何使Serdes与多步骤kafka流一起工作

Apache kafka 如何使Serdes与多步骤kafka流一起工作,apache-kafka,apache-kafka-streams,Apache Kafka,Apache Kafka Streams,我是卡夫卡的新手,我正在使用Twitter API作为数据源构建一个启动项目。我已经创建了一个Producer,它可以查询twitterapi,并使用键和值的字符串序列化程序将数据发送到我的kafka主题。我的Kafka流应用程序读取这些数据并进行字数统计,但也按推特的日期进行分组。这一部分是通过一个名为wordCounts的KTable来完成的,以利用其upsert功能。此KTable的结构为: 关键字:{word:exampleWord,日期:exampleDate},值:NumberOfO

我是卡夫卡的新手,我正在使用Twitter API作为数据源构建一个启动项目。我已经创建了一个Producer,它可以查询twitterapi,并使用键和值的字符串序列化程序将数据发送到我的kafka主题。我的Kafka流应用程序读取这些数据并进行字数统计,但也按推特的日期进行分组。这一部分是通过一个名为wordCounts的KTable来完成的,以利用其upsert功能。此KTable的结构为:

关键字:{word:exampleWord,日期:exampleDate},值:NumberOfOfOccurences

然后,我尝试将KTable流中的数据重新构造为平面结构,以便稍后将其发送到数据库。您可以在WordCountsStructuredKStream对象中看到这一点。这将重新构造数据,使其看起来像下面的结构。该值最初是一个JsonObject,但我将其转换为字符串以匹配我设置的SERDE

Key: null, Value: {word: exampleWord, date: exampleDate, Counts: numberOfOccurences}
然而,当我试图将此发送到我的第二个卡夫卡主题时,我得到以下错误

序列化程序(键: org.apache.kafka.common.serialization.StringSerializer/值: org.apache.kafka.common.serialization.StringSerializer)不可用 与实际键或值类型兼容(键类型: com.google.gson.JsonObject/value-type:com.google.gson.JsonObject)。 更改StreamConfig中的默认Serdes或提供正确的Serdes 通过方法参数

我对此感到困惑,因为我发送给主题的KStream类型是
。有人知道我该怎么解决这个问题吗

public class TwitterWordCounter {

private final JsonParser jsonParser = new JsonParser();

public Topology createTopology(){
    StreamsBuilder builder = new StreamsBuilder();


    KStream<String, String> textLines = builder.stream("test-topic2");
    KTable<JsonObject, Long> wordCounts = textLines
            //parse each tweet as a tweet object
            .mapValues(tweetString -> new Gson().fromJson(jsonParser.parse(tweetString).getAsJsonObject(), Tweet.class))
            //map each tweet object to a list of json objects, each of which containing a word from the tweet and the date of the tweet
            .flatMapValues(TwitterWordCounter::tweetWordDateMapper)
            //update the key so it matches the word-date combination so we can do a groupBy and count instances
            .selectKey((key, wordDate) -> wordDate)
            .groupByKey()
            .count(Materialized.as("Counts"));

    /*
        In order to structure the data so that it can be ingested into SQL, the value of each item in the stream must be straightforward: property, value
        so we have to:
         1. take the columns which include the dimensional data and put this into the value of the stream.
         2. lable the count with 'count' as the column name
     */
    KStream<String, String> wordCountsStructured = wordCounts.toStream()
            .map((key, value) -> new KeyValue<>(null, MapValuesToIncludeColumnData(key, value).toString()));

    KStream<String, String> wordCountsPeek = wordCountsStructured.peek(
            (key, value) -> System.out.println("key: " + key + "value:" + value)
    );

    wordCountsStructured.to("test-output2", Produced.with(Serdes.String(), Serdes.String()));

    return builder.build();
}

public static void main(String[] args) {
    Properties config = new Properties();
    config.put(StreamsConfig.APPLICATION_ID_CONFIG, "wordcount-application1111");
    config.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "myIPAddress");
    config.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
    config.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
    config.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());

    TwitterWordCounter wordCountApp = new TwitterWordCounter();

    KafkaStreams streams = new KafkaStreams(wordCountApp.createTopology(), config);
    streams.start();

    // shutdown hook to correctly close the streams application
    Runtime.getRuntime().addShutdownHook(new Thread(streams::close));

}

//this method is used for taking a tweet and transforming it to a representation of the words in it plus the date
public static List<JsonObject> tweetWordDateMapper(Tweet tweet) {
    try{

        List<String> words = Arrays.asList(tweet.tweetText.split("\\W+"));
        List<JsonObject> tweetsJson = new ArrayList<JsonObject>();
        for(String word: words) {
            JsonObject tweetJson = new JsonObject();
            tweetJson.add("date", new JsonPrimitive(tweet.formattedDate().toString()));
            tweetJson.add("word", new JsonPrimitive(word));
            tweetsJson.add(tweetJson);
        }

        return tweetsJson;
    }
    catch (Exception e) {
        System.out.println(e);
        System.out.println(tweet.serialize().toString());
        return new ArrayList<JsonObject>();
    }

}

public JsonObject MapValuesToIncludeColumnData(JsonObject key, Long countOfWord) {
    key.addProperty("count", countOfWord); //new JsonPrimitive(count));
    return key;
}
公共类TwitterWordCounter{
private final JsonParser JsonParser=new JsonParser();
公共拓扑createTopology(){
StreamsBuilder builder=新的StreamsBuilder();
KStream textLines=builder.stream(“test-topic2”);
KTable wordCounts=文本行
//将每个tweet解析为tweet对象
.mapValues(tweetString->new Gson().fromJson(jsonParser.parse(tweetString.getAsJsonObject(),Tweet.class))
//将每个tweet对象映射到一个json对象列表,每个json对象包含tweet中的一个单词和tweet的日期
.flatMapValues(TwitterWordCounter::tweetWordDateMapper)
//更新键,使其与单词日期组合匹配,以便我们可以执行groupBy和计数实例
.选择key((key,wordDate)->wordDate)
.groupByKey()
.count(具体化为“Counts”);
/*
为了构造数据以便将其摄取到SQL中,流中每个项的值都必须简单明了:property,value
因此,我们必须:
1.获取包含维度数据的列,并将其放入流的值中。
2.使用“count”作为列名标记计数
*/
KStream wordCountsStructured=wordCounts.toStream()
.map((key,value)->newkeyValue(null,MapValuesToIncludeColumnData(key,value).toString());
KStream wordCountsPeek=wordcountssstructured.peek(
(键,值)->System.out.println(“键:+key+”值:+value)
);
wordCountsStructured.to(“test-output2”,生成的.with(Serdes.String(),Serdes.String());
返回builder.build();
}
公共静态void main(字符串[]args){
属性配置=新属性();
config.put(StreamsConfig.APPLICATION_ID_config,“wordcount-application1111”);
config.put(StreamsConfig.BOOTSTRAP_SERVERS_config,“myIPAddress”);
config.put(ConsumerConfig.AUTO_OFFSET_RESET_config,“最早”);
config.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_config,Serdes.String().getClass());
config.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_config,Serdes.String().getClass());
TwitterWordCounter wordCountApp=新TwitterWordCounter();
KafkaStreams streams=新的KafkaStreams(wordCountApp.createTopology(),config);
streams.start();
//关闭钩子以正确关闭streams应用程序
Runtime.getRuntime().addShutdownHook(新线程(streams::close));
}
//此方法用于获取tweet,并将其转换为其中的单词加上日期的表示形式
公共静态列表tweetWordDateMapper(Tweet-Tweet){
试一试{
List words=Arrays.asList(tweet.tweetText.split(\\W+));
List tweetsJson=new ArrayList();
for(字符串字:字){
JsonObject tweetJson=新的JsonObject();
添加(“日期”,新的JsonPrimitive(tweet.formattedDate().toString());
添加(“word”,新的JsonPrimitive(word));
tweetsJson.add(tweetJson);
}
返回tweetsJson;
}
捕获(例外e){
系统输出打印ln(e);
System.out.println(tweet.serialize().toString());
返回新的ArrayList();
}
}
公共JsonObject MapValuesToIncludeColumnData(JsonObject键,长countOfWord){
key.addProperty(“count”,countOfWord);//新的JsonPrimitive(count));
返回键;
}

因为您在groupBy()之前执行密钥更改操作,所以它将创建一个重新分区主题,对于该主题,它将依赖默认密钥value serdes,您已将其设置为String Serde

您可以修改
groupBy()
groupBy(Grouped.with(StringSerde,JsonSerde)
的调用,这应该会有所帮助