Kafka是什么
是一个分布式的流式处理平台,具有高吞吐,可持久化,可水平扩展,支持流式数据处理等多种特性。
主要功能
• 消息系统:作为消息中间件,提供系统解耦,冗余存储,流量削锋,缓冲,异步通信,扩展性,可恢复性等功能;还支持消息顺序性保障。
• 存储系统:把消息持久化到磁盘,可以配置数据长期保留
• 流式处理平台:提供一个完整的流式处理类库
体系结构
• Zookeeper:管理Kafka元数据
• Broker:Kafka服务节点,存储数据
• Producer:生产者
• Consumer:消费者
Broker组成
• Topic是一个逻辑概念,一个Topic中由多个分区(Partition)组成,横跨多个broker存储
• 一个Broker中有多个Topic
• Partition有多副本机制,一主多从,leader负责读写,follower只负责与leader进行数据同步
• 消息被顺序追加到分区日志文件尾部,有一个offset偏移量,是消息在分区中的唯一标识
Partition分区
分区有多副本机制,一个Leader和多个Follower
Leader
负责处理读写请求
Follower
只负责与Leader副本的消息同步,当Leader副本出现故障时,从follower副本中重新选举新的Leader对外提供服务
Offset
消息在分区中的唯一标识,偏移量,通过offset保证消息在分区的顺序性
AR、ISR、OSR
- AR:分区中所有副本统称为AR
- ISR:与 Leader 副本保持一定程度同步的副本,包括 Leader 副本
- OSR:与 Leader 副本同步滞后的副本,不包括 Leader 副本
AR = ISR + OSR
正常情况下,所有的follower副本都应该与Leader副本保持一定的同步,即 AR = ISR
Leader 副本复制维护 ISR 集合中所有 Follower 副本的滞后状态
• 当 Follower 副本落后太多或失效,Leader 副本会把 Follower 副本从 ISR 集合中移到 OSR中
• 当 OSR 集合中有 Follower 追上了 Leader 副本,Leader 会把 Follower 从 OSR 集合中移到 ISR中
HW与LEO
HW:High Watermark高水位,标识了一个特定的消息偏移量,消费者只能拉取到这个offset之前的消息
LEO:Log End Offset,标识下一条待写消息的offset,等于日志中最后一条消息的offset值加1,分区ISR集合中每个副本都会维护自身的LEO。
如下图所示:
• Leader写入了5条消息,LEO为5
• Follower1同步了5条消息,LEO为5
• Follower2同步了4条消息,LEO为4
那么这个分区的 HW 取最小值4,消费者可以消费到 offset 为 0 至 3 之间的消息。
总结:Kafka的复制机制既不是完全的同步复制,也不是单纯的异步复制。而是基于ISR和高水位的方式,有效的权衡了数据的可靠性和性能之间的关系。
生产消费示例代码
生产者
public static void main(String[] args) throws InterruptedException {
// 创建 producer
KafkaProducer<String, String> producer = createProducer();
// 消息记录
ProducerRecord<String, String> record = new ProducerRecord<String, String>("hello-kafka",
"Message", "hello kafka message:" + Math.random());
producer.send(record, new Callback() {
public void onCompletion(RecordMetadata metadata, Exception exception) {
System.out.println(metadata.toString());
if (exception == null) {
System.out.println("消息发送成功");
} else {
System.out.println("消息发送异常");
exception.printStackTrace();
}
}
});
Thread.sleep(5000);
producer.close();
}
public static KafkaProducer<String, String> createProducer() {
Properties properties = new Properties();
properties.put("bootstrap.servers", "localhost:9092");
properties.put("acks", "all");
properties.put("retries", 1);
properties.put("batch.size", 16384);
properties.put("key.serializer", StringSerializer.class.getName());
properties.put("value.serializer", StringSerializer.class.getName());
properties.put(ProducerConfig.PARTITIONER_CLASS_CONFIG, DemoPartitioner.class);
properties.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, DemoInterceptor.class.getName());
return new KafkaProducer<String, String>(properties);
}
消费者
public static void main(String[] args) throws InterruptedException {
// 创建消费者
KafkaConsumer<String, String> consumer = createConsumer();
// 订阅topic
consumer.subscribe(Collections.singletonList("hello-kafka"));
while (true) {
ConsumerRecords<String, String> msgList = consumer.poll(2000);
if (msgList != null && msgList.count() > 0) {
for (ConsumerRecord<String, String> msg : msgList) {
System.out.println("接收到消息key:" + msg.key());
System.out.println("接收到消息value:" + msg.value());
System.out.println("接收到消息offset:" + msg.offset());
}
}
System.out.println("===================================");
Thread.sleep(10000);
}
}
public static KafkaConsumer<String, String> createConsumer() {
Properties properties = new Properties();
properties.put("bootstrap.servers", "localhost:9092");
properties.put("group.id", "groupA");
properties.put("enable.auto.commit", "true");
properties.put("auto.commit.interval.ms", "1000");
properties.put("auto.offset.reset", "earliest");
properties.put("session.timeout.ms", "30000");
properties.put("key.deserializer", StringDeserializer.class.getName());
properties.put("value.deserializer", StringDeserializer.class.getName());
return new KafkaConsumer<String, String>(properties);
}