Kafka Stream介绍

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/opensure/article/details/51507698

Apache Kafka 0.10.0正式发布了,此版本带来了一系列新特性和bug修复,本文介绍新特性Kafka Stream

一、概述

Kafka Streams是一套类库,它使得Apache Kafka可以拥有流处理的能力,通过使用Kafka Stream API进行业务逻辑处理最后写回Kakfa或者其他系统中。Kafka Stream中有几个重要的流处理概念:严格区分Event time和Process Time、支持窗口函数、应用状态管理。开发者使用Kafka Stream的门槛非常低,比如单机进行一些小数据量的功能验证而不需要在其他机器上启动一些服务(比如在Storm运行Topology需要启动Nimbus和Supervisor,当然也支持Local Mode),Kafka Stream的并发模型可以对单应用多实例进行负载均衡。


二、主要功能

1、轻量的嵌入到java应用中

2、除了Kafka Stream Client lib以外无外部依赖

3、支持本地状态故障转移,以实现非常高效的有状态操作,如join和window函数

4、低延迟消息处理,支持基于event-time的window操作

5、提供必要的流处理原语、hige-level Stream DSL和low-level Processor API


三、开发指南

核心概念

  • Stream Processing Topology
1、stream是Kafka Stream最重要的抽象,它代表了一个无限持续的数据集。stream是有序的、可重放消息、对不可变数据集支持故障转移
2、一个stream processing application由一到多个processor topologies组成,其中每个processor topology是一张图,由多个streams(edges)连接着多个stream processor(node)
3、一个stream processor是processor topology中的一个节点,它代表一个在stream中的处理步骤:从上游processors接受数据、进行一些处理、最后发送一到多条数据到下游processors
Kafka Stream提供两种开发stream processing topology的API
1、high-level  Stream DSL:提供通用的数据操作,如map和fileter
2、lower-level Processor API:提供定义和连接自定义processor,同时跟state store(下文会介绍)交互
  • Time
在流处理中时间是一个比较重要的概念,比如说在窗口(windows)处理中,时间就代表两个处理边界
1、Event time:一条消息最初产生/创建的时间点
2、Processing time:消息准备被应用处理的时间点,如kafka消费某条消息的时间,processing time的单位可以是毫秒、小时或天。Processing time晚于Event time

Kafka Stream 使用TimestampExtractor 接口为每个消息分配一个timestamp,具体的实现可以是从消息中的某个时间字段获取timestamp以提供event-time的语义或者返回处理时的时钟时间,从而将processing-time的语义留给开发者的处理程序。开发者甚至可以强制使用其他不同的时间概念来进行定义event-time和processing time。
  • States
有些stream应用不需要state,因为每条消息的处理都是独立的。然而维护stream处理的状态对于复杂的应用是非常有用的,比如可以对stream中的数据进行join、group和aggreagte,Kafka Stream DSL提供了这个功能。
Kafka Stream使用state stores提供基于stream的数据存储和数据查询,Kafka Stream内嵌了多个state store,可以通过API访问到,这些state store的实现可以是持久化的KV存储引擎、内存HashMap或者其他数据结构。Kafka Stream提供了local state store的故障转移和自动发现。

两种API
1、Low-level Processor API
  • Processor
开发着通过实现Processor接口并实现process和punctuate方法,每条消息都会调用process方法,punctuate方法会周期性的被调用
public class MyProcessor extends Processor {
    private ProcessorContext context;
    private KeyValueStore kvStore;

    @Override
    @SuppressWarnings("unchecked")
    public void init(ProcessorContext context) {
        this.context = context;
        this.context.schedule(1000);
        this.kvStore = (KeyValueStore) context.getStateStore("Counts");
    }

    @Override
    public void process(String dummy, String line) {
        String[] words = line.toLowerCase().split(" ");

        for (String word : words) {
            Integer oldValue = this.kvStore.get(word);

            if (oldValue == null) {
                this.kvStore.put(word, 1);
            } else {
                this.kvStore.put(word, oldValue + 1);
            }
        }
    }

    @Override
    public void punctuate(long timestamp) {
        KeyValueIterator iter = this.kvStore.all();

        while (iter.hasNext()) {
            KeyValue entry = iter.next();
            context.forward(entry.key, entry.value.toString());
        }

        iter.close();
        context.commit();
    }

    @Override
    public void close() {
        this.kvStore.close();
    }
};
  • Processor Topology
使用TopologyBuilder拼装processor
TopologyBuilder builder = new TopologyBuilder();

builder.addSource("SOURCE", "src-topic")

    .addProcessor("PROCESS1", MyProcessor1::new /* the ProcessorSupplier that can generate MyProcessor1 */, "SOURCE")
    .addProcessor("PROCESS2", MyProcessor2::new /* the ProcessorSupplier that can generate MyProcessor2 */, "PROCESS1")
    .addProcessor("PROCESS3", MyProcessor3::new /* the ProcessorSupplier that can generate MyProcessor3 */, "PROCESS1")

    .addSink("SINK1", "sink-topic1", "PROCESS1")
    .addSink("SINK2", "sink-topic2", "PROCESS2")
    .addSink("SINK3", "sink-topic3", "PROCESS3");
上面的Topology做了几件事
1、首先一个叫做“SOURCE”的source node加入到topology,它会消费一个叫“src-topic”的Kafka Topic
2、其次三个processor nodes加入,其中“PROCESS1”是“SOURCE”的子节点,而"PROCESS2"和“PROCESS3”是“PROCESS1”的子节点
3、最后三个sink node加入,将三个processor nodes分别写入到三个Kafka topic中
  • Local State Store
Processor API除了依次处理消息外,还能够保存一些状态,开发者可以使用TopologyBuilder.addStateStore方法来创建local state并且关联到某个processor node,也可以连接一个已创建的local state store到processor node
TopologyBuilder.connectProcessorAndStateStores.

TopologyBuilder builder = new TopologyBuilder();

builder.addSource("SOURCE", "src-topic")

    .addProcessor("PROCESS1", MyProcessor1::new, "SOURCE")
    // create the in-memory state store "COUNTS" associated with processor "PROCESS1"
    .addStateStore(Stores.create("COUNTS").withStringKeys().withStringValues().inMemory().build(), "PROCESS1")
    .addProcessor("PROCESS2", MyProcessor3::new /* the ProcessorSupplier that can generate MyProcessor3 */, "PROCESS1")
    .addProcessor("PROCESS3", MyProcessor3::new /* the ProcessorSupplier that can generate MyProcessor3 */, "PROCESS1")

    // connect the state store "COUNTS" with processor "PROCESS2"
    .connectProcessorAndStateStores("PROCESS2", "COUNTS");

    .addSink("SINK1", "sink-topic1", "PROCESS1")
    .addSink("SINK2", "sink-topic2", "PROCESS2")
    .addSink("SINK3", "sink-topic3", "PROCESS3");

2、High-level Stream DSL
使用Stream DSL创建processor topology,开发者可以使用KStreamBuilder类,继承自TopologyBuilder,下面是官方的一个例子,完整的源码可以在streams/examples包中找到
  • Create Source Streams from Kafka
KStream可以从多个kafka topic中创建,而KTable只能单个topic
KStreamBuilder builder = new KStreamBuilder();
    KStream source1 = builder.stream("topic1", "topic2");
    KTable source2 = builder.table("topic3");
  • Transform a stream
KStream和KTable分别提供了一系列的transformation操作,每个操作都可以生成一至多个KStream和KTable对象并且传到一至多个下游的processor topology,所有的操作都可以链接到一起组成一个复杂的processor topology。由于KStream和KTable是强类型的,所有transformation操作都定义成了泛型的函数,开发者可以指定输入和输入的数据类型。
在这些transformation中,如filter、map、mapValues等,都是无状态的,并且可以同时用于KStream和KTable,开发者可以传一个自定义函数作为参数,如
// written in Java 8+, using lambda expressions
    KStream mapped = source1.mapValue(record -> record.get("category"));
无状态transformation,顾名思义,处理过程中不依赖于任何状态,实现起来也简便,因为不需要为他们关联state store
有状态transformation,需要连接到state store并产生结果,如join和aggregate操作
//written in Java 8+, using lambda expressions
KTable, Long> counts = source1.aggregateByKey(
    () -> 0L,  // initial value
    (aggKey, value, aggregate) -> aggregate + 1L,   // aggregating value
    HoppingWindows.of("counts").with(5000L).every(1000L), // intervals in milliseconds
);

KStream joined = source1.leftJoin(source2,
    (record1, record2) -> record1.get("user") + "-" + record2.get("region");
);
  • Write streams back to Kafka
最后,开发者可以将最终的结果stream写回到kafka,通过 KStream.to and KTable.to
joined.to("topic4");
如果应用希望继续读取写回到kafka中的数据,方法之一是构造一个新的stream并读取kafka topic,Kafka Stream提供了另一种更方便的方法:through
// equivalent to
//
// joined.to("topic4");
// materialized = builder.stream("topic4");
KStream materialized = joined.through("topic4");
最后,开发者运行前需要配置StreamsConfig类,详细的配置项,可以在 这里找到





猜你喜欢

转载自blog.csdn.net/opensure/article/details/51507698
今日推荐