Kafka编程实战(JAVA)
一、概述
kafka编程实战主要由两个方面:
- 消息的发送----生产者生产数据发送到Kafa队列中
- 消息的消费----消费者从队列中拉取数据消费
主要内容:
- Kafka消息发送流程
- Kafka消息消费流程
- 消息发送失败重试机制
- 组件扩展
二、Kafka消息发送流程
如图为Kafka客户端架构主要有以下几个部分:
-
main线程----负责消息的封装、初始化、分区计算
- Interceptors:拦截器,可组成连接器链,如web中的Filter等
- Serializer:初始化器
- Partitioner:负责分区计算,计算消息要发往哪个分区
-
Sender线程
可理解成一个while…true循环,不断将监听RecordAccumulator,将其中的数据发往对应的分区
-
共享变量RecordAccumulator
如图RecordAccumulator中由多个队列,这些队列和主题中的分区是一一对应的
主线程封装好数据,保存在RecordAccumulator中,sender负责具体将共享变量中的数据发送网络传输
-
为了减少网络传输次数,不断的网络连接建立是比较耗费资源的,所以sender从共享变量中获取数据是一批一批的
-
main线程,向共享变量中发送数据是一条一条一条的
-
共享变量中对立中每批数据量的大小是可以配置的
**batch.size:**只有数据积累到batch.size之后,sender才会发送数据
**linger.ms:**如果数据迟迟未达到batch.size,sender等待linger.time之后就会发送数据
-
三、Kafka消息发送
首先Kafka发送消息的方式在老版本中有两种:
- 异步发送
- 同步发送
在新版本中只有异步发送
3.1、集群环境搭建
三台服务器
在linux环境中,首先安装zookeeper(kafaka是依赖于kafka工作的),zookeeper集群配置参考zookeeper集群搭建(docker)
安装kafka,下载其压缩包,解压即可
3.1.1、对kafka进行配置:
#broker的全局唯一编号,不能重复
broker.id=0
#删除topic功能使能
delete.topic.enable=true
#处理网络请求的线程数量
num.network.threads=3
#用来处理磁盘IO的现成数量
num.io.threads=8
#发送套接字的缓冲区大小
socket.send.buffer.bytes=102400
#接收套接字的缓冲区大小
socket.receive.buffer.bytes=102400
#请求套接字的缓冲区大小
socket.request.max.bytes=104857600
#kafka运行日志存放的路径
log.dirs=/opt/module/kafka/logs
#topic在当前broker上的分区个数
num.partitions=1
#用来恢复和清理data下数据的线程数量
num.recovery.threads.per.data.dir=1
#segment文件保留的最长时间,超时将被删除
log.retention.hours=168
#配置连接Zookeeper集群地址
zookeeper.connect=192.168.23.100:2181,192.168.23.101:2181,192.168.23.102:2181
3.1.2、环境变量
#KAFKA_HOME
export KAFKA_HOME=/opt/module/kafka
export PATH=$PATH:$KAFKA_HOME/bin
3.1.3、修改broker.id
broker.id=1、broker.id=2
每台机器上的broker.id必须唯一
3.1.4、基本命令
#启动
bin/kafka-server-start.sh stop
#关闭
bin/kafka-server-stop.sh stop
3.1.5、群起脚本
for i in `cat /opt/module/hadoop-2.7.2/etc/hadoop/slaves`
do
echo "========== $i =========="
ssh $i 'source /etc/profile&&/opt/module/kafka_2.11-0.11.0.2/bin/kafka-server-start.sh -daemon /opt/module/kafka_2.11-0.11.0.2/config/server.properties'
echo $?
done
其中路径需要根据自己路径修改
启动kafka集群前保证zookeeper已经启动,否则启动失败
3.2、创建发送客户端
通过maven构建kafka客户端
3.2.1、导入依赖
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>0.11.0.0</version>
</dependency>
3.2.2、异步发送
需要用到的类:
- KafkaProducer:需要创建一个生产者对象,用来发送数据
- ProducerConfig:获取所需的一系列配置参数
- ProducerRecord:每条数据都要封装成一个ProducerRecord对象
客户端API有两种:
- 不带回调函数的api
public class CustomProducer {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Properties props = new Properties();
props.put("bootstrap.servers", "192.168.23.100:9092");//kafka集群,broker-list
props.put("acks", "all");
props.put("retries", 1);//重试次数
props.put("batch.size", 16384);//批次大小
props.put("linger.ms", 1);//等待时间
props.put("buffer.memory", 33554432);//RecordAccumulator缓冲区大小
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
Producer<String, String> producer = new KafkaProducer<>(props);
for (int i = 0; i < 100; i++) {
producer.send(new ProducerRecord<String, String>("first", Integer.toString(i), Integer.toString(i)));
}
producer.close();
}
}
- 带回调函数的API:发送失败后,会自动重试(异步调用)
public class CustomProducer2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Properties props = new Properties();
props.put("bootstrap.servers", "192.168.23.100:9092");//kafka集群,broker-list
props.put("acks", "all");
props.put("retries", 1);//重试次数
props.put("batch.size", 16384);//批次大小
props.put("linger.ms", 1);//等待时间
props.put("buffer.memory", 33554432);//RecordAccumulator缓冲区大小
props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
Producer<String, String> producer = new KafkaProducer<>(props);
for (int i = 0; i < 100; i++) {
producer.send(new ProducerRecord<String, String>("first", Integer.toString(i), Integer.toString(i),new Callback() {
//回调函数,该方法会在Producer收到ack时调用,为异步调用
@Override
public void onCompletion(RecordMetadata metadata, Exception exception) {
if (exception == null) {
System.out.println("success->" + metadata.offset());
} else {
exception.printStackTrace();
}
}
});
}
producer.close();
}
}
3.2.2、同步发送
每次发送消息后,等到接收到ack后再发送消息
Producer<String, String> producer = new KafkaProducer<>(props);
for (int i = 0; i < 100; i++) {
producer.send(new ProducerRecord<String, String>("first", Integer.toString(i), Integer.toString(i))).get();
}
producer.close();
send方法返回的是Future对象,其个get方法可以阻塞线程,当拿到返回值后再继续发送
异步、同步的api是相同的,只是利用了Future的特性,开发中大部分场景下不使用
四、Kafka消息消费
Consumer消费数据时的可靠性是很容易保证的,因为数据在Kafka中是持久化的,故不用担心数据丢失问题。
由于consumer在消费过程中可能会出现断电宕机等故障,consumer恢复后,需要从故障前的位置的继续消费,所以consumer需要实时记录自己消费到了哪个offset,以便故障恢复后继续消费。
所以offset的维护是Consumer消费数据是必须考虑的问题。
4.1、offset提交
offset提交有两种:
手动提交
- 异步提交
- 同步提交
相同点: 都会将本次 poll 的一批数据最高的偏移量提交;
不同点: commitSync会失败重试,一直到提交成功(如果由于不可恢复原因导致,也会提交失败);而commitAsync则没有失败重试机制,故有可能提交失败。
可能都会有重复消费
手动提交时,要关闭自动提交配置
props.put("enable.auto.commit", "false")
public class CustomConsumer {
public static void main(String[] args) {
Properties props = new Properties();
props.put("bootstrap.servers", "192.168.23.100:9092");
props.put("group.id", "test");//消费者组,只要group.id相同,就属于同一个消费者组
props.put("enable.auto.commit", "false");//自动提交offset
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Arrays.asList("first"));
while (true) {
ConsumerRecords<String, String> records = consumer.poll(100);
for (ConsumerRecord<String, String> record : records) {
System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
}
//同步
consumer.commitSync();
//异步
//consumer.commitASync();
}
}
}
自动提交
默认是自动提交(异步提交)
props.put("enable.auto.commit", "true")
props.put("auto.commit.interval.ms", "1000");
五、自定义Interceptor
自定义Intercetpor要实现接口org.apache.kafka.clients.producer.ProducerInterceptor
原理与web中servlet、filter、Intercetpor是一样的
消息发送前以及producer回调逻辑前有机会对消息做一些定制化需求
该接口有以下几个方法:
-
configure(configs) 获取配置信息和初始化数据时调
-
onSend(ProducerRecord)
Producer确保在消息被序列化以及计算分区前调用该方法。用户可以在该方法中对消息做任何操作,但最好保证不要修改消息所属的topic和分区,否则会影响目标分区的计算。
-
onAcknowledgement(RecordMetadata, Exception)
该方法会在消息从RecordAccumulator成功发送到Kafka Broker之后,或者在发送过程中失败时调用。并且通常都是在producer回调逻辑触发之前。onAcknowledgement运行在producer的IO线程中,因此不要在该方法中放入很重的逻辑,否则会拖慢producer的消息发送效率。
-
close
关闭interceptor,主要用于执行一些资源清理工作
如前所述,interceptor可能被运行在多个线程中,因此在具体实现时用户需要自行确保线程安全。另外倘若指定了多个interceptor,则producer将按照指定顺序调用它们,并仅仅是捕获每个interceptor可能抛出的异常记录到错误日志中而非在向上传递。这在使用过程中要特别
留意。
六、Kafka监控
kafka监控使用一个web接口的访问插件
6.1 Kafka Monitor
一个web项目
1.上传jar包KafkaOffsetMonitor-assembly-0.4.6.jar到集群
2.在/opt/module/下创建kafka-offset-console文件夹
3.将上传的jar包放入刚创建的目录下
4.在/opt/module/kafka-offset-console目录下创建启动脚本start.sh,内容如下
#!/bin/bash
java -cp KafkaOffsetMonitor-assembly-0.4.6-SNAPSHOT.jar \
com.quantifind.kafka.offsetapp.OffsetGetterWeb \
--offsetStorage kafka \
--kafkaBrokers 192.168.23.100:9092,192.168.23.101:9092,192.168.23.101:9092 \
--kafkaSecurityProtocol PLAINTEXT \
--zk 192.168.23.100:2181,192.168.23.101:2181,192.168.23.102:2181 \
--port 8086 \
--refresh 10.seconds \
--retain 2.days \
--dbName offsetapp_kafka &
5.在/opt/module/kafka-offset-console目录下创建mobile-logs文件夹
mkdir /opt/module/kafka-offset-console/mobile-logs
6.启动KafkaMonitor
./start.sh
7.登录页面hadoop102:8086端口查看详情
6.2 Kafka Manager
1.上传压缩包kafka-manager-1.3.3.15.zip到集群
2.解压到/opt/module
3.修改配置文件conf/application.conf
kafka-manager.zkhosts="kafka-manager-zookeeper:2181"
修改为:
kafka-manager.zkhosts="192.168.23.100:2181,192.168.23.101:2181,192.168.23.102:2181"
4.启动kafka-manager
bin/kafka-manager
5.登录hadoop102:9000页面查看详细信息