1. 概念
1. Kafka是一个分布式的基于发布/订阅的消息队列,应用于大数据实时处理。
1.1 消息队列就是为了让消费者跟生产者解耦,实现高效,但是安全性肯定降低,需要额外处理。
1.2 大数据一般处理流程:日志收集者(flume) ---> Kafka ---> 消息消费者(spark、flink)。
1.3 消息队列分两种:
1.3.1 点对点(一对一)
1.3.2 发布/订阅(一对多)
2. 架构
1. 每一个Topic分为多个partition
2. 每一个partition都有多个副本在不同机器
3. 消费者组内两个消费者只能消费同一个topic的不同partition分区的消息(一个分区只能被一个消费组内的一个消费者消费),组内消费者形成一个订阅者
4. 消费者组内消费者数量等于分区数时,平行度最好
5. zookeeper帮助Kafka存储机器broker信息
6. 0.9版本之前zk还存储着offset,之后版本将offset存储在了Kafka,减少Kafka于zk过多的通信
3. 安装部署
1. 依赖于zookeeper,首先安装zookeeper
2. 解压Kafka安装包
3. 配置环境变量
4. 配置Kafka属性
4.1 server.property文件
broker.id=0
log.dirs=/kafka/logs
num.partitions=1
log.retention.hours=168
log.retention.bytes=1073741824
log.retention.check.interval.ms=300000
zookeeper.connect=master:2181,slave1:2181,slave2:2181
5. 启动Kafka(预先启动zookeeper)
kafka-sever-start.sh config/server.property &
4. 命令行操作
1. 查看所有topic
kafka-topics.sh --list --zookeeper master:2181,slave1:2181
2. 新建topic 指定topic名 指定副本数 指定分区数
kafka-topics.sh --create --zookeeper cdh-master:2181,cdh-slave1:2181 --topic qdd1 --replication-factor 2 --partitions 2
2.*** 修改分区数2到3
kafka-topics.sh --alter --zookeeper master:2181,slave1:2181 --topic first --partitions 3
3. 查看/删除指定topic详细
kafka-topics.sh --describe/delete --zookeeper master:2181,slave1:2181 --topic first
4. 控制台开启一个生产者
kafka-console-producer.sh --broker-list master:9092,slave1:9092 --topic first
5. 控制台开启一个消费者 --from-beginning 从头开始消费
kafka-console-consumer.sh --bootstrap-server master:9092,slave1:9092 --topic first
5. 高级释义
1. 数据文件存储说明
1.1 .log文件存储真正的数据片段segement,数据满了1g后新建一个.log生成新的片段;
1.2 .index文件存储index
2. 生产者分区策略
2.1 分区原因: 方便扩展以及提高并发
2.2 分区原则:
2.2.1 指定分区发数据
2.2.2 指定key,根据hash方式到分区
2.2.3 什么也不指定,按照随机开始的方式,然后轮询发往各分区
3. 数据同步acks返回机制
3.1 半数以上副本同步完成即返回ack acks=-1/all(ISR同步完成)
3.1.1 延迟低,但是容灾差
3.2 全部副本完成同步再返回ack(Kafka使用此种,因为网络延迟对Kafka影响小)
3.2.1 延迟高,但是容灾好
3.3 leader写完数据就返回ack acks=1
3.4 不等待任何应答即返回ack 可能leader都未完成写操作 acks=0
4. ISR(in-sync replica set) 同步副本
4.1 例如:由于Kafka选用全部副本同步完成才返回ack,这样会有一种情况:当某一个副本由于网络原因长时间不同同步完成,
此时leader便不能返回ack。为了解决此问题,提出ISR列表的概念:当某一个follower,长时间未向leader同步数据,
便将此副本从ISR列表剔除,以便ISR列表保证全部可以快速完成同步返回ack,当然ISR列表也用于当leader挂了的时候,
可以从中选择数据完整的副本作为新的leader。注:当然,所有副本同步数据不完整时,便选择同步多的作为ISR,
但是给消费者只暴露最低的同步数(即HW - high watermark),即使leader复活也要截取到当前的HW。(生产者丢失了一部分数据哦)
4.2 replica.lag.time.max.ms 决定是否被剔除ISR列表的同步数据时间
5. Exactly Once语义
5.1 ACK设置为-1: At Least Once(至少一次) 保证数据不丢失,但是不保证数据不重复
5.2 ACK设置为0: At Most Once(最多一次) 生产者只发送一次,保证不重复,但是会丢失
5.3 Kafka 0.11引入特性: 幂等性
At Least Once + 幂等性 = Exactly Once (数据去重后 便是 仅此一次)
5.4 设置幂等性: enable.idompotence=true
6. 消费者
6.1 消费方式
6.1.1 pull 消费者从broker拉取数据(Kafka选用)
Kafka没有数据时,消费者需要循环请求返回空数据,Kafka传入timeout决定不要频繁拉取空数据列表
6.1.2 push broker向消费者推送数据
broker决定发送速率,会造成发送太快,consumer来不及处理消息
6.2 分区分配策略
6.2.1 RoundRobin轮询(如果同一个消费者组内消费者订阅同一样的主题,可用;如果不一样会造成轮询分配错误,因为他把主题作为一个主体了)
6.2.2 Range(一个消费者组只能消费一个主题的同一个分区一次,其他消费者便不可以再消费)
7. Kafka高效读写
7.1 顺序写磁盘 600M/s > 随机写磁盘 100K/s ,省去了大量磁头寻址的时间
7.2 零拷贝
直接将你的读写交给操作系统去执行,不经过代码拷贝;
8. Zookeeper作用
负责broker管理,以及选主(ISR)管理
9. Kafka事务
幂等性+At least Once,只解决单次回话内数据不重复,当生产者挂了时,不保证重复消费
此时,需要引入事务:
9.1 Producer事务
引入一个Transaction ID,客户端保存一个事务ID和生产者ID的绑定,当生产挂掉重启后,根据事务ID获取之前的PID重新赋予,
而不是重新生成PID,此时便保证了幂等性的效果,从而达到精准一次性消费的效果。
9.2 Consumer事务
消费者事务修改offset,但是比较弱
6. KafkaAPI
1. 消息发送流程
Kafka的生产者Producer发送消息是异步;
main线程、sender线程两个线程,一个共享变量-RecordAccumulator
main线程将消息发送给RecordAccumulator,sender线程不断从RecordAccumulator拉取消息发送给Kafka broker
1.1 main线程:
sender(ProducerRecord) -> Interceptors -> Serializer -> Partitioner -> RecordAccumulator
1.2 相关参数
batch.size 只有数据积累到该量时,sender才会发送数据
linger.ms 当数据迟迟达不到batch.size时,sender等待linger.ms之后就会自动发送数据
7. Java Producer代码
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>2.2.0</version>
</dependency>
package com.dream.bigdata.bi.es.kafka;
import org.apache.kafka.clients.producer.*;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
public class MyKafkaProducer {
public static void main(String[] args) {
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "aaa:9092,bbb:9092,ccc:9092");
properties.put("acks", "all");
properties.put("retries", 3);
properties.put("batch.size", 16384);
properties.put("linger.ms", 1);
properties.put("buffer.memory", 33554432);
properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, "com.dream.bigdata.bi.es.kafka.MyPartitioner");
KafkaProducer producer = new KafkaProducer(properties);
for(int i=0; i<10; i++){
try {
producer.send(new ProducerRecord("qdd100", "qdd is comming" + i),
new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception exception) {
if(null == exception){
System.out.println(metadata.partition() + " ===> " + metadata.offset());
}else{
exception.printStackTrace();
}
}
}).get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
producer.close();
}
}
8. Java Consumer代码
package com.dream.bigdata.bi.es.kafka;
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 java.util.ArrayList;
import java.util.Arrays;
import java.util.Properties;
public class MyKafkaConsumer {
public static void main(String[] args) {
Properties properties = new Properties();
properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "aaa:9092,bbb:9092,ccc:9092");
properties.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, true);
properties.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, 1000);
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, "qdd");
KafkaConsumer consumer = new KafkaConsumer(properties);
consumer.subscribe(Arrays.asList("qdd100", "qdd", "qdd1"));
while (true){
ConsumerRecords<String, String> consumerRecords = consumer.poll(100);
for (ConsumerRecord<String, String> consumerRecord: consumerRecords) {
System.out.println(consumerRecord.key() + " ===> " + consumerRecord.value());
}
}
}
}
9. 拦截器(interceptor)案例
9.1 在发送之前给 消息添加时间戳前缀的 拦截器1
package com.dream.bigdata.bi.es.kafka;
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 MyAddTimestampInterceptor implements ProducerInterceptor<String, String> {
@Override
public void configure(Map<String, ?> configs) {
}
@Override
public ProducerRecord<String, String> onSend(ProducerRecord<String, String> record) {
String value = record.value();
ProducerRecord<String, String> producerRecord = new ProducerRecord<>(record.topic(), record.partition()
, record.key(), System.currentTimeMillis()+"_"+value);
return producerRecord;
}
@Override
public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
}
@Override
public void close() {
}
}
9.2 在逐条发送过程中给数据成功做统计,并在拦截器关闭前输出统计结果的拦截器2
package com.dream.bigdata.bi.es.kafka;
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 MyCountInterceptor implements ProducerInterceptor<String, String> {
int success;
int error;
@Override
public void configure(Map<String, ?> configs) {
}
@Override
public ProducerRecord<String, String> onSend(ProducerRecord<String, String> record) {
return record;
}
@Override
public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
if(exception != null){
error++;
}else{
success++;
}
}
@Override
public void close() {
System.out.println("success: " + success);
System.out.println("error: " + error);
}
}
9.3 创建一个带拦截器的生产者
package com.dream.bigdata.bi.es.kafka;
import org.apache.kafka.clients.producer.*;
import java.util.Arrays;
import java.util.Properties;
import java.util.concurrent.ExecutionException;
public class MyKafkaProducer {
public static void main(String[] args) {
Properties properties = new Properties();
properties.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "113.143.100.155:9092,113.143.100.140:9092,113.143.100.148:9092");
properties.put("acks", "all");
properties.put("retries", 3);
properties.put("batch.size", 16384);
properties.put("linger.ms", 1);
properties.put("buffer.memory", 33554432);
properties.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
properties.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, "com.dream.bigdata.bi.es.kafka.MyPartitioner");
properties.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG,
Arrays.asList("com.dream.bigdata.bi.es.kafka.MyAddTimestampInterceptor",
"com.dream.bigdata.bi.es.kafka.MyCountInterceptor"));
KafkaProducer producer = new KafkaProducer(properties);
for(int i=0; i<10; i++){
producer.send(new ProducerRecord("qdd100", "qdd", "qdd is comming" + i),
new Callback() {
@Override
public void onCompletion(RecordMetadata metadata, Exception exception) {
if(null == exception){
System.out.println(metadata.partition() + " ===> " + metadata.offset());
}else{
exception.printStackTrace();
}
}
});
}
producer.close();
}
}