一:kafka命令行操作
- 查看当前服务器中的所有topic
a:进入/opt/module/kafka_2.11-0.11.0.2目录下执行下面的命令
bin/kafka-topics.sh --zookeeper hadoop102:2181 --list - 创建topic
a:进入/opt/module/kafka_2.11-0.11.0.2目录下执行下面的命令:
bin/kafka-topics.sh --zookeeper hadoop102:2181 --create --replication-factor 3 --partitions 3 --topic test1
注意:
--replication-factor 3 表示副本数为3
--partitions 3 分区数为3
- 删除topic
a:进入/opt/module/kafka_2.11-0.11.0.2目录下执行下面的命令:
bin/kafka-topics.sh --zookeeper hadoop102:2181 --delete --topic test1
- 发送消息
a:先创建一个topic
b:进入生产者窗口:bin/kafka-console-producer.sh --broker-list hadoop102:9092 --topic test1
b:进入消费者,消费消息:bin/kafka-console-consumer.sh --zookeeper hadoop102:2181 --from-beginning --topic test1
注意: --from-beginning:会把first主题中以往所有的数据都读取出来。根据业务场景选择是否增加该配置。
c:效果
d:通过上面的效果我们发现,我们创建的topic主题有3个分区,那么我们一个消费者对应3个分区,一个分区
内的消息是有序的,多个消费者分区就应该是无序的,那么为什么我们这里消费者获取的数据是有序的呢?
原因:我们生产消息速度太慢了,一旦速度提升上来,就会无序了。 - 查看指定topic详情
命令:bin/kafka-topics.sh --zookeeper hadoop102:2181 --describe --topic test1
二:Kafka生产过程分析
- 写入分析
producer采用推(push)模式将消息发布到broker,每条消息都被追加(append)到分区(patition)中,属于顺序写磁盘(顺序写 磁盘效率比随机写内存要高,保障kafka吞吐率)。
a:首先我们在kafka的server.properties配置文件中配置了日志路径
b:我们进入这个路径下我们创建的主题
前面的test1代表topic,后面的0,1,2表示的是分区号,所以每个分区都有一个文件夹
c:顺序写磁盘效率高的原因是磁盘指针不需要寻址,节约大量的时间。 - 生产者将消息放入Partition
每个Partition中的消息都是有序的,生产的消息被不断追加到Partition log上,其中的每一个消息都被赋予了一个唯一的offset值。
发布到Kafka主题的每条消息包括键值和时间戳。消息到达服务器端的指定分区后,都会分配到一个自增的偏移量。原始的消息内容和分配的偏移量以及其他一些元数据信息最后都会存储到分区日志文件中。消息的键也可以不用设置,这种情况下消息会均衡地分布到不同的分区。 - 分区的原因
a:方便在集群中扩展
每个Partition可以通过调整以适应它所在的机器,而一个topic又可以有多个Partition组成,因此整个集群就可以适应任意大小的 数据了。
b:可以提高并发,因为可以以Partition为单位读写了。
Kafka比传统消息系统有更强的顺序性保证,它使用主题的分区作为消息处理的并行单元。Kafka以分区作为最小的粒度,
将每 个 分区分配给消费者组中不同的而且是唯一的消费者,并确保一个分区只属于一个消费者,即这个消费者就是这
个分区 的唯一读取线程。那么,只要分区的消息是有序的,消费者处理的消息顺序就有保证。每个主题有多个分区,
不同的消费者处理不同的分区,所以Kafka不仅保证了消息的有序性,也做到了消费者的负载均衡。传统消息系统在服务
端保持 消息的顺序,如果有多个消费者消费同一个消息队列,服务端会以消费存储的顺序依次发送给消费者。但由于消息是
异步发送给消费者的,消息到达消费者的顺序可能是无序的,这就意味着在并行消费时,传统消息系统无法很好地保证消息
被顺序处理。虽然我们可以设置一个专用的消费者只消费一个队列,以此来解决消息顺序的问题,但是这就使得消费处理无
法真正执行。 - 分区的原则
- 副本(Replication)
- producer写入消息流程如下
三:消费模型
消息由生产者发布到Kafka集群后,会被消费者消费。消息的消费模型有两种:推送模型(push)和拉取模型(pull)。
基于推送模型(push)的消息系统,由消息代理记录消费者的消费状态。消息代理在将消息推送到消费者后,标记这条消息为
已消费,但这种方式无法很好地保证消息被处理。比如,消息代理把消息发送出去后,当消费进程挂掉或者由于网络原因没有收
到这条消息时,就有可能造成消息丢失(因为消息代理已经把这条消息标记为已消费了,但实际上这条消息并没有被实际处理)。
如果要保证消息被处理,消息代理发送完消息后,要设置状态为“已发送”,只有收到消费者的确认请求后才更新为“已消费”,这就
需要消息代理中记录所有的消费状态,这种做法显然是不可取的。
Kafka采用拉取模型,由消费者自己记录消费状态,每个消费者互相独立地顺序读取每个分区的消息,如下图所示,有两个消费者(不同消费者组)拉取同一个主题的消息,消费者A的消费进度是3,消费者B的消费进度是6。消费者拉取的最大上限通过最高水位(watermark)控制,生产者最新写入的消息如果还没有达到备份数量,对消费者是不可见的。这种由消费者控制偏移量的优点是:消费者可以按照任意的顺序消费消息。比如,消费者可以重置到旧的偏移量,重新处理之前已经消费过的消息;或者直接跳到最近的位置,从当前的时刻开始消费。
四:kafka提供了两套consumer API:高级Consumer API和低级API。
不能细化控制如分区、副本、zk等
五:同一个消费者组中的消费者,同一时刻只能有一个消费者消费
六:Kafka生产者Java API
- maven项目添加指定版本的kafka依赖
- 代码实现
package com.kafka.producer; import java.util.Properties; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerRecord; public class CustomerProducer { public static void main(String[] args) { //1:配置生产者属性 Properties props = new Properties(); //配置kafka集群节点信息 props.put("bootstrap.servers","hadoop102:9092"); //配置发送的消息是否等待应答 props.put("acks","all"); //配置消息发送失败重试次数 props.put("retries","0"); //批量处理数据的大小 props.put("batch.size","16384"); //设置批处理数据的延迟,单位:ms props.put("linger.ms","5"); //设置内存缓冲区大小,32MB props.put("buffer.memory","33554432"); //对要发送的数据进行序列化 props.put("key.serializer","org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer","org.apache.kafka.common.serialization.StringSerializer"); //2:实例化kafkaProducer KafkaProducer<String,String> producer = new KafkaProducer<String,String>(props); //3:调用kafka发送消息 for (int i = 0; i < 50; i++) { producer.send(new ProducerRecord<String, String>("test1","hello"+i)); } //关闭资源 producer.close(); } }
- 我们开启一个消费者查看结果
- 由上面结果可知,这个主题有三个分区,我们一个消费者获取三个分区内的数据,肯定是无序的。
- 发送消息加上回调函数代码
package com.kafka.producer; import java.util.Properties; import org.apache.kafka.clients.producer.Callback; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.clients.producer.RecordMetadata; public class CustomerProducer2 { public static void main(String[] args) { //1:配置生产者属性 Properties props = new Properties(); //配置kafka集群节点信息 props.put("bootstrap.servers","hadoop102:9092"); //配置发送的消息是否等待应答 props.put("acks","all"); //配置消息发送失败重试次数 props.put("retries","0"); //批量处理数据的大小 props.put("batch.size","16384"); //设置批处理数据的延迟,单位:ms props.put("linger.ms","5"); //设置内存缓冲区大小,32MB props.put("buffer.memory","33554432"); //对要发送的数据进行序列化 props.put("key.serializer","org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer","org.apache.kafka.common.serialization.StringSerializer"); //2:实例化kafkaProducer KafkaProducer<String,String> producer = new KafkaProducer<String,String>(props); //3:调用kafka发送消息 for (int i = 0; i < 50; i++) { //发送消息加上回调函数 producer.send(new ProducerRecord<String, String>("test1","hello"+i),new Callback() { @Override public void onCompletion(RecordMetadata metadata, Exception exception) { if(metadata!=null) { System.out.println(metadata.offset()+"-----"+metadata.partition()); } } }); } //关闭资源 producer.close(); } }
七:Kafka消费者API
package com.kafka;
import java.util.Arrays;
import java.util.Properties;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
public class CustomerConsumer {
public static void main(String[] args) {
Properties props = new Properties();
// 定义kakfa 服务的地址,不需要将所有broker指定上
props.put("bootstrap.servers", "hadoop102:9092");
// 制定consumer group
props.put("group.id", "test");
// 是否自动确认offset
props.put("enable.auto.commit", "true");
// 自动确认offset的时间间隔
props.put("auto.commit.interval.ms", "1000");
// key的序列化类
props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
// value的序列化类
props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
// 定义consumer
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
// 消费者订阅的topic, 可同时订阅多个
consumer.subscribe(Arrays.asList("test1"));
while (true) {
// 读取数据,读取超时时间为100ms
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());
}
}
}
八:Kafka producer拦截器(interceptor)
- 拦截器原理
Producer拦截器(interceptor)是在Kafka 0.10版本被引入的,主要用于实现clients端的定制化控制逻辑。
对于producer而言,interceptor使得用户在消息发送前以及producer回调逻辑前有机会对消息做一些定制化需求,比如修改消息等。同时,producer允许用户指定多个interceptor按序作用于同一条消息从而形成一个拦截链(interceptor chain)。Intercetpor的实现接口是org.apache.kafka.clients.producer.ProducerInterceptor, - 拦截器案例
⑴需求:
实现一个简单的双interceptor组成的拦截链。第一个interceptor会在消息发送前将时间戳信息加到消息value的最前部;
第二个interceptor会在消息发送后更新成功发送消息数或失败发送消息数。
⑵创建两个拦截器
package com.kafka.intercepter; import java.util.Map; import org.apache.kafka.clients.producer.ProducerInterceptor; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.clients.producer.RecordMetadata; /*** * 在消息发送前将时间戳信息加到消息value的最前部 * @author KGF * */ public class TimeIntecpter implements ProducerInterceptor<String,String>{ @Override public void configure(Map<String, ?> configs) { } /** * 该方法就是Producer确保在消息被序列化以及计算分区前调用该方法。 * 用户可以在该方法中对消息做任何操作,但最好保证不要修改消息所属的topic和分区,否则会影响目标分区的计算。 * 消息传递到这里的时候还没有到达kafka集群 */ @Override public ProducerRecord<String, String> onSend(ProducerRecord<String, String> record) { return new ProducerRecord<String, String>(record.topic(), record.partition(), record.key(), System.currentTimeMillis()+","+record.value()); } @Override public void onAcknowledgement(RecordMetadata metadata, Exception exception) { } @Override public void close() { } }
⑶创建生成者,在生产者中添加拦截器链package com.kafka.intercepter; import java.util.Map; import org.apache.kafka.clients.producer.ProducerInterceptor; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.clients.producer.RecordMetadata; /*** * 在消息发送后更新成功发送消息数或失败发送消息数。 * @author KGF * */ public class CounterIntecepter implements ProducerInterceptor<String,String> { private int errorCounter = 0; private int successCounter = 0; @Override public void configure(Map<String, ?> configs) { } /** * 进入kafka集群前不对消息进行任何处理 */ @Override public ProducerRecord<String, String> onSend(ProducerRecord<String, String> record) { return record; } /** * 该方法会在消息被应答或消息发送失败时调用,并且通常都是在producer回调逻辑触发之前。 * onAcknowledgement运行在producer的IO线程中,因此不要在该方法中放入很重的逻辑,否则会拖慢producer的消息发送效率 */ @Override public void onAcknowledgement(RecordMetadata metadata, Exception exception) { // 统计成功和失败的次数 if (exception == null) { successCounter++; } else { errorCounter++; } } /*** * 关闭interceptor,主要用于执行一些资源清理工作 */ @Override public void close() { // 保存结果 System.out.println("Successful sent: " + successCounter); System.out.println("Failed sent: " + errorCounter); } }
package com.kafka.producer; import java.util.ArrayList; import java.util.List; import java.util.Properties; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.clients.producer.ProducerRecord; public class CustomerProducer { public static void main(String[] args) { //1:配置生产者属性 Properties props = new Properties(); //配置kafka集群节点信息 props.put("bootstrap.servers","hadoop102:9092"); //配置发送的消息是否等待应答 props.put("acks","all"); //配置消息发送失败重试次数 props.put("retries","0"); //批量处理数据的大小 props.put("batch.size","16384"); //设置批处理数据的延迟,单位:ms props.put("linger.ms","5"); //设置内存缓冲区大小,32MB props.put("buffer.memory","33554432"); //对要发送的数据进行序列化 props.put("key.serializer","org.apache.kafka.common.serialization.StringSerializer"); props.put("value.serializer","org.apache.kafka.common.serialization.StringSerializer"); // 2 构建拦截链 List<String> interceptors = new ArrayList<>(); interceptors.add("com.kafka.intercepter.TimeIntecpter"); interceptors.add("com.kafka.intercepter.CounterIntecepter"); props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, interceptors); //2:实例化kafkaProducer KafkaProducer<String,String> producer = new KafkaProducer<String,String>(props); //3:调用kafka发送消息 for (int i = 0; i < 50; i++) { producer.send(new ProducerRecord<String, String>("test1","hello"+i)); } //关闭资源 producer.close(); } }