文章目录

一、前言

本文主要讲解kafka stream和kafka interceptor,kafka stream是一个实时的流客户端,可以从kafka-server某个topic取数据,也可以从发送数据到kafka-server某个topic,只要kafka-stream之后一直运行不停止,就可以一次建立连接,永远取kafka交互。工程中,只要集成了kafka stream的Java程序可以实时取到指定input-topic上的消息实体,且可以将消息加工之后,实时地将消息发送到output-topic。

kafka interceptor是一个在发送消息之前的拦截器,可以修改这个消息/记录的topic、partition、key、value、timestamp五个属性,而且拦截器还可以知道此消息是否发送成功。

kafka partition类似拦截器,也是在发送消息之前设置,可以自定义消息发送到哪个partition。springboot集成kafka和集成其他中间件一样,基本上导入pom依赖,配置好application.yaml文件,然后配置@Configuration修饰就好了。

本文源代码:​​kafka stream与interceptor、自定义partition、springboot集成kafka​

二、Kafka四个核心API

第一篇博文中(​​解开Kafka神秘的面纱(一):kafka架构与应用场景​​),介绍过Kafka最重要的四个核心API是Producer API、Consumer API、Streams API、Connector API,四者的关系如下图:

解开Kafka神秘的面纱(四):kafka stream及interceptor_数据

Producer API、Consumer API就是生产者和消费者,很容易理解,Connector API主要侧重kafka可以和其他数据源交互(各种关系型数据库和非关系型数据库),比较简单,本文介绍的是Streams API,主要包括stream和interceptor两个部分,就是上图右边的stream和processors。

kafka开发中,针对四种核心api,涉及到kafka的三个依赖存根。

<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka_2.13</artifactId>
<version>2.1.1</version>
</dependency>

这是kafka核心包2.1.1是kafka的版本,2.13是这个kafka所依赖的scala版本。

<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>2.1.1</version>
</dependency>

这个是kafka客户端包,主要封装了producer consumer两个api。

<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-streams</artifactId>
<version>2.1.1</version>
</dependency>

这个是streams依赖,主要封装了stream interceptor依赖。

三、kafka stream

3.1 Kafka Streams概述

Kafka Streams是一个客户端库,用于构建任务关键型实时应用程序和微服务,其中输入输出数据存储在Kafka集群中。Kafka Streams结合了在客户端编写和部署标准Java和Scala应用程序的简单性以及Kafka服务器端集群技术的优势,使这些应用程序具有高度可扩展性,弹性,容错性,分布式等等。Kafka Streams具有如下特点:

(1) 功能强大:高扩展性,弹性,容错

(2) 轻量级:无需专门的集群,一个库,而不是框架

(3) 完全集成:100%的Kafka 0.10.0版本兼容,易于集成到现有的应用程序

(4) 实时性:毫秒级延迟、并非微批处理、窗口允许乱序数据、允许迟到数据

3.2 为什么要有Kafka Streams

为什么要引入kafka stream?因为kafka是一个流式存储系统(这里的流不要理解为Java IO流),每个消息由 index+value+timestamp 构成,但是不能直接用cat命令查看,如图:

解开Kafka神秘的面纱(四):kafka stream及interceptor_散列表_02

因为是流式存储,如果想要在程序看到消息实体,需要使用kafka stream;而且之前kafka消费者是对生产者生产的消息直接消费,生产者生产什么样的消息,消费者就消费什么样的消息,如下图:

解开Kafka神秘的面纱(四):kafka stream及interceptor_kafka_03

现在,如果有了kafka stream,不仅可以在程序中拿到消息实体,还可以在程序对消息加工之后,在交给消费者消费。

即之前是 kafka创建一个topic,然后生产者向这个topic发送消息,消费者从这个topic取消息,生产者生产什么消息,什么消息就存到topic的partition上,然后消费者原封不动的将指定名称的topic上面的消息取下来(kafka上消息不会删除,只是移动offset,默认七天后删除)。

现在是创建两个topic,input-topic和output-topic,并提供一个集成了kafka stream的Java程序,从input-topic取数据,然后加工数据,将加工好的数据放到output-topic上,最后启动生产者向 input-topic 上发送消息,启动消费者取 output-topic 上的消息,既然是从 output-topic 上取,取到的当然是经过 kafka stream 程序加工之后的消息。


eg: 之前rabbitmq消息是可以直接看到,但是现在kafka流式存储不行。


3.3 单词统计案例

以下是WordCountDemo示例代码的要点(为了方便阅读,使用的是java8 lambda表达式)。

第一步:启动zk和kafka

./zkServer.sh start
./bin/kafka-server-start.sh config/server.properties

第二步:准备输入主题并启动生产者

创建名为streams-plaintext-input的输入主题和名为streams-wordcount-output的输出主题:

创建一个输入topic

​./bin/kafka-topics.sh --create --bootstrap-server 192.168.100.120:9092 --topic stream-plaintext-input --partitions 1 --replication-factor 1​

创建一个输出topic并启用压缩,因为输出流是更改日志流

​./bin/kafka-topics.sh --create --bootstrap-server 192.168.100.120:9092 --topic stream-wordcount-output --partitions 1 --replication-factor 1 --config cleanup.policy=compact​

使用相同的kafka-topics工具描述创建的主题:

​./bin/kafka-topics.sh --zookeeper 192.168.100.120:2181 --describe​

解开Kafka神秘的面纱(四):kafka stream及interceptor_哈希算法_04

也可以直接查看两个topic是否创建好了

​./bin/kafka-topics.sh --bootstrap-server 192.168.100.120:9092 --list​

第三步:启动Wordcount应用程序

​./bin/kafka-run-class.sh org.apache.kafka.streams.examples.wordcount.WordCountDemo​

演示应用程序将从输入主题stream-plaintext-input读取,对每个读取消息执行WordCount算法的计算,并将其当前结果连续写入输出主题streams-wordcount-output。

注意:在kafka解压目录的libs包目录下,这里存在一个 kafka-stream-examples-2.8.0.jar,这个jar包就是kafka官方提供的一个演示kafka stream的examples,如下图:

解开Kafka神秘的面纱(四):kafka stream及interceptor_散列表_05

这个jar包解压之后,里面有一个 ​​org.apache.kafka.streams.examples.wordcount.WordCountDemo​​​ 类,这个类的作用是对生产者发送的消息统计,这个类监听的生产者topic是​​streams-plaintext-input​​​,消费者topic是 ​​streams-wordcount-output​​,如下

解开Kafka神秘的面纱(四):kafka stream及interceptor_哈希算法_06

解开Kafka神秘的面纱(四):kafka stream及interceptor_链表_07

直接在centos上运行 ​​./bin/kafka-run-class.sh org.apache.kafka.streams.examples.wordcount.WordCountDemo​​ 这个程序有时候会连不上 127.0.0.1:9092 本机kafka,我们可以将 kafka-stream-examples-2.8.0.jar 在自己电脑上解压,将 WordCountDemo 类单独拿出来,放在idea上运行,如下:

解开Kafka神秘的面纱(四):kafka stream及interceptor_散列表_08

第四步:启动生产者和消费者

启动生产者:​​./bin/kafka-console-producer.sh --bootstrap-server 192.168.100.120:9092 --topic stream-plaintext-input​

启动消费者:​​./bin/kafka-console-consumer.sh --bootstrap-server 192.168.100.120:9092 --topic stream-wordcount-output --from-beginning --formatter kafka.tools.DefaultMessageFormatter --property print.key=true --property print.value=true --property key.deserializer=org.apache.kafka.common.serialization.StringDeserializer -- property value.deserializer=org.apache.kafka.common.serialization.LongDeserializer​

运行结果:

解开Kafka神秘的面纱(四):kafka stream及interceptor_链表_09

四、kafka interceptor

4.1 拦截器原理

Producer拦截器(interceptor)是在Kafka 0.10版本被引入的,主要用于实现clients端的定制化控制逻辑。拦截器只是对于producer而言(一定不是针对consumer而言的,当消息已经到了消费阶段,这个消息早就存放到kafka-server了),interceptor使得用户在消息发送前以及producer回调逻辑前有机会对消息做一些定制化需求,比如修改消息等。

同时,producer允许用户指定多个interceptor按序作用于同一条消息从而形成一个拦截链(interceptor chain)。Intercetpor的实现接口是org.apache.kafka.clients.producer.ProducerInterceptor,如下:

解开Kafka神秘的面纱(四):kafka stream及interceptor_kafka_10

解释上面四个方法:

(1)configure(configs):获取配置信息和初始化数据时调用。

(2)onSend(ProducerRecord):该方法封装进KafkaProducer.send方法中,即它运行在用户主线程中。Producer 确保在消息被序列化以及计算分区前调用该方法。用户可以在该方法中对消息做任何操作,包括topic partition index value timestamp但最好保证不要修改消息所属的topic和分区partition,否则会影响目标分区的计算。

(3)onAcknowledgement(RecordMetadata, Exception):该方法会在消息被应答或消息发送失败时调用,并且通常都是在producer回调逻辑触发之前。onAcknowledgement运行在producer的IO线程中,因此不要在该方法中放入很重的逻辑,否则会拖慢producer的消息发送效率。

(4)close:该方法用来关闭interceptor,主要用于执行一些资源清理工作。

如前所述,interceptor可能被运行在多个线程中,因此在具体实现时用户需要自行确保线程安全。另外倘若指定了多个interceptor,则producer将按照指定顺序调用它们,并仅仅是捕获每个interceptor可能抛出的异常记录到错误日志中而非在向上传递。这在使用过程中要特别留意。

interceptor和stream区别:stream是取input-topic的数据,加工数据之后,无法将加工之后的数据重新插入到input-topic中去,只能放到另一个output-topic中去,但是,有了interceptor之后,可以取input-topic的数据,加工数据之后,再将加工之后的数据重新插入到input-topic中去。而且,还可以定制化消息存放在某个partition,比如,可以将partition-0的消息拿出来,放到partition-1中去。

4.2 拦截器案例

4.2.1 需求

需求:实现一个简单的双interceptor组成的拦截链。第一个interceptor会在消息发送前将时间戳信息加到消息value的最前部;第二个interceptor会在消息发送后更新成功发送消息数或失败发送消息数,即

解开Kafka神秘的面纱(四):kafka stream及interceptor_散列表_11

每条消息先到TimeInterceptor,然后再到CountInterceptor,最后在发送出去。

4.2.2 案例实操

涉及的类包括三个,两个拦截器和一个主程序

第一个程序:时间戳拦截器

public class TimeInterceptor implements ProducerInterceptor<String, String>

第二个程序:计数拦截器,统计发送消息成功和发送失败消息数,并在producer关闭时打印这两个计数器

public class CounterInterceptor implements ProducerInterceptor<String, String>

第三个程序:producer主程序

public class InterceptorProducer

解开Kafka神秘的面纱(四):kafka stream及interceptor_散列表_12

解开Kafka神秘的面纱(四):kafka stream及interceptor_数据_13

解开Kafka神秘的面纱(四):kafka stream及interceptor_数据_14

4.2.3 测试

第一步:运行客户端InterceptorProducer主程序

解开Kafka神秘的面纱(四):kafka stream及interceptor_哈希算法_15

第二步:在kafka上启动消费者

​./bin/kafka-console-consumer.sh --bootstrap-server 192.168.100.120:9092 --topic test2 --from-beginning​

解开Kafka神秘的面纱(四):kafka stream及interceptor_散列表_16

4.3 kafka自定义分区

自定义分区和interceptor一样,也是生产端的,就是指定key的消息发送到某个partition。

如果一个topic拥有多个分区,但是,消息被发送给kafka的时候,只是指定了topic的名称,就是这条消息应该发送给哪个topic,消息消费也是指定topic的,partition这个概念是对客户端透明的,客户端生产消息和消费消息都不会涉及到。

那么,当一个topic拥有多个分区,某条消息具体存放到哪个partition上面呢?kafka有自己的默认规则,这个默认规则就在源码 DefaultPartitioner 类里面,如下图:

解开Kafka神秘的面纱(四):kafka stream及interceptor_哈希算法_17

好了,现在我们可以实现Partitioner接口得到一个自定义的MyPartitioner类,然后

新建一个topic,名为test3,拥有三个partition

​./bin/kafka-topics.sh --create --topic test3 --bootstrap-server 192.168.100.120:9092 --partitions 3 --replication-factor 1​

​./bin/kafka-topics.sh --bootstrap-server 192.168.100.120:9092 --list​

解开Kafka神秘的面纱(四):kafka stream及interceptor_链表_18

运行成功,自定义partition为1,10条消息都发送到了1上面去了

解开Kafka神秘的面纱(四):kafka stream及interceptor_kafka_19

eg: interceptor 和 partion 都是生产端,所以都是在生产代码里面修改。

4.4 springboot集成kafka

解开Kafka神秘的面纱(四):kafka stream及interceptor_散列表_20

解开Kafka神秘的面纱(四):kafka stream及interceptor_kafka_21

解开Kafka神秘的面纱(四):kafka stream及interceptor_散列表_22

四、尾声

本文主要介绍了kafka stream和kafka interceptor。

天天打码,天天进步!!