背景

上一篇我们介绍了Kafka Streams中的消息过滤操作filter,今天我们展示一个对消息进行转换Key的操作,依然是结合一个具体的实例展开介绍。所谓转换Key是指对流处理中每条消息的Key进行变换操作,以方便后面进行各种groupByKey操作。

演示功能说明

本篇演示selectKey的用法,即根据指定的Key选择逻辑对每条消息的Key进行变换操作。今天使用的输入topic消息格式如下:

ID | First Name | Last Name | Phone Number

比如这样:

3 | San | Zhang | 13910010000

我们的目标是提取出手机号的号段(比如1391)作为消息的新Key,然后输出到一个新的Kafka主题上。

初始化项目

创建项目目录:

mkdir selectKey-streams
cd selectKey-streams/

配置项目

在selectKey-streams目录下创建build.gradle文件,内容如下:

buildscript {

repositories {

jcenter()

}

dependencies {

classpath 'com.github.jengelman.gradle.plugins:shadow:4.0.2'

}

}

plugins {

id 'java'

}

apply plugin: 'com.github.johnrengelman.shadow'

repositories {

mavenCentral()

jcenter()

maven {

url 'http://packages.confluent.io/maven'

}

}

group 'huxihx.kafkastreams'

sourceCompatibility = 1.8

targetCompatibility = '1.8'

version = '0.0.1'

dependencies {

implementation 'org.slf4j:slf4j-simple:1.7.26'

implementation 'org.apache.kafka:kafka-streams:2.3.0'

testCompile group: 'junit', name: 'junit', version: '4.12'

}

jar {

manifest {

attributes(

'Class-Path': configurations.compile.collect { it.getName() }.join(' '),

'Main-Class': 'huxihx.kafkastreams.SelectKeyStreamsApp'

)

}

}

shadowJar {

archiveName = "kstreams-transform-standalone-${version}.${extension}"

}

然后执行下列命令下载Gradle的wrapper套件:

gradle wrapper

之后在selectKey-streams目录下创建一个名为configuration的文件夹用于保存我们的参数配置文件:

mkdir configuration

创建一个名为dev.properties的文件:

application.id=selectKey-app
bootstrap.servers=localhost:9092

input.topic.name=nonkeyed-records
input.topic.partitions=1
input.topic.replication.factor=1

output.topic.name=keyed-records
output.topic.partitions=1
output.topic.replication.factor=1

开发主流程

创建src/main/java/huxihx/kafkastreams目录,并在该目录下创建SelectKeyStreamsApp.java文件:

package huxihx.kafkastreams;

import org.apache.kafka.clients.admin.AdminClient;
import org.apache.kafka.clients.admin.NewTopic;
import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.streams.KafkaStreams;
import org.apache.kafka.streams.StreamsBuilder;
import org.apache.kafka.streams.StreamsConfig;
import org.apache.kafka.streams.Topology;
import org.apache.kafka.streams.kstream.Consumed; import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.CountDownLatch; public class SelectKeyStreamsApp { public static void main(String[] args) throws Exception {
if (args.length < 1) {
throw new IllegalArgumentException("Environment configuration file must be specified.");
} SelectKeyStreamsApp app = new SelectKeyStreamsApp();
Properties envProps = app.loadEnvProperties(args[0]);
Properties streamProps = app.buildStreamsProperties(envProps); app.preCreateTopics(envProps); Topology topology = app.buildTopology(envProps); final KafkaStreams streams = new KafkaStreams(topology, streamProps);
final CountDownLatch latch = new CountDownLatch(1); Runtime.getRuntime().addShutdownHook(new Thread("streams-jvm-shutdown-hook") {
@Override
public void run() {
streams.close();
latch.countDown();
}
}); try {
streams.start();
latch.await();
} catch (Exception e) {
System.exit(1);
}
System.exit(0);
} private Topology buildTopology(Properties envProps) {
final StreamsBuilder builder = new StreamsBuilder(); final String inputTopic = envProps.getProperty("input.topic.name");
final String outputTopic = envProps.getProperty("output.topic.name"); builder.stream(inputTopic, Consumed.with(Serdes.String(), Serdes.String()))
.selectKey((noKey, value) -> {
String[] fields = value.split("\\|");
return fields[fields.length - 1].trim().substring(0, 4);
})
.to(outputTopic);
return builder.build();
} private Properties buildStreamsProperties(Properties envProps) {
Properties props = new Properties();
props.put(StreamsConfig.APPLICATION_ID_CONFIG, envProps.getProperty("application.id"));
props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, envProps.getProperty("bootstrap.servers"));
props.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
props.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());
return props;
} private void preCreateTopics(Properties envProps) throws Exception {
Map<String, Object> config = new HashMap<>();
config.put("bootstrap.servers", envProps.getProperty("bootstrap.servers"));
try (AdminClient client = AdminClient.create(config)) {
Set<String> existingTopics = client.listTopics().names().get(); List<NewTopic> topics = new ArrayList<>();
String inputTopic = envProps.getProperty("input.topic.name");
if (!existingTopics.contains(inputTopic)) {
topics.add(new NewTopic(inputTopic,
Integer.parseInt(envProps.getProperty("input.topic.partitions")),
Short.parseShort(envProps.getProperty("input.topic.replication.factor")))); } String outputTopic = envProps.getProperty("output.topic.name");
if (!existingTopics.contains(outputTopic)) {
topics.add(new NewTopic(outputTopic,
Integer.parseInt(envProps.getProperty("output.topic.partitions")),
Short.parseShort(envProps.getProperty("output.topic.replication.factor"))));
} client.createTopics(topics);
}
} private Properties loadEnvProperties(String filePath) throws IOException {
Properties envProps = new Properties();
try (FileInputStream input = new FileInputStream(filePath)) {
envProps.load(input);
}
return envProps;
}
}

测试

首先我们运行下列命令构建项目:

./gradlew clean shadowJar

然后启动Kafka集群,之后运行Kafka Streams应用:

java -jar build/libs/kstreams-transform-standalone-0.0.1.jar configuration/dev.properties

之后启动一个Console Consumer去测试输出topic的Key值是否真的设置了我们的手机号段:

bin/kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic keyed-records --property print.key=true

最后启动一个Console Producer按照规定的事件格式去生成对应的消息:

bin/kafka-console-producer.sh --broker-list localhost:9092 --topic nonkeyed-records

>1 | Wang | Wu | 18601234567
>2 | Li | Si | 13901234567
>3 | Zhang | San | 13921234567
>4 | Alice | Joe | 13901234568

如果一切正常,你应该可以在Console Consumer的输出中看到:

1860 1 | Wang | Wu | 18601234567
1390 2 | Li | Si | 13901234567
1390 3 | Zhang | San | 13921234567
1390 4 | Alice | Joe | 13901234568

前面的4位数字就是我们提取的手机号段信息。后面你可以使用这个Key进行各种groupBy操作,比如统计各个号段的人数等。

总结

很多场合下我们都需要修改原始消息中的Key值,方便后续进行统计操作。本例演示了如何使用selectKey函数方便地对消息Key进行变换。

Kafka Streams开发入门(3)的更多相关文章

  1. Kafka Streams开发入门(5)

    1. 背景 上一篇演示了split操作算子的用法.今天展示一下split的逆操作:merge.Merge算子的作用是把多股实时消息流合并到一个单一的流中. 2. 功能演示说明 假设我们有多个Kafka ...

  2. Kafka Streams开发入门(4)

    背景 上一篇演示了filter操作算子的用法.今天展示一下如何根据不同的条件谓词(Predicate)将一个消息流实时地进行分流,划分成多个新的消息流,即所谓的流split.有的时候我们想要对消息流中 ...

  3. Kafka Streams开发入门(2)

    背景 上一篇我们介绍了Kafka Streams中的消息转换操作map,今天我们给出另一个经典的转换操作filter的用法.依然是结合一个具体的实例展开介绍. 演示功能说明 本篇演示filter用法, ...

  4. Kafka Streams开发入门(1)

    背景 最近发现Confluent公司在官网上发布了Kafka Streams教程,共有10节课,每节课给出了Kafka Streams的一个功能介绍.这个系列教程对于我们了解Kafka Streams ...

  5. Kafka .net 开发入门

    Kafka安装 首先我们需要在windows服务器上安装kafka以及zookeeper,有关zookeeper的介绍将会在后续进行讲解. 在网上可以找到相应的安装方式,我采用的是腾讯云服务器,借鉴的 ...

  6. 大全Kafka Streams

    本文将从以下三个方面全面介绍Kafka Streams 一. Kafka Streams 概念 二. Kafka Streams 使用 三. Kafka Streams WordCount   一. ...

  7. Kafka Streams | 流,实时处理和功能

    1.目标 在我们之前的Kafka教程中,我们讨论了Kafka中的ZooKeeper.今天,在这个Kafka Streams教程中,我们将学习Kafka中Streams的实际含义.此外,我们将看到Kaf ...

  8. 七 Kafka Streams VS Consumer API

    1 kafka Streams:   概念: 处理和分析储存在Kafka中的数据,并把处理结果写回Kafka或发送到外部系统的最终输出点,它建立在一些很重要的概念上,比如事件时间和消息时间的准确区分, ...

  9. Kafka入门实战教程(7):Kafka Streams

    1 关于流处理 流处理平台(Streaming Systems)是处理无限数据集(Unbounded Dataset)的数据处理引擎,而流处理是与批处理(Batch Processing)相对应的.所 ...

随机推荐

  1. SpringMVC的乱码问题

    1:表单提交controller获得中文参数后乱码解决方案 注意: jsp页面编码设置为UTF-8 form表单提交方式为必须为post,get方式下面spring编码过滤器不起效果 <%@ p ...

  2. <虚树+树型DP> SDOI2011消耗战

    <虚树+树型DP> SDOI2011消耗战 #include <iostream> #include <cstdio> #include <cstring&g ...

  3. Linux下进程间通信方式——共享内存

    1.什么是共享内存? 共享内存就是允许两个或多个进程共享一定的存储区.就如同 malloc() 函数向不同进程返回了指向同一个物理内存区域的指针.当一个进程改变了这块地址中的内容的时候,其它进程都会察 ...

  4. 洛谷p3916图的遍历题解

    题面 思路: 反向建边,dfs艹咋想出来的啊 倒着遍历,如果你现在遍历到的这个点已经被标记了祖先是谁了 那么就continue掉 因为如果被标记了就说明前面已经遍历过了 而我们的顺序倒着来的 前边的一 ...

  5. 【可持久化线段树】【P5826】【模板】子序列自动机

    [可持久化线段树][P5826][模板]子序列自动机 Description 给定一个序列 \(A\),有 \(q\) 次询问,每次询问一个序列 \(B\) 是不是 \(A\) 的子序列 Limita ...

  6. 第01组 Alpha事后诸葛亮

    目录 一.总结思考 1.设想和目标 ①我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? ②我们达到目标了么(原计划的功能做到了几个? 按照原计划交付时间交付了么? 原 ...

  7. 我对网络IO的理解

    Unix/Linux系统下IO主要分为磁盘IO,网络IO,我今天主要说一下对网络IO的理解,网络IO主要是socket套接字的读(read).写(write),socket在Linux系统被抽象为流( ...

  8. 请教网友:#pragma pack(push) #pragma pack(pop)无效

    //try 一 try #pragma back(push) #pragma pack(2) struct E { char a; short b; double c; float d; char e ...

  9. Unittest 类方法

    import unittest,time from selenium import webdriver class TestClass(unittest.TestCase): @classmethod ...

  10. Gamma阶段第十次scrum meeting

    每日任务内容 队员 昨日完成任务 明日要完成的任务 张圆宁 #91 用户体验与优化https://github.com/rRetr0Git/rateMyCourse/issues/91(持续完成) # ...