Kafka streams dsl最全方法及用法说明(附带案例)

官方介绍:
Kafka Streams是一个用于构建应用程序和微服务的客户端库,其中输入和输出数据存储在Kafka集群中。它结合了在客户端编写和部署标准Java和Scala应用程序的简单性,以及Kafka服务器端集群技术的优势。

DSL (Domain Specific Language) 领域特定语言
它是建立在流处理器API之上的。推荐大多数用户使用,尤其是初学者。大多数数据处理操作可以只用几行DSL代码表示。

👇 调试时可以用offsetexplorer,是一款可视化的kafka UI 👇
offsetexplorer

stream

创造一个stream流

final StreamsBuilder builder = new StreamsBuilder();
// key序列化,value序列化均为String序列化器
KStream<String, String> stream = builder.stream("delay-test", Consumed.with(Serdes.String(), Serdes.String()));

创建出stream流并不意味着它就开启了,需要手动开启。

Topology topology = builder.build();// 流的拓扑结果
System.out.println(topology.describe());
KafkaStreams kafkaStreams = new KafkaStreams(topology, props);
kafkaStreams.start();

Runtime.getRuntime().addShutdownHook(new Thread("streams-shutdown-hook") {
    @Override
    public void run() {
        kafkaStreams.close(); // 最好正确的关闭它
    }
});

Peek

查看流

stream.peek((key, value) -> System.out.println("key=" + key + ", value=" + value));

Branch

分支,按照特定规则划分流

Map<String, KStream<String, String>> map = stream.split(Named.as("Branch-"))
        .branch((k, v) -> v.startsWith("A"))
        .branch((k, v) -> v.startsWith("B"))
        .defaultBranch(Branched.as("C"));
// KStream branches.get("Branch-A") 包含了全部value以"A"开头的流
// KStream branches.get("Branch-B") 包含了全部value以"B"开头的流
// KStream branches.get("Branch-C") 其他记录流

Filter

过滤,只处理想要的流(此功能可以实现基于Kafka的死信队列)

stream.filter((k, v) -> {
    long time = Long.parseLong(v);
    // 假设value存的是时间,当前时间 <= 存的时间,意味着该处理消息了
    // 没有过滤到的会自动等待。
    return System.currentTimeMillis() <= time;
}).to("handle-topic"); // 将消息发送到handle-topic,此时有监听器监听这个topic的话将被立刻执行

Inverse Filter

反向过滤

KStream<String, Long> onlyPositives = stream.filterNot((key, value) -> value <= 0);// 只要value > 0的

FlatMap

根据收到的消息,变成其他类型的消息(0条、1条、多条)

stream.flatMap(
 // 例如: (345, "Hello") -> ("HELLO", 1000), ("hello", 9000)
(key, value) -> {
  List<KeyValue<String, Integer>> result = new LinkedList<>();
  result.add(KeyValue.pair(value.toUpperCase(), 1000));
  result.add(KeyValue.pair(value.toLowerCase(), 9000));
  return result;
}

FlatMapValues

类似FlatMap,但是不能改变数据类型

stream.flatMapValues((k, v) -> List.of(v + "666"))
        .peek((k, v) -> System.out.println(v)); //(key,bEfw_EPb) -> (key,bEfw_EPb666)

Foreach

单纯的遍历吧,不能改数据

stream.foreach((key, value) -> System.out.println(key + " => " + value));

GroupByKey

根据key分组,后续可以进行聚合函数的操作

KGroupedStream<byte[], String> groupedStream = stream.groupByKey();

// 指定序列化
KGroupedStream<byte[], String> groupedStream = stream.groupByKey(
    Grouped.with(
      Serdes.String(), /* key */
      Serdes.String())     /* value */
  );

GroupBy

自己制定分组方式

KGroupedStream<String, String> groupedStream = stream.groupBy(
    (key, value) -> value,
    Grouped.with(
      Serdes.String(), /* key */
      Serdes.String())  /* value */
  );

// 分组的时候可以改值的类型
KGroupedTable<String, Integer> groupedTable = table.groupBy(
    (key, value) -> KeyValue.pair(value, value.length()),
    Grouped.with(
      Serdes.String(), /* key */
      Serdes.Integer()) /* value */
  );

Cogroup

聚合多个groupStream

Map

遍历消息,可以更改消息

stream.map(
    (key, value) -> KeyValue.pair(value.toLowerCase(), value.length())); // (KEY1,value1) -> (key1,6)

Map (values only)

只能改value,不能改key

stream.mapValues(value -> value.toUpperCase());

SelectKey

只能改key,感觉这个东西和Map (values only)结合起来是map的完整功能吧。

stream.selectKey((k, v) -> {
    return "key-init";
}).peek(((key, value) -> {
    System.out.println(key + ":" + value);
}));
// (key,YnfYWNoR) -> (key-init,YnfYWNoR)

Merge

流的合并

KStream<byte[], String> stream1 = ...;
KStream<byte[], String> stream2 = ...;
KStream<byte[], String> merged = stream1.merge(stream2);

Aggregate

滚动聚合

key是null将被忽略,state store name用于存储之前的结果,再下一次运行时继续聚合。

看一个例子,往Topic中一次放入三条数据(对象的形式)

{“from”:“ivk1Cfa6”,“to”:“0dNonPAg”,“body”:“p0_YbUJS”}
{“from”:“9QLtz40V”,“to”:“07Nrgwo9”,“body”:“BmLmJ6cd”}
{“from”:“eYZI_yA_”,“to”:“vD9cqTXg”,“body”:“YRXZ_7JJ”}

有记忆化功能,因为聚合的数据被存储到了aggregated-table-store中。

KStream<String, String> stream = builder.stream("my-topic", Consumed.with(Serdes.String(), Serdes.String()));
stream.groupByKey().aggregate(
        () -> new String("".getBytes(), StandardCharsets.UTF_8),// 给定初始值
    	// aggKey消息key、newValue当前收到的消息、aggValue当前聚合的结果
        (aggKey, newValue, aggValue) -> {
            System.out.println("aggKey=" + aggKey);
            System.out.println("newValue=" + newValue);
            System.out.println("aggValue=" + aggValue);
            Message message = JSONObject.parseObject(newValue, Message.class);
            return aggValue + message.getBody();
        },
        Materialized.<String, String, KeyValueStore<Bytes, byte[]>>as("aggregated-table-store") /* state store name */
                .withValueSerde(Serdes.String())
).toStream().peek((k, v) -> {
    System.out.println(k);
    System.out.println(v);
});

// 最后一次输出
aggKey=key2
newValue={"from":"eYZI_yA_","to":"vD9cqTXg","body":"YRXZ_7JJ"}
aggValue=p0_YbUJSBmLmJ6cd
key2
p0_YbUJSBmLmJ6cdYRXZ_7JJ

Aggregate (windowed)

窗口聚合,和上面说到的类似,只不过有一个窗口期,过了这个窗口期数据清空。

{“from”:“_rBU2X4n”,“to”:“ef6_UDSq”,“body”:“uxTl6m1j”}
{“from”:“81jx6zd_”,“to”:“PdyJdG3P”,“body”:“CAa69VG6”}

先发送第一条,过了五分钟发送第二条,你会发现这两条消息没有合并在一起,因为过了窗口期

stream.groupByKey().windowedBy(SlidingWindows.ofTimeDifferenceWithNoGrace(Duration.ofMinutes(5))).aggregate(
        () -> new String("".getBytes(), StandardCharsets.UTF_8),
        (aggKey, newValue, aggValue) -> {
            System.out.println("aggKey=" + aggKey);
            System.out.println("newValue=" + newValue);
            System.out.println("aggValue=" + aggValue);
            Message message = JSONObject.parseObject(newValue, Message.class);
            return aggValue + message.getBody();
        },
        Materialized.<String, String, WindowStore<Bytes, byte[]>>as("aggregated-table-store") /* state store name */
                .withValueSerde(Serdes.String())
).toStream().peek((k, v) -> {
    System.out.println(k);
    System.out.println(v);
});

count

按照分组key统计数量

key是null将被忽略

KStream<String, String> stream = builder.stream("my-topic", Consumed.with(Serdes.String(), Serdes.String()));
stream.groupByKey().count().toStream().peek((k, v) -> {
    System.out.println(k);
    System.out.println(v);
});
/**
 * 第一次 key 1
 * 第二次 key 2
 * 第三次 key2 1 ...
 */

count(window)

一样,多了个窗口期

reduce

滚动聚合,和Aggregate 功能类似

key是null将被忽略

KStream<String, String> stream = builder.stream("my-topic", Consumed.with(Serdes.String(), Serdes.String()));
stream.groupByKey().reduce((oldValue, newValue) -> {
    return oldValue + newValue;
}).toStream().peek((k, v) -> {
    System.out.println(k);
    System.out.println(v);
});

reduce (windowed)

滚动聚合,和Aggregate 功能类似,多了窗口期

key是null将被忽略

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你熬夜了吗?

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值