Kafka Streams和interceptor

Kafka Stream

概述

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

Kafka Streams特点

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

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

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

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

Kafka Streams作用

流式处理系统:

  • Spark Streaming(Spark Streaming基于Apache Spark,可以非常方便与图计算,SQL处理等集成,功能强
    大,对于熟悉其它Spark应用开发的用户而言使用门槛低)

  • Apache Storm(应用广泛,提供记录级别的处理能力,当前也支持SQL on Stream)

  • 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的成本非常低(大部分流式处理系统都已经集成kafka,使用成本低)。

  • 第四,使用Storm或Spark Streaming时,需要为框架本身的进程预留资源,如Storm的supervisor和Spark on YARN的node manager。即使对于应用实例而言,框架本身也会占用部分资源,如Spark Streaming需要为shuffle和storage预留内存。但是Kafka作为类库不占用系统资源(kafka占用资源少)。

  • 第五,由于Kafka本身提供数据持久化,因此Kafka Stream提供滚动部署和滚动升级以及重新计算的能力。

  • 第六,由于Kafka Consumer Rebalance机制,Kafka Stream可以在线动态调整并行度。

Kafka producer拦截器(interceptor)

拦截器原理

Producer拦截器(interceptor)是在Kafka 0.10版本被引入的,主要用于实现clients端的定制化控制逻辑。

对于producer而言,interceptor使得用户在消息发送前以及producer回调逻辑前有机会对消息做一些定制化需求,比如修改消息等。同时,producer允许用户指定多个interceptor按序作用于同一条消息从而形成一个拦截链(interceptor chain)。Intercetpor的实现接口是 org.apache.kafka.clients.producer.ProducerInterceptor,其定义的方法包括:

(1)configure(configs)

获取配置信息和初始化数据时调用。

(2)onSend(ProducerRecord):

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

(3)onAcknowledgement(RecordMetadata, Exception):

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

(4)close:

关闭interceptor,主要用于执行一些资源清理工作

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

Kafka 自定义分区器

在调用Kafka的Producer API时,如果没有指定分区器,那么数据将会根据默认分区器的算法均分到各个分区。然而实际的生产环境中,可能Kafka的分区数不止一个(官方建议:Kafka的分区数量应该是Broker数量的整数倍!),所以这时需要我们自定义分区器。

默认分区器DefaultPartitioner

默认分区器:
org.apache.kafka.clients.producer.internals.DefaultPartitioner

默认分区器获取分区:
如果消息的 key 为 null,此时 producer 会使用默认的 partitioner 分区器将消息随机分布到 topic 的可用 partition 中。
如果 key 不为 null,并且使用了默认的分区器,kafka 会使用自己的 hash 算法对 key 取 hash 值,使用hash 值与 partition 数量取模,从而确定发送到哪个分区。注意:此时 key 相同的消息会发送到相同的分区(只要 partition 的数量不变化)

自定义分区器

查看源码可以发现:

1、DefaultPartitioner实现了Partitioner接口

2、分区算法的实现在这个方法中:

public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster){…………}

3、如果我们需要实现自己的分区器,那么可以有2种方法

(1)新建一个包路径和DefaultPartitioner所在的路径一致,然后更改

public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster){…………}

方法体的内容,更改为我们自己的算法即可。

(2)新建一个类,实现Partitioner接口

public class MySamplePartitioner implements Partitioner {

   private final AtomicInteger counter = new AtomicInteger(new Random().nextInt());
   private Random random = new Random();
   //我的分区器定义
   @Override
   public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {

     List partitioners = cluster.partitionsForTopic(topic);

     int numPartitions = partitioners.size();

     ......
   }
}

分区器使用

1、如果重载默认分区器,那么不用在Producer中做修改

2、如果是实现Partitioner接口的方式,那么需要在Producer中添加一个属性:

props.put("partitioner.class","com.study.kafka.MySamplePartitioner");//我的自定义分区器

partitioner.class值为自定义分区类的完整包名,这样生产者就会选择自定义的分区策略。

说明:

1.客户端测试环境中,自定义分区类跟生产者类在一个项目中,不需要其他操作;

2.想要自定义的分区放到kafka的服务器端环境时,需要将自定义的分区类生成jar包放到kafka环境的lib下,同样配置文件中指定完整包名。

当然我们还可以在不使用默认分区和自定义分区器的情况下直接指定分区直,具体做法:

在创建ProducerRecord时,指定消息的分区值。


 int partition = 0;

 if(key < 100){
   partition = 0;
 } else if(key < 200){
   partition = 1;
 }else{
   partition = 2;
 }

 ProducerRecord<String,String> records = new ProducerRecord<String,String>(TOPIC,partition,key,value);

 kafkaProducer.send(records);
posted @ 2022-09-07 23:48  snail灬  阅读(62)  评论(0编辑  收藏  举报