<Zhuuu_ZZ>流式计算之Kafka Stream

一 What is Kafka Stream

Kafka Streams。Apache Kafka 开源项目的一个组成部分。是一个功能强大,易于使用的库。用于在 Kafka 上构建高可分布式、拓展性,容错的应用程序。

二 Features

  • 功能强大
    • 高扩展性,弹性,容错
  • 轻量级
    • 无需专门的集群
    • 一个库,而不是框架
  • 完全集成
    • 100%的 Kafka 0.10.0 版本兼容
    • 易于集成到现有的应用程序
  • 实时性
    • 毫秒级延迟
    • 并非微批处理
    • 窗口允许乱序数据
    • 允许迟到数据

三 Why need KafkaStream

  • 当前已经有非常多的流式处理系统,最知名且应用最多的开源流式处理系统有 Spark Streaming 和 Apache Storm。Apache Storm 发展多年,应用广泛,提供记录级别的处理能力,当前也支持 SQL on Stream。而 Spark Streaming 基于 Apache Spark,可以非常方便与图计算,SQL 处理等集成,功能强大,对于熟悉其它 Spark 应用开发的用户而言使用门槛低。另外,目前主流的 Hadoop 发行版,如 Cloudera 和 Hortonworks,都集成了 Apache Storm 和 Apache Spark,使得部署更容易。
  • 既然 Apache Spark 与 Apache Storm 拥用如此多的优势,那为何还需要 Kafka Stream 呢?
  • 主要有如下原因。
    • 第一,Spark 和 Storm 都是流式处理框架,而 Kafka Stream 提供的是一个基于 Kafka 的流式处理类库。框架要求开发者按照特定的方式去开发逻辑部分,供框架调用。开发者很难了解框架的具体运行方式,从而使得调试成本高,并且使用受限。而 Kafka Stream 作为流式处理类库,直接提供具体的类给开发者调用,整个应用的运行方式主要由开发者控制,方便使用和调试。
      在这里插入图片描述
    • 第二,虽然 Cloudera 与 Hortonworks 方便了 Storm 和 Spark 的部署,但是这些框架的部署仍然相对复杂。而 Kafka Stream 作为类库,可以非常方便的嵌入应用程序中,它对应用的打包和部署基本没有任何要求。
    • 第三,就流式处理系统而言,基本都支持 Kafka 作为数据源。例如 Storm 具有专门的kafka-spout,而 Spark 也提供专门的 spark-streaming-kafka 模块。事实上,Kafka 基本上是主流的流式处理系统的标准数据源。换言之,大部分流式系统中都已部署了 Kafka,此时使用Kafka Stream 的成本非常低。
    • 第四,==使用 Storm 或 Spark Streaming 时,需要为框架本身的进程预留资源,==如 Storm的 supervisor 和 Spark on YARN 的 node manager。即使对于应用实例而言,框架本身也会占用部分资源,如 Spark Streaming 需要为 shuffle 和 storage 预留内存。但是 Kafka 作为类库不占用系统资源。
    • 第五,由于Kafka 本身提供数据持久化,因此 Kafka Stream 提供滚动部署和滚动升级以及重新计算的能力。
    • 第六,由于 Kafka Consumer Rebalance 机制,Kafka Stream 可以在线动态调整并行度。

四 Kafka Stream案例

1、Maven工程添加依赖

<dependency>
      <groupId>org.apache.kafka</groupId>
      <artifactId>kafka_2.11</artifactId>
      <version>2.0.0</version>
    </dependency>
    <dependency>
      <groupId>org.apache.kafka</groupId>
      <artifactId>kafka-streams</artifactId>
      <version>2.0.0</version>
    </dependency>

2、案例一:实现topic之间的流传输

  • Linux创建输入流topic
//创建传入流的topic
[root@hadoopwei ~]# kafka-topics.sh --create --zookeeper 192.168.198.201:2181 --topic mystreamin --partitions 1 --replication-factor 1
 Created topic "mystreamin".
  • IDEA创建流处理并执行
package cn.kgc.kb09.kafkaStream;

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 java.util.Properties;
import java.util.concurrent.CountDownLatch;

/**
 * @author: Zhuuu_ZZ
 * @Date 2020/12/15
 * @Description:
 */
public class MyStream {
    public static void main(String[] args) {
        Properties prop =new Properties();
        prop.put(StreamsConfig.APPLICATION_ID_CONFIG,"mystream");
        prop.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.198.201:9092");
        prop.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
        prop.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG,Serdes.String().getClass());

        //创建流构造器
        StreamsBuilder builder=new StreamsBuilder();

        //构建好builder 将mystreamin topic中的数据写入到mystreamout topic中
        builder.stream("mystreamin").to("mystreamout");

        final Topology topo=builder.build();
        final KafkaStreams streams = new KafkaStreams(topo, prop);
        final CountDownLatch latch=new CountDownLatch(1);
        Runtime.getRuntime().addShutdownHook(new Thread("stream"){
            @Override
            public void run() {
                streams.close();
                latch.countDown();
            }
        });

        try {
            streams.start();
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.exit(0);

    }
}


  • Linux生产消息进入流输入topic并消费流输出topic
 //生产消息
[root@hadoopwei ~]# kafka-console-producer.sh --topic mystreamin --broker-list 127.0.0.1:9092
         

//消费消息
[root@hadoopwei ~]# kafka-console-producer.sh --topic mystreamout --bootstrap-server127.0.0.1:9092

3、案例二:利用Kafka流实现wordcount

  • Linux创建流输入topic
//创建传入流的topic
[root@hadoopwei ~]# kafka-topics.sh --create --zookeeper 192.168.198.201:2181 --topic wordcount_input --partitions 1 --replication-factor 1
 Created topic "wordcount_input".

  • IDEA自定义流处理过程
package cn.kgc.kb09.kafkaStream;

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.streams.*;
import org.apache.kafka.streams.kstream.KStream;
import org.apache.kafka.streams.kstream.KTable;

import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;

/**
 * @author: Zhuuu_ZZ
 * @Date 2020/12/15
 * @Description:
 */
public class WordCountStream {
    public static void main(String[] args) {
        Properties prop=new Properties();
        prop.put(StreamsConfig.APPLICATION_ID_CONFIG,"wordcount");
        prop.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG,"192.168.198.201:9092");
        prop.put(StreamsConfig.COMMIT_INTERVAL_MS_CONFIG,"3000");
        prop.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG,"earliest");  //earliest latest none
        prop.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG,"true");
        prop.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
        prop.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG,Serdes.String().getClass());

        //创建流构造器
        //wordcount-input
        //hello world
        //hello java
        StreamsBuilder builder=new StreamsBuilder();
        KTable<String,Long> count = builder.stream("wordcount_input")   //从kafka中一条一条的取数据
                .flatMapValues(                     //返回压扁后的数据
                        (value) -> {           //对数据进行按空格切割,返回List集合
                            String[] split = value.toString().split(" ");
                            List<String> strings = Arrays.asList(split);
                            return strings;
//                    return null;
                        }
                )               //key:null value:hello,null world,null hello,null java
                .map((k, v) -> {
                    return new KeyValue<String, String>(v, "1");
                }).groupByKey().count();
        count.toStream().foreach((k,v)->{
            System.out.println("key:"+k+" "+"value:"+v);
        });
        count.toStream().map((x,y)->{
                    return new KeyValue<String,String>(x,y.toString());
        })
    .to("wordcount_out");

        final Topology topo=builder.build();
        final KafkaStreams streams = new KafkaStreams(topo, prop);
        final CountDownLatch latch=new CountDownLatch(1);
        Runtime.getRuntime().addShutdownHook(new Thread("stream"){
            @Override
            public void run() {
                streams.close();
                latch.countDown();
            }
        });

        try {
            streams.start();
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.exit(0);
    }
}


  • Linux生产消息进入流输入topic并消费流输出topic
 //生产消息
[root@hadoopwei ~]# kafka-console-producer.sh --topic wordcount_input --broker-list 127.0.0.1:9092
               >

//消费消息
[root@hadoopwei ~]# kafka-console-producer.sh --topic wordcount_out --bootstrap-server127.0.0.1:9092

4、案例三:利用Kafka流实现对输入数字的求和

  • Linux创建输入流topic
//创建传入流的topic
[root@hadoopwei ~]# kafka-topics.sh --create --zookeeper 192.168.198.201:2181 --topic sumin --partitions 1 --replication-factor 1
 Created topic "sumin".

  • IDEA自定义流处理过程并运行
package cn.kgc.kb09.kafkaStream;

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.streams.*;
import org.apache.kafka.streams.kstream.KTable;


import java.util.*;
import java.util.concurrent.CountDownLatch;

/**
 * @author: Zhuuu_ZZ
 * @Date 2020/12/15
 * @Description:
 */
public class SumStream {
    public static void main(String[] args) {
        Properties prop = new Properties();
        prop.put(StreamsConfig.APPLICATION_ID_CONFIG, "sum");
        prop.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.198.201:9092");
        prop.put(StreamsConfig.COMMIT_INTERVAL_MS_CONFIG, "3000");
        prop.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true");
        prop.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");  //earliest latest none
        prop.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
        prop.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());
        //创建流构造器
        StreamsBuilder builder = new StreamsBuilder();

        KTable<String, String> sum = builder.stream("sumin")   //从kafka中一条一条的取数据
                .map((x, y) -> {
                   return new KeyValue<>("1", y.toString());
               }).groupByKey()
               .reduce((x, y) -> {//必须输入两个数字后才输出和,因为一开始内部store-state没有缓存,等两个数字求出和sum后会存入到x为一个内部store-state,然后下个数字进来为y,和x相加
                   // int i = Integer.parseInt(x)+ Integer.parseInt(y);
                   //  return String.valueOf(i);
                Interger i=Integer.parseInt(x)+ Integer.parseInt(y);
                 return i.toString();
               });
         sum.toStream().foreach((x,y)-> System.out.println(y));
   sum.toStream().to("sumout");


        final Topology topo = builder.build();
        final KafkaStreams streams = new KafkaStreams(topo, prop);
        final CountDownLatch latch = new CountDownLatch(1);
        Runtime.getRuntime().addShutdownHook(new Thread("stream") {
            @Override
            public void run() {
                streams.close();
                latch.countDown();
            }
        });

        try {
            streams.start();
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.exit(0);
    }

}


  • Linux生产消息进入流输入topic并消费流输出topic
 //生产消息
[root@hadoopwei ~]# kafka-console-producer.sh --topic sumin --broker-list 127.0.0.1:9092
               >

//消费消息
[root@hadoopwei ~]# kafka-console-producer.sh --topic sumout --bootstrap-server127.0.0.1:9092

5、案列四:Kafka Stream实现不同窗口的流处理

  • Linux创建输入流topic
//创建传入流的topic
[root@hadoopwei ~]# kafka-topics.sh --create --zookeeper 192.168.198.201:2181 --topic windowdemo --partitions 1 --replication-factor 1
 Created topic "windowdemo".

  • IDEA实现不同窗口的流处理
package cn.kgc.kb09.kafkaStream;

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.streams.*;
import org.apache.kafka.streams.kstream.KStream;
import org.apache.kafka.streams.kstream.KTable;
import org.apache.kafka.streams.kstream.SessionWindows;
import org.apache.kafka.streams.kstream.TimeWindows;

import java.time.Duration;
import java.util.Arrays;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;

/**
 * @author: Zhuuu_ZZ
 * @Date 2020/12/16
 * @Description:
 */
public class WindowStream {
    public static void main(String[] args) {
        Properties prop = new Properties();
         //不同的窗口流不能使用相同的应用ID
        prop.put(StreamsConfig.APPLICATION_ID_CONFIG, "sessionwindow");
        prop.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.198.201:9092");
        prop.put(StreamsConfig.COMMIT_INTERVAL_MS_CONFIG, 3000);
        prop.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "false");
        prop.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");  //earliest latest none
        prop.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
        prop.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());
        //创建流构造器
        StreamsBuilder builder = new StreamsBuilder();

        KStream<Object, Object> source = builder.stream("windowdemo");
         source.flatMapValues(value-> Arrays.asList(value.toString().split("\\s+")))
                 .map((x,y)->{
                     return new KeyValue<String,String>(y,"1");
                 }).groupByKey()

         //以下所有窗口的时间均可通过下方参数调设
                 //Tumbling Time Window(窗口为20秒,20秒内有效)
              //  .windowedBy(TimeWindows.of(Duration.ofSeconds(20).toMillis()))

                 //Hopping Time Window(窗口为5秒,每次移动2秒,所以若5秒内只输入一次会出现5/2+1=3次)
              // .windowedBy(TimeWindows.of(Duration.ofSeconds(5).toMillis()).advanceBy(Duration.ofSeconds(2).toMillis()))

                 //Session Time Window(20秒内只要输入Session就有效,距离下一次输入超过20秒Session失效,所有从重新从0开始)
              .windowedBy(SessionWindows.with(Duration.ofSeconds(20).toMillis()))

                 .count().toStream().foreach((x,y)->{
             System.out.println("x:"+x+"  y:"+y);
         });



        final Topology topo = builder.build();
        final KafkaStreams streams = new KafkaStreams(topo, prop);
        final CountDownLatch latch = new CountDownLatch(1);
        Runtime.getRuntime().addShutdownHook(new Thread("stream") {
            @Override
            public void run() {
                streams.close();
                latch.countDown();
            }
        });

        try {
            streams.start();
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.exit(0);
    }

}

注意

  • ERROR:
    • Exception in thread “sum-a3bbe4d0-4cc9-4812-a7a0-e650a8a60c9f-StreamThread-1” java.lang.IllegalArgumentException: Window endMs time cannot be smaller than window startMs time.

    • 数组越界

  • 解决方案:
    • 大概率是窗口ID一致,请修改prop.put(StreamsConfig.APPLICATION_ID_CONFIG, "sessionwindow");的参数。

6、案列五:利用Kafka Stream实现数据清洗

(1)需求:Flume将数据加载进入Kafka的流输入topic后,利用Kafka Stream将数据清洗后写入流输出topic。
(2)实现:

  • 创建流输入topic
//创建传入流的topic
[root@hadoopwei ~]# kafka-topics.sh --create --zookeeper 192.168.198.201:2181 --topic user_friends_raw --partitions 1 --replication-factor 1
 Created topic "user_friends_raw".
  • 创建Flume Agent配置文件并执行
    • vi userfriend-flume-kafka.conf
user_friend.sources=userFriendSource
user_friend.channels=userFriendChannel
user_friend.sinks=userFriendSink

user_friend.sources.userFriendSource.type=spooldir
user_friend.sources.userFriendSource.spoolDir=/opt/flume160/conf/jobkb09/dataSourceFile/userFriend
user_friend.sources.userFriendSource.deserializer=LINE
user_friend.sources.userFriendSource.deserializer.maxLineLength=320000
user_friend.sources.userFriendSource.includePattern=userFriend_[0-9]{4}-[0-9]{2}-[0-9]{2}.csv
user_friend.sources.userFriendSource.interceptors=head_filter
user_friend.sources.userFriendSource.interceptors.head_filter.type=regex_filter
user_friend.sources.userFriendSource.interceptors.head_filter.regex=^user,friends*
user_friend.sources.userFriendSource.interceptors.head_filter.excludeEvents=true


user_friend.channels.userFriendChannel.type=file
user_friend.channels.userFriendChannel.checkpointDir=/opt/flume160/conf/jobkb09/checkPointFile/userFriend
user_friend.channels.userFriendChannel.dataDirs=/opt/flume160/conf/jobkb09/dataChannelFile/userFriend

user_friend.sinks.userFriendSink.type=org.apache.flume.sink.kafka.KafkaSink
user_friend.sinks.userFriendSink.batchSize=640
user_friend.sinks.userFriendSink.brokerList=192.168.198.201:9092
user_friend.sinks.userFriendSink.topic=user_friends_raw

user_friend.sources.userFriendSource.channels=userFriendChannel
user_friend.sinks.userFriendSink.channel=userFriendChannel
//执行配置文件
[root@hadoopwei jobkb09]# flume-ng agent -n user_friend -c /opt/flume160/conf/ -f /opt/flume160/conf/jobkb09/userfriend-flume-kafka.conf -Dflume.root.logger=INFO,console

//将数据文件导入Source源文件夹下
[root@hadoopwei jobkb09]# cp /opt/flume160/conf/jobkb09/tmp/user_friends.csv /opt/flume160/conf/jobkb09/dataSourceFile/userFriend/userFriend_2020-12-16.csv
  • 此时数据已写入流输入Topic中,编写IDEA流处理过程
package cn.kgc.kb09.kafkaStream;

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.common.serialization.Serdes;
import org.apache.kafka.streams.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;

/**
 * @author: Zhuuu_ZZ
 * @Date 2020/12/16
 * @Description:
 */
public class UserFriendStream {
    public static void main(String[] args) {
        Properties prop = new Properties();
        prop.put(StreamsConfig.APPLICATION_ID_CONFIG, "userfriendapp");
        prop.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, "192.168.198.201:9092");
        prop.put(StreamsConfig.DEFAULT_KEY_SERDE_CLASS_CONFIG, Serdes.String().getClass());
        prop.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, Serdes.String().getClass());
        StreamsBuilder builder = new StreamsBuilder();
        
        //流处理过程
         builder.stream("user_friends_raw").flatMap((k,v)->{
           List<KeyValue<String,String>> list=new ArrayList();
             String[] info = v.toString().split(",");
             if(info.length==2) {
                 String[] friends = info[1].split("\\s+");
                 if (info[0].trim().length() > 0) {
                     for (String friend : friends) {
                         System.out.println(info[0] + " " + friend);
                         list.add(new KeyValue<String, String>(null, info[0] + "," + friend));
                     }
                 }
             }
             return list;
         }).to("user_friends");


        final Topology topo = builder.build();
        final KafkaStreams streams = new KafkaStreams(topo, prop);
        final CountDownLatch latch = new CountDownLatch(1);
        Runtime.getRuntime().addShutdownHook(new Thread("stream") {
            @Override
            public void run() {
                streams.close();
                latch.countDown();
            }
        });

        try {
            streams.start();
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.exit(0);
    }
}

  • Linux消费流输出Topic信息
[root@hadoopwei jobkb09]# kafka-console-consumer.sh --bootstrap-server 127.0.0.1:9092 --topic user_friends --from-beginning

五 Kafka设计解析&数据可靠性深度解读

参考:
链接:Kafka设计解析(七)Kafka Stream
链接: kafka数据可靠性深度解读.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值