Kafka API
一、消息发送流程
Kafka消息发送采用异步发送机制。
- main线程负责将消息经过拦截,序列化,分区,然后提交到RecordAccumulator
- RecordAccumulator是共享数据的数据结构
- sender线程则负责从共享数据中拉取数据
二、自定义生产者和消费者
使用就是配置+send/get
比较重要的是理解消息发送流程,如何考虑生产者和消费者同步问题,结合消息发送流程理解。
生产者
使用
1 acks=all 2 retries=1 3 batch.size=16384 4 linger.ms=1 5 buffer.memory=33554432 6 key.serializer=org.apache.kafka.common.serialization.StringSerializer 7 value.serializer=org.apache.kafka.common.serialization.StringSerializer 8 bootstrap.servers=hadoop102:9092 |
代码
1 public static void main(String[] args) throws ExecutionException, InterruptedException, IOException { 2 Properties props = new Properties(); 3 props.load(CustomProducer.class.getResourceAsStream("producer.properties")); 4 Producer<String, String> producer = new KafkaProducer<String, String>(props); 5 for (int i = 0; i < 10; i++) { 6 Future<RecordMetadata> future = producer.send(new ProducerRecord<String, String>("first", "" + i, "" + i), 7 // 回调时机是ack成功,即发送成功 8 new Callback() { 9 @Override 10 public void onCompletion(RecordMetadata recordMetadata, Exception e) { 11 if (e == null) { 12 System.out.println("success -> " + recordMetadata); 13 } else { 14 e.printStackTrace(); 15 } 16 } 17 }); 18 // 发送后阻塞,等到回复 19 RecordMetadata recordMetadata = future.get(); 20 System.out.println("send " + i); 21 } 22 producer.close(); 23 } |
同步讨论:
生成者的同步场景是,如果future不调get,那么事实上发送和接收是异步的。而如果调get,只有main收到kafka返回的ack和metaData才会 继续发送。生产者发送的去重是由kafka的幂等性+事务保证的,因此不用代码考虑。
消费者
1 bootstrap.servers=hadoop102:9092 2 group.id=test 3 enabel.auto.commit=false 4 auto.commit.interval.ms=5000 5 key.deserializer=org.apache.kafka.common.serialization.StringDeserializer 6 value.deserializer=org.apache.kafka.common.serialization.StringDeserializer |
代码
1 public static void main(String[] args) throws IOException { 2 Properties props = new Properties(); 3 props.load(CustomConsumer.class.getResourceAsStream("consumer.properties")); 4 KafkaConsumer<String, String> consumer = new KafkaConsumer<String, String>(props); 5 consumer.subscribe(Arrays.asList("first")); 6 while (true) { 7 ConsumerRecords<String, String> records = consumer.poll(100); 8 for (ConsumerRecord<String, String> record : records) { 9 System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value()); 10 } 11 consumer.commitSync(); 12 } 13 } |
同步讨论:
消费者的同步,主要考虑的是消费和offset提交,offset默认定时(5ms)提交一次,这样的话,一旦提交之前挂了,就会导致重复消费的问题,像上述代码可以改成手动提交,但是问题在于,上述提交之前也可以被打断。所以消费者实现同步,防止重复很困难,你需要保持消费和提交是同步的。但kafka挂不挂其实不是main线程说的算的,main挂不挂也不是kafka能控制的,于是消费和提交的原子性就比较难考虑。
可能的实现方式如下:
- 将record取出后暂时不消费,等待提交完成之后消费之
- offset记录在其他地方,但是这件事必须由下游框架保证消费和记录的原子性,这样的话,即使提交失败也可以重试
- 去重,在消费者使用map之类去重