Kafka Api 练习

kafka的事务

Producer的事务

为了实现跨分区跨会话的事务,需要引入一个全局唯一的TransactionID,并将Producer获取的PID和TransactionId,这样Producer重启后就可以通过正在进行的TransactionID获取原来的PID
为了管理事务,Kafka引入了一个新的组件Transaction Coordinator.Producer就是通过和Transaction Coordinator交互获得Transaction ID对应的任务状态,Transaction Coordinator还负责将事务所有写入kafka的内部Topic,这样整个服务重启,由于事务状态得到保存,进行中的事务状态可以得到恢复,从而继续进行


Consumer事务

上述事务是对于Producer而言,事务的保证就会相对较弱,尤其无法保证Commit的信息被精准消费,这是由于Consumer可以通过offset访问任意信息,而且不同SegmentFile声明周期不同,同一事务的信息可能会出现重启后被删除的情况

Kafka API

消息发送流程

kafka的Producer发送消息采用的是异步发送的方式。在消息发送的过程中,涉及到了两个线程----main线程和Sender线程,以及一个线程共享变量----RecordAccumulator.mian线程将消息发送给RecoderAccumulator,Sender线程不断从RecordAccumulator中拉取消息发送到Kafka broker.

相关参数

batch.size:只有数据积累到batch.size之后,sender才会发送数据

linger.ms:如果数据迟迟未达到batch.size,sender等待linger.time之后就会发送数据

Kafka API

caution:同一个消费者组里的消费者不能消费同一个topic中的同一个partion

导入依赖

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

生产者

package com.song.producer;

import org.apache.kafka.clients.KafkaClient;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;

import java.util.Properties;

public class MyProducer {
    public static void main(String[] args) {
        //1.创建kafka生产者的配置信息
        Properties properties = new Properties();
        //2.指定连接的kafka集群  broker-list----->参数可以不用记参考
        properties.put( ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop01:9092");

        //3.ack应答级别
        properties.put("acks", 3);
        //4.重试次数
        properties.put("retries", 3);
        //5.批次的大小
        properties.put("batch.size", 16384); //16k
        //6.等待时间
        properties.put("linger.ms", 1);
        //7.RecordAccumulator缓冲区大小
        properties.put("buffer.memory", 33554432);
        //8.kv 序列化
        properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
        //9.创建producer
        KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
        //10.发送数据-->轮询发送两个分区
        for (int i = 0; i < 10; i++) {
            producer.send(new ProducerRecord<>("first","song---"+i));
        }
        //11.关闭资源
        producer.close();
    }
    //测试,需要先创建一个topic[first]:   bin/kafka-console-consumer.sh   --zookeeper  hadoop02:2181     --topic  first

}

带回调信息的producer

package com.song.producer;

import org.apache.kafka.clients.producer.*;

import java.util.Properties;

public class CallBackProducer {
    public static void main(String[] args) {
        //1.创建配置信息
        Properties properties = new Properties();
        properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG,"hadoop01:9092");
        properties.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");
        properties.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG,"org.apache.kafka.common.serialization.StringSerializer");
        //2.创建生产者对象
        KafkaProducer<String, String> producer = new KafkaProducer<>(properties);
        //3.发送数据
        for (int i = 0; i < 10; i++) {
            producer.send(new ProducerRecord<>("first", "song-->" + i), (recordMetadata, e) -> {
                if (e==null){
                    System.out.println(recordMetadata.partition());
                    System.out.println(recordMetadata.offset());
                }else {
                    e.printStackTrace();
                }
            });
            //4.关闭资源
            producer.close();
        }
    }
}

自定义分区器

package com.song.partitioner;

import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;
import org.apache.kafka.common.PartitionInfo;
import org.apache.kafka.common.internals.Topic;

import java.util.List;
import java.util.Map;

//自定义的组件不是继承就是实现
//自定义分区可以参考DefaultPartitioner
public class MyPartitioner implements Partitioner {
    //k,v已经是序列化的数据了
    @Override
    public int partition(String topic, Object key, byte[] bytes, Object value, byte[] bytes1, Cluster cluster) {
        //确定了存活的partitioner
        List<PartitionInfo> partitionInfos = cluster.availablePartitionsForTopic(topic);
        /**
         Integer count = cluster.partitionCountForTopic(topic);
         return key.toString().hashCode()%count;
         */

        //这里简单点,都写到1号分区
        return 1;
    }

    @Override
    public void close() {

    }

    @Override
    public void configure(Map<String, ?> map) {

    }
}

使用自定义分区器,添加到producer配置里面,使用send里面的回调函数查看结果

....
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,"com.song.partitioner.MyPartitioner");
....
 for (int i = 0; i < 10; i++) {
            producer.send(new ProducerRecord<>("first-topic", "song-key","hehehe-value" + i), new Callback() {
                @Override
                public void onCompletion(RecordMetadata recordMetadata, Exception e) {
                    if (e==null){
                        System.out.println("分区 :"+recordMetadata.partition());
                    }else {
                        e.printStackTrace();
                    }
                }
            });
        }
...

同步发送 API
同步发送的意思就是,一条消息发送之后,会阻塞当前线程,直至返回 ack。 由于 send 方法返回的是一个 Future 对象,根据 Futrue 对象的特点【futrue.get()获取的返回值会阻塞线程】,我们也可以实现同步发送的效果,只需在调用 Future 对象的 get 方发即可。

Consumer API

Consumer 消费数据时的可靠性是很容易保证的,因为数据在 Kafka 中是持久化的,故
不用担心数据丢失问题。
由于 consumer 在消费过程中可能会出现断电宕机等故障,consumer 恢复后,需要从故
障前的位置的继续消费,所以 consumer 需要实时记录自己消费到了哪个 offset,以便故障恢
复后继续消费。
所以 offset 的维护是 Consumer 消费数据是必须考虑的问题。

package com.song.consumer;

import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.apache.kafka.common.serialization.StringDeserializer;

import java.util.Arrays;
import java.util.Properties;

public class MyConsumer {
    public static void main(String[] args) {
        Properties properties = new Properties();
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop01:9092");
        //开启自动提交offset
        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
        //自动提交的延迟
        properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");  // 默认自动提交
        //k,v的反序列化
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
        //自定义的消费者组
        properties.put(ConsumerConfig.GROUP_ID_CONFIG, "bigdata");
        
        //重置消费者offset[earliest,later],换组 或者 offSet失效  时这个参数生效
        properties.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);
        //订阅主题,也可以是多个主题
        consumer.subscribe(Arrays.asList("first", "second"));
        while (true) {
            //获取数据,
            ConsumerRecords<String, String> consumerRecords = consumer.poll(100);
            //解析并打印
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {

                String key = consumerRecord.key();
                String value = consumerRecord.value();
                System.out.println(key + "--->" + value);

            }
        }

    }

}

notice : 当offset不设置为自动提交时,只有在每一次启动时才会读取保存在__consumer或者保存在zk里面的旧的offset,否则consumer会读取维护在内存中的offset

手动提交offset(了解)

由于自动提交是基于时间提交的,开发人员难以把握offset提交的时机,因此kafka提供了手动提交offset的API

手动提交有两种:commitAsync(异步) commitSync(同步)

//设置手动提交
....
        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
...
while(true){
...
//异步提交 
   consumer.commitAsync(new OffsetCommitCallback() {                 @Override                 public void onComplete(Map<TopicPartition, OffsetAndMetadata> offsets, Exception exception) {                     if (exception != null) {                         System.err.println("Commit failed for" + offsets);                     }                 }             }); 

}

官方提供了一种自定义offset(consumer 最终解决方案)

Kafka 0.9 版本之前,offset 存储在 zookeeper,0.9 版本及之后,默认将 offset 存储在 Kafka的一个内置的 topic 中。除此之外,Kafka 还可以选择自定义存储 offset。 offset 的维护是相当繁琐的,因为需要考虑到消费者的 Rebalance。 当有新的消费者加入消费者组、已有的消费者推出消费者组或者所订阅的主题的分区发生变化,就会触发到分区的重新分配,重新分配的过程叫做 Rebalance。 消费者发生 Rebalance 之后,每个消费者消费的分区就会发生变化。因此消费者要首先获取到自己被重新分配到的分区,并且定位到每个分区最近提交的 offset 位置继续消费。

要实现自定义存储 offset,需要借助 ConsumerRebalanceListener,以下为示例代码,其中提交和获取 offset 的方法,需要根据所选的 offset 存储系统自行实现。

package com.song.consumer;

import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.TopicPartition;

import java.util.*;
import java.util.function.Consumer;

public class CustomerConsumer {
    private static Map<TopicPartition, Long> currentOffset = new HashMap<>();

    public static void main(String[] args) {
        //创建配置信息
        Properties properties = new Properties();
        properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop01:9092");
        properties.put(ConsumerConfig.GROUP_ID_CONFIG, "bigdata");
        properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false);
        //k,v的反序列化
        properties.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
        properties.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
        //创建消费者
        KafkaConsumer<String, String> consumer = new KafkaConsumer<>(properties);
        //订阅主题,也可以是多个主题
        consumer.subscribe(Arrays.asList("first", "second"), new ConsumerRebalanceListener() {
            //before  rebanlce
            @Override
            public void onPartitionsRevoked(Collection<TopicPartition> collection) {
                commitOffset(currentOffset);
            }

            //after  rebanlce
            @Override
            public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
                partitions.clear();
                for (TopicPartition partition : partitions) {
                    //定位到最近提交offset位置继续
                    consumer.seek(partition, getOffset(partition));

                }

            }


        });
        while (true) {
            ConsumerRecords<String, String> consumerRecords = consumer.poll(100);
            for (ConsumerRecord<String, String> consumerRecord : consumerRecords) {
                System.out.printf("offset = %d, key = %s, value = %s%n", consumerRecord.offset(), consumerRecord.key(), consumerRecord.value());
                currentOffset.put(new TopicPartition(consumerRecord.topic(), consumerRecord.partition()), consumerRecord.offset());

            }
            //异步提交
            commitOffset(currentOffset);
        }

    }

    //获取某分区最新的offset
    private static long getOffset(TopicPartition partition) {
        return 0;
    }

    //提交该消费者所有分区的offset
    private static void commitOffset(Map<TopicPartition, Long> currentOffset) {
    }
}

自定义拦截器

拦截器原理
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):
该方法会在消息从 RecordAccumulator 成功发送到 Kafka Broker 之后,或者在发送过程中失败时调用。并且通常都是在 producer 回调逻辑触发之前。onAcknowledgement 运行在producer 的 IO 线程中,因此不要在该方法中放入很重的逻辑,否则会拖慢 producer 的消息发送效率。

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

拦截器案例

1)需求:

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

import org.apache.kafka.clients.producer.ProducerInterceptor;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;

import java.util.Map;

public class TimeInterceptor implements ProducerInterceptor {

    @Override
    public void configure(Map<String, ?> map) {

    }

    @Override
    public ProducerRecord onSend(ProducerRecord producerRecord) {
        //取出数据
        Object value = producerRecord.value();
        //创建一个对象
        return new ProducerRecord(producerRecord.topic(), producerRecord.partition(), producerRecord.timestamp(), producerRecord.key(), System.currentTimeMillis() + "," + value);
    }

    @Override
    public void onAcknowledgement(RecordMetadata recordMetadata, Exception e) {

    }

    @Override
    public void close() {

    }


}
package com.song.interceptor;

import org.apache.kafka.clients.producer.ProducerInterceptor;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;

import java.util.Map;

public class CounterInterceptor implements ProducerInterceptor {
    int  success;
    int  error;
    @Override
    public ProducerRecord onSend(ProducerRecord producerRecord) {
        return producerRecord;
    }

    @Override
    public void onAcknowledgement(RecordMetadata recordMetadata, Exception e) {
        if (recordMetadata!=null){
            success++;
        }else {
            error++;
        }

    }

    @Override
    public void close() {
        System.out.println("success"+success);
        System.out.println("error"+error);
    }

    @Override
    public void configure(Map<String, ?> map) {

    }
}

拦截器和分区器一样 ,在配置文件中添加

....
interceptors.add("com.song.interceptor.TimeInterceptor");
interceptors.add("com.song.interceptor.CounterInterceptor");
properties.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG,interceptors);
...

Kafka监控

Kafka Eagle

1.修改 kafka 启动命令 修改 kafka-server-start.sh 命令中

if [ "x$KAFKA_HEAP_OPTS" = "x" ]; then    
export KAFKA_HEAP_OPTS="-Xmx1G -Xms1G"
fi

if [ "x$KAFKA_HEAP_OPTS" = "x" ]; then    
export KAFKA_HEAP_OPTS="-server -Xms2G -Xmx2G -XX:PermSize=128m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:ParallelGCThreads=8 XX:ConcGCThreads=5 -XX:InitiatingHeapOccupancyPercent=70"     
export JMX_PORT="9999"    
#export KAFKA_HEAP_OPTS="-Xmx1G -Xms1G" 
fi

注意:修改之后在启动 Kafka 之前要分发之其他节点

2.上传压缩包 kafka-eagle-bin-1.3.7.tar.gz 到集群/opt/software 目录

3.解压到本地

tar -zxvf kafka-eagle-bin1.3.7.tar.gz 
#进入到解压的目录
#将 kafka-eagle-web-1.3.7-bin.tar.gz 解压至/opt/module 
 tar -zxvf kafka-eagleweb-1.3.7-bin.tar.gz -C /opt/module/
#修改名称
 mv kafka-eagle-web-1.3.7/ eagle 
#.给启动文件执行权限 
 bin]chmod 777 ke.sh 
 

4.修改配置文件

kafka.eagle.zk.cluster.alias=cluster1 
cluster1.zk.list=hadoop01:2181,hadoop02:2181,hadoop03:2181,
 
 cluster1.kafka.eagle.offset.storage=kafka 
 

kafka.eagle.metrics.charts=true
kafka.eagle.sql.fix.error=false 
 

kafka.eagle.driver=com.mysql.jdbc.Driver 
kafka.eagle.url=jdbc:mysql://hadoop01:3306/ke?useUnicode=true&ch aracterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull 
kafka.eagle.username=root 
kafka.eagle.password=000000 

5.添加环境变量

export KE_HOME=/opt/module/eagle 
export PATH=$PATH:$KE_HOME/bin 

source /etc/profile

6.启动(注意:启动之前需要先启动 ZK 以及 KAFKA )

eagle]$ bin/ke.sh start

7.登录页面查看监控数据

http://hadoop01:8084/ke

Kafka对接flume 略

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值