需求:有一个topic:student-grade是学生的各门课程的成绩,第二个topic:student-course是学生的弱势学科,需要重点关注的,现在想把每个学生的每个弱势科目的成绩单独变成一条记录,这样可以达到老师只看学生弱势科目的成绩。这个需求并不是真实的,只是在这里想来实现一下kstream join globalktable时只能得到一条记录,如果在一条记录里面存在多个值的集合,但是想把每个值都单独作为一条记录流向topic中。本来想的是使用branch来实现,但仔细想了下对于不同个数的值,就不确定要分多少个branch,最后找到了flatMapValues()实现该效果。
先启动kafka。
1.创建一个学生成绩的topic:
[root@bogon kafka_2.12-2.1.0]# bin/kafka-topics.sh --create --zookeeper 192.168.184.128:2181 --replication-factor 1 --partitions 1 --topic student-grade
添加记录:
producer.send(new ProducerRecord<>("student-grade","201403","{\"Chinese\":\"94\",\"Math\":\"75\",\"English\":\"88\",\"Physics\":\"65\",\"Chemistry\":\"83\",\"Biology\":\"89\"}"));
2.创建一个关于每个学生的薄弱学科的topic(每个学生的薄弱学科不一样):
[root@bogon kafka_2.12-2.1.0]# bin/kafka-topics.sh --create --zookeeper 192.168.184.128:2181 --replication-factor 1 --partitions 1 --topic student-course
添加记录:
producer.send(new ProducerRecord<>("student-course","201403","Math|Physics"));
3.创建一个最后要流出的topic:
[root@bogon kafka_2.12-2.1.0]# bin/kafka-topics.sh --create --zookeeper 192.168.184.128:2181 --replication-factor 1 --partitions 1 --topic student-result
4.创建项目:
pom.xml:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>www.wyhuii.com</groupId>
<artifactId>wyhuii</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>MyKafkaStreams</name>
<dependencies>
<!-- kafka -->
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>2.1.0</version>
</dependency>
<!-- kafka streams -->
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-streams</artifactId>
<version>2.1.0</version>
</dependency>
<!-- JSONObject -->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20160810</version>
</dependency>
</dependencies>
</project>
5.创建stream进行流处理:
package teststreams;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.common.utils.Bytes;
import org.apache.kafka.streams.KafkaStreams;
import org.apache.kafka.streams.StreamsBuilder;
import org.apache.kafka.streams.StreamsConfig;
import org.apache.kafka.streams.kstream.GlobalKTable;
import org.apache.kafka.streams.kstream.KStream;
import org.apache.kafka.streams.kstream.KeyValueMapper;
import org.apache.kafka.streams.kstream.Materialized;
import org.apache.kafka.streams.kstream.ValueJoiner;
import org.apache.kafka.streams.kstream.ValueMapper;
import org.apache.kafka.streams.state.KeyValueStore;
import org.json.JSONObject;
public class OneToMultipleStream {
public static void main(String[] args) {
//配置kafka服务信息
Properties prop = new Properties();
prop.put(StreamsConfig.APPLICATION_ID_CONFIG, "kstreams-join");
prop.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.184.128:9092");
prop.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
prop.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());
prop.put(StreamsConfig.STATE_DIR_CONFIG, "C:\\IT\\tool\\kafka-state-store");//设置状态仓库的存储路径
StreamsBuilder builder = new StreamsBuilder();
//创建KStreams
KStream<String, String> gradeStream = builder.stream("student-grade");
//创建globalKtable
GlobalKTable<String, String> courseTable = builder.globalTable(
"student-course",
Materialized.<String, String, KeyValueStore<Bytes, byte[]>>as("student-course-statestore")//由于是ktable,需要指定一个存储statestore的文件
.withKeySerde(Serdes.String())
.withValueSerde(Serdes.String())
);
//将kstream与globalktable join得到一条结果
KStream<String, String> join = gradeStream.join(courseTable,
//指定了kstream中的key,即要与globalktable join的key,利用这个key在globalktable中相同的key
new KeyValueMapper<String, String, String>(){
@Override
public String apply(String key, String value) {
return key;
}
},
//join之后的新value,根据需求自己处理
new ValueJoiner<String, String, String>(){
@Override
public String apply(String gradeValue, String courseValue) {
return gradeValue+"#"+courseValue;
}
}
);
//我们想把一条学生的所有科目的成绩与该学生需要重点关注的科目进行组合之后,只想得到需要重点关注的那些科目,并将每个科目的成绩都单独成为一条记录,所以使用flatMapValues()可以得到多条结果
KStream<String, String> resultStream = join.flatMapValues(new ValueMapper<String, Iterable<String>>(){
@Override
public Iterable<String> apply(String value) {
List<String> list = new ArrayList<String>();
String[] split = value.split("#");
String gradeValue = split[0];
String courseValue = split[1];
String[] course = courseValue.split("\\|");
for(int i = 0; i< course.length; i++) {
String newValue = null;
newValue = course[i]+":"+new JSONObject(gradeValue).get(course[i]).toString();
list.add(newValue);
}
return list;
}
});
resultStream.to("student-result");
KafkaStreams streams = new KafkaStreams(builder.build(), prop);
streams.start();
}
}
6.启动消费者查看student-result的topic的记录:
这样就达到了我们要的效果,使得kstream与globalktable join之后的结果可以进行细化得到多条记录。