kafka初探

简介

kafka是一个分布式的发布和订阅的消息系统。也就是消息的发布者把消息进行分类,然后发送到kafka上。而订阅者去读取也就是去消费一个特定类型的消息。当然在这样的一个系统中一般都会存在一个节点来做一个中心的作用。

生产者

kafka是流式的处理数据,那么这个数据的来源就是由生产者发送到kafka的。整个消息的发送流程如下图:

kafka初探_第1张图片
kafka1.jpg

大致上来看我们需要构建一个ProducerRecord 一个对象。然后把这个对象发送到对应的一个broker上。

首先一个ProducerRecord对象包含了topic,parition,key ,value。生产者需要把键值对象序列化成字节数据,这样才能够在网上传播。然后如果指定了分区,数据就会往分区写,如果没有指定就会根据这个key,把数据往指定的分区写。在成功写入之后,服务器响应会发回一个RecordMetaData对象,包含记录在分区里面的偏移量。接下来,我们来看看 producer.send() 怎么使用:

private Properties kafkaProps = new Properties();
kafkaProps.put("bootstrap.servers","broker1:9092,broker2:9092");
kafkaProps.put("key.serializer","org.apache.kafka.common.serialzation.StringSerializer");
kafkaProps.put("value.serializer","org.apache.kafka.common.serialzation.StringSerializer");
private KafkaProducer producer = new KafkaProducer(kafkaProps);


ProducerRecord record = new ProducerRecord<>("CustomerCountry",
    "Precision Products","France");

//这个发送方式,忽略了返回值,这是个异步方式
try{
    producer.send(record);
}catch(Exception e){
    e.printStackTrace();
}

// 同步发送
try{
    producer.send(record).get();
}catch(Exception e){
    e.printStackTrace();
}

//异步发送方式,但是可以处理异常情况

private  class DemoProducerCallback implements Callback{
    @Override
    public void onCompletion(RecordMetadata recordMetadata, Exception e){
        if(e != null){
            e.printStackTrace();
        }
    }
}

producer.send(record, new DemoProducerCallback());

这里,我们关注了发送者的两种发送方式:同步和异步。send方法会返回一个Future对象,如果使用这个对象的get方法,等到kafka的返回,那么会阻塞在这里。如果你不关注这个返回值得话,我们可以用完send就跑。然后使用回掉方法,特殊的关注一些异常就好了。

需要注意的是,消息是被放进缓冲区中,然后使用单独的线程发送到服务端。我们会等到缓冲区到达一定大小统一发送,发送者在发送的时候会把属于同一分区的消息依次发送,这样分区里面的消息就是有序的。关于缓冲区这个是有具体的参数可以配置的。使用的时候搜索一下就行。

最后,发送到broker的消息类型是字节数组,所以我们在发送之前需要把key-value进行相应的序列化,kafka有自带的一些序列化方法,假如你使用的是你自己定义类,那么你可以自己写序列化方法,也可以使用向thrift这样来生成数据。

消费者

消费者消费它订阅的那个topic的消息。因为我们消费之后可能要做一些耗时的处理工作,所以一个消费者可能消费不过来这么多消息,导致消息阻塞在broker上。但是Kafka是很好横向扩展的,横向扩展的意思是你多来几个消费者来消费同一个分区不就好了。那么这些消费同一个topic的消费者,就属于一个消费者群组。topic下面消息的最小单位应该是分区(partition),每个消费者消费的都是一个partition。如果说消费者的数量大于了partition的数量,那么会有一些消费者,消费不到数据。另一个是,kafka能保证的消息有序只有paritition这个级别了,不能保证topic级别的消息有序性。

假如说生产者崩了一个那也就崩了,只不过是没有消息进来而已。但是消费者崩了的话,可能消息就会堵在broker上,对整个业务造成影响。所以在消费的离开或者加入群组的时候。kafka会进行一项rebalance的操作,就是把分区在重新分配给消费者。所以就得知道在rebalance之前各个消费者消费patition的情况,得知道他们的offset。kafka 0.8 及之前都是把这个信息放在Zookeeper上。但实际上Zookeeper并不能承受住大规模的读写。所以经常rebalance失败,消息堵住,影响业务。但是kafka 0.9及之后,consumer在消费的时候会把偏移信息发送给一个特殊的topic->__consumer_offsets。这样broker就可以直接维护这些信息了。下面我们来看一下如何使用kafka consumer:

private Properties kafkaProps = new Properties();
kafkaProps.put("bootstrap.servers","broker1:9092,broker2:9092");
kafkaProps.put("group.id","CountryCounter");
kafkaProps.put("key.serializer","org.apache.kafka.common.serialzation.StringSerializer");
kafkaProps.put("value.serializer","org.apache.kafka.common.serialzation.StringSerializer");

KafkaConsumer consumer = new KafkaConsumer(kafkaProps);

consumer.subscribe(Collections.singleList("constomerCountries"));

try{
    while(true){
        // 消费者必须持续对Kafka进行轮询,不然就会被认为死亡,会触发rebalance
        ConsumerRecords records = consumer.poll(100);
        for(ConsumerRecordsrecord: records){
            log.debug("topic = %s, partition = %s, offset = %d, customer = %s,country = %s\n",
                record.topic(),record.partition(), record.offset(),record.key(),record.value());
        }
    }
}finally{
    consumer.close();
}

poll()这个方法是很重要的,消费者在每次轮询的时候都会检查是否该提交偏移量了,如果是,那么就会使用poll来提交一次偏移量到_consumer_offsets 这个topic上。当然了,提交的时机也是我们可以设置的配置。

当然,出了自动提交还有手动提交的api:commitSync 和 commitAsync。

还有两个特殊的api:

  • public void onPartitionsRevoked(Collection partitions)

  • public void onPartitionsAssigned(Collection partitions)

    这两个方法,都是在consumer.subscribe的时候调用,这样发生rebalance的时候,或者重新分配区之后和消费者读取消息之前被调用。可以执行这两个方法里面的操作。

最后,在另一个线程里面调用consumer.wakeup() 就可以优雅的退出consumer.poll() 轮询。

你可能感兴趣的:(kafka初探)