kafka的消费者

kafka消费者

1、消费方式

consumer采用pull(拉)模式从broker中读取数据。

push(推)模式很难适应消费速度不同的消费者,因为消息发送速率是由broker决定的。它的目标是尽可能以最快速度传递消息,但是这样很容易造成consumer来不及处理消息,典型的表现就是拒绝服务以及网络堵塞。而pull模式则可以根据consumer的消费能力以适当的速率消费消息。

pull模式不足之处是,如果kafka没有数据,消费者可能会陷入循环中,一直返回空数据,针对这一点,kafka的消费者在消费数据时会传入一个时长参数timeout,如果当前没有数据可供消费,consumer会等待一段时间之后再返回,这段时间即为timeout。

2、分区分配策略

一个consumer group 中又多个consumer,一个topic又多个partition,所以必然会涉及到partition的分配问题,即确定那个partition由那个consumer来消费。

kafka由两种分配策略,一个是RoundRobin,另外一个是Range(默认)。

range在分配的时候,首先看订阅的消费者是谁,如果订阅者里面有一个组的,会把消息的partition,分配给组内订阅这个topic的消费者。range是优先topic的,在看组的。

RoundRobin这个是按组来分的,不同消费者组只能消费到自己分配到的partition数据,其他的就不能消费到了,它因为是轮询的机制,所以消费者组之间消费的数据差不多。

下面是分区策略的源码:org.apache.kafka.clients.producer.internals.DefaultPartitioner

  /**
     * Compute the partition for the given record.
     *
     * @param topic The topic name
     * @param key The key to partition on (or null if no key)
     * @param keyBytes serialized key to partition on (or null if no key)
     * @param value The value to partition on or null
     * @param valueBytes serialized value to partition on or null
     * @param cluster The current cluster metadata
     */
    public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
        List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
        int numPartitions = partitions.size();
        if (keyBytes == null) {
            int nextValue = nextValue(topic);
            List<PartitionInfo> availablePartitions = cluster.availablePartitionsForTopic(topic);
            if (availablePartitions.size() > 0) {
                int part = Utils.toPositive(nextValue) % availablePartitions.size();
                return availablePartitions.get(part).partition();
            } else {
                // no partitions are available, give a non-available partition
                return Utils.toPositive(nextValue) % numPartitions;
            }
        } else {
            // hash the keyBytes to choose a partition
            return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
        }
    }

我们可以自己实现一个分区规则:只要我们实org.apache.kafka.clients.producer.Partitioner

 

3、offset的维护

由于consumer在消费过程中可能会出现断电宕机等故障,consumer恢复后,需要从故障前的位置的继续消费,所以consumer需要实时记录自己消费到了那个offset,以便故障恢复后继续消费

 

4、kafka高效读写数据

4.1、顺序写磁盘

kafka的producer生产数据,要写入到log文件中,写的过程是一直追加到文件末端,为顺序写。官网有数据表明,同样的磁盘,顺序写能到600M/s,而随机写只有100K/s。这与磁盘的机械机构有关,顺序写之所以快,是因为其省去了大量的磁头寻址的时间。

4.2、零复制技术(零复制)

5、zookeeper在kafka中的作用

kafka集群中有一个broker会被选举为controller,负责管理集群broker的上下线,所有topic的分区副本分配和leader选举等工作。controller的管理工作都是依赖于zookeeper的。

以下为partition的leader选举过程:

上图是kafka集群注册到zookeeper上,下图可以看到不同分区的leader,和isr的服务器

下面是在3台kafka集群上,建一个topic=TOPIC_SECOND_MESSAGE 两个分区,两个备份(备份数不能多余服务器数,也就是多少台kafka服务,分区可以是多台的)
./kafka-topics.sh --create --zookeeper localhost:2181 --topic TOPIC_SECOND_MESSAGE --partitions 2 --replication-factor 2

下图是3台kafka集群里面选出来的leader

 

6、kafka的事物

kafka从0.11版本开始引入了事物支持,事物可以保证kafka在Exactly Once 语义的语义的基础上,生产和消费可以跨分区和回话,要么全部成功,要么全部失败。

6.1、producer事物

为了实现跨分区会话的事务,需要引入一个全局唯一的Transaction ID,并将Producer获得的PID和Transaction ID绑定。这样当producer重启后就可以通过正在进行的Transaction ID获得原来的PID。

为了管理Transaction,kafka引入了一个新的组件Transaction coordinator。producer'就是通过和Transaction coordinator交互获得Transaction ID对应的任务状态。transaction coordinator还负责将事务所有写入kafka的一个内部topic,这样即使整个服务器重启,由于事务状态得到保证,进行中的事务状态可以得到恢复,从而继续进行。

6.2、consumer事务

上述事务机制主要是从producer方面考虑,对于consumer而言,事务的保证就会相对较弱,尤其无法保证commit的信息被精确消费。这是由于consumer可以通过offset访问任意信息,而且不同的segment file生命周期不同,同一个事务的消息可能会出现重启后被删除的情况。

 

7、kafka API

7.1、kafka消息发送流程

kafka的producer发送消息采用的是异步发送的方式。在消息发送的过程中,涉及到了两个线程-main线程和sender线程,以及一个线程共享变量-recordAccumulator。main线程将消息发送给RecordAccumulator,sender线程不断从RecordAccumulotor中拉取消息发送到kafka broker

相关参数:

  • batch.size:只有数据积累到batch.size之后,sender才会发送数据。
  • linger.ms:如果数据迟迟未打到batch.size等待linger.time之后就会发送数据。

Interceptors:拦截器

Serializer:序列化

Partitioner:分区 ,自己写一个

源码:org.apache.kafka.clients.producer.KafkaProducer

    @Override
    public Future<RecordMetadata> send(ProducerRecord<K, V> record, Callback callback) {
        // intercept the record, which can be potentially modified; this method does not throw exceptions
//     1、拦截器
        ProducerRecord<K, V> interceptedRecord = this.interceptors.onSend(record);
        return doSend(interceptedRecord, callback);
    }

private Future<RecordMetadata> doSend(ProducerRecord<K, V> record, Callback callback) {
        TopicPartition tp = null;
        try {
            throwIfProducerClosed();
            // first make sure the metadata for the topic is available
            ClusterAndWaitTime clusterAndWaitTime;
            try {
                clusterAndWaitTime = waitOnMetadata(record.topic(), record.partition(), maxBlockTimeMs);
            } catch (KafkaException e) {
                if (metadata.isClosed())
                    throw new KafkaException("Producer closed while send in progress", e);
                throw e;
            }
            long remainingWaitMs = Math.max(0, maxBlockTimeMs - clusterAndWaitTime.waitedOnMetadataMs);
            Cluster cluster = clusterAndWaitTime.cluster;
            byte[] serializedKey;
            try {
// 序列化
                serializedKey = keySerializer.serialize(record.topic(), record.headers(), record.key());
            } catch (ClassCastException cce) {
                throw new SerializationException("Can't convert key of class " + record.key().getClass().getName() +
                        " to class " + producerConfig.getClass(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG).getName() +
                        " specified in key.serializer", cce);
            }
            byte[] serializedValue;
            try {
                serializedValue = valueSerializer.serialize(record.topic(), record.headers(), record.value());
            } catch (ClassCastException cce) {
                throw new SerializationException("Can't convert value of class " + record.value().getClass().getName() +
                        " to class " + producerConfig.getClass(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG).getName() +
                        " specified in value.serializer", cce);
            }
// 分区规则
            int partition = partition(record, serializedKey, serializedValue, cluster);
            tp = new TopicPartition(record.topic(), partition);

            setReadOnly(record.headers());
            Header[] headers = record.headers().toArray();

            int serializedSize = AbstractRecords.estimateSizeInBytesUpperBound(apiVersions.maxUsableProduceMagic(),
                    compressionType, serializedKey, serializedValue, headers);
            ensureValidRecordSize(serializedSize);
            long timestamp = record.timestamp() == null ? time.milliseconds() : record.timestamp();
            log.trace("Sending record {} with callback {} to topic {} partition {}", record, callback, record.topic(), partition);
            // producer callback will make sure to call both 'callback' and interceptor callback
            Callback interceptCallback = new InterceptorCallback<>(callback, this.interceptors, tp);

            if (transactionManager != null && transactionManager.isTransactional())
                transactionManager.maybeAddPartitionToTransaction(tp);

            RecordAccumulator.RecordAppendResult result = accumulator.append(tp, timestamp, serializedKey,
                    serializedValue, headers, interceptCallback, remainingWaitMs);
            if (result.batchIsFull || result.newBatchCreated) {
                log.trace("Waking up the sender since topic {} partition {} is either full or getting a new batch", record.topic(), partition);
                this.sender.wakeup();
            }
            return result.future;
            // handling exceptions and record the errors;
            // for API exceptions return them in the future,
            // for other exceptions throw directly
        .....
        } catch (Exception e) {
            // we notify interceptor about all exceptions, since onSend is called before anything else in this method
            this.interceptors.onSendError(record, tp, e);
            throw e;
        }
    }

同步发送:(有序)用feture的get方法来阻塞主线程来达到同步的效果

同步发送的意思就是,一条消息发送之后,会阻塞当前线程,直至返回ack。

由于send方法返回的是一个Future对象,根据Future对象的特点,我们也可以实现同步发送的效果,只需在调用Future对象的get方法即可

 

 

如何重新消费数据?spring.kafka.consumer.auto-offset-reset

第一种方案:换组,把offset参数auto.offset.reset设置为earliest(最早的offset,由于数据只保留7天的)参数就可以了。

为什么要换组:是因为我们原来的那个组已经消费了,它的offset是不能在修改了,只能建一个新组才能从开始消费数据(消费保留下来的数据,kafka默认只保留7天的数据)。

auto.offset.reset有两个值:

  • earliest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,从头开始消费,或者没有有效的offset时,设置从头开始消费
  • latest:当各分区下有已提交的offset时,从提交的offset开始消费;无提交的offset时,消费新产生的该分区下的数据

当设置成earliest时:(新加一个组,KAFKA_GROUP_ID_THIRD)

原来的组test-hello-group是消费完的,拉去消费的offset:

TOPIC_SECOND_MESSAGE-0 to the committed offset FetchPosition{offset=6, offsetEpoch=Optional[0], currentLeader=LeaderAndEpoch{leader=localhost:9092 (id: 1 rack: null), epoch=1}

KAFKA_GROUP_ID_THIRD:这个会重头消费一遍 

[Consumer clientId=consumer-3, groupId=test-hello-group] Setting offset for partition TOPIC_SECOND_MESSAGE-0 to the committed offset FetchPosition{offset=6, offsetEpoch=Optional[0], currentLeader=LeaderAndEpoch{leader=localhost:9092 (id: 1 rack: null), epoch=1}}
[Consumer clientId=consumer-2, groupId=test-hello-group] Setting offset for partition TOPIC_FIRST_MESSAGE-0 to the committed offset FetchPosition{offset=34, offsetEpoch=Optional[0], currentLeader=LeaderAndEpoch{leader=localhost:9093 (id: 2 rack: null), epoch=1}}
[Consumer clientId=consumer-3, groupId=test-hello-group] Setting offset for partition TOPIC_SECOND_MESSAGE-1 to the committed offset FetchPosition{offset=6, offsetEpoch=Optional[0], currentLeader=LeaderAndEpoch{leader=localhost:9093 (id: 2 rack: null), epoch=3}}
[Consumer clientId=consumer-1, groupId=KAFKA_GROUP_ID_THIRD] Successfully joined group with generation 1
[Consumer clientId=consumer-1, groupId=KAFKA_GROUP_ID_THIRD] Setting newly assigned partitions: TOPIC_SECOND_MESSAGE-0, TOPIC_SECOND_MESSAGE-1
[Consumer clientId=consumer-1, groupId=KAFKA_GROUP_ID_THIRD] Found no committed offset for partition TOPIC_SECOND_MESSAGE-0
[Consumer clientId=consumer-1, groupId=KAFKA_GROUP_ID_THIRD] Found no committed offset for partition TOPIC_SECOND_MESSAGE-1
[Consumer clientId=consumer-1, groupId=KAFKA_GROUP_ID_THIRD] Resetting offset for partition TOPIC_SECOND_MESSAGE-1 to offset 0.
[Consumer clientId=consumer-1, groupId=KAFKA_GROUP_ID_THIRD] Resetting offset for partition TOPIC_SECOND_MESSAGE-0 to offset 0.

当设置成latest:(新加一个组KAFKA_GROUP_ID_FOURTH)

他不会重新消费,只会拉去现在消费的offset,

[Consumer clientId=consumer-4, groupId=KAFKA_GROUP_ID_FOURTH] Resetting offset for partition TOPIC_SECOND_MESSAGE-0 to offset 6

2019-12-03 17:40:57.320  INFO 65835 --- [ntainer#3-0-C-1] o.a.k.c.c.internals.ConsumerCoordinator  : [Consumer clientId=consumer-2, groupId=KAFKA_GROUP_ID_THIRD] Setting offset for partition TOPIC_SECOND_MESSAGE-0 to the committed offset FetchPosition{offset=6, offsetEpoch=Optional[0], currentLeader=LeaderAndEpoch{leader=localhost:9092 (id: 1 rack: null), epoch=1}}
2019-12-03 17:40:57.320  INFO 65835 --- [ntainer#2-0-C-1] o.a.k.c.c.internals.ConsumerCoordinator  : [Consumer clientId=consumer-1, groupId=test-hello-group] Setting offset for partition TOPIC_SECOND_MESSAGE-0 to the committed offset FetchPosition{offset=6, offsetEpoch=Optional[0], currentLeader=LeaderAndEpoch{leader=localhost:9092 (id: 1 rack: null), epoch=1}}
2019-12-03 17:40:57.321  INFO 65835 --- [  restartedMain] o.a.kafka.common.utils.AppInfoParser     : Kafka version: 2.3.1
2019-12-03 17:40:57.321  INFO 65835 --- [ntainer#2-0-C-1] o.a.k.c.c.internals.ConsumerCoordinator  : [Consumer clientId=consumer-1, groupId=test-hello-group] Setting offset for partition TOPIC_SECOND_MESSAGE-1 to the committed offset FetchPosition{offset=6, offsetEpoch=Optional[0], currentLeader=LeaderAndEpoch{leader=localhost:9093 (id: 2 rack: null), epoch=3}}
2019-12-03 17:40:57.321  INFO 65835 --- [  restartedMain] o.a.kafka.common.utils.AppInfoParser     : Kafka commitId: 18a913733fb71c01
2019-12-03 17:40:57.321  INFO 65835 --- [ntainer#3-0-C-1] o.a.k.c.c.internals.ConsumerCoordinator  : [Consumer clientId=consumer-2, groupId=KAFKA_GROUP_ID_THIRD] Setting offset for partition TOPIC_SECOND_MESSAGE-1 to the committed offset FetchPosition{offset=6, offsetEpoch=Optional[0], currentLeader=LeaderAndEpoch{leader=localhost:9093 (id: 2 rack: null), epoch=3}}
2019-12-03 17:40:57.321  INFO 65835 --- [  restartedMain] o.a.kafka.common.utils.AppInfoParser     : Kafka startTimeMs: 1575366057321
2019-12-03 17:40:57.321  INFO 65835 --- [  restartedMain] o.a.k.clients.consumer.KafkaConsumer     : [Consumer clientId=consumer-4, groupId=KAFKA_GROUP_ID_FOURTH] Subscribed to topic(s): TOPIC_SECOND_MESSAGE
2019-12-03 17:40:57.321  INFO 65835 --- [  restartedMain] o.s.s.c.ThreadPoolTaskScheduler          : Initializing ExecutorService
2019-12-03 17:40:57.327  INFO 65835 --- [ntainer#1-0-C-1] org.apache.kafka.clients.Metadata        : [Consumer clientId=consumer-4, groupId=KAFKA_GROUP_ID_FOURTH] Cluster ID: NYQYLPj4SVeaxs0_8Xgo6g
2019-12-03 17:40:57.328  INFO 65835 --- [ntainer#1-0-C-1] o.a.k.c.c.internals.AbstractCoordinator  : [Consumer clientId=consumer-4, groupId=KAFKA_GROUP_ID_FOURTH] Discovered group coordinator localhost:9092 (id: 2147483646 rack: null)
2019-12-03 17:40:57.328  INFO 65835 --- [ntainer#1-0-C-1] o.a.k.c.c.internals.ConsumerCoordinator  : [Consumer clientId=consumer-4, groupId=KAFKA_GROUP_ID_FOURTH] Revoking previously assigned partitions []
2019-12-03 17:40:57.328  INFO 65835 --- [ntainer#1-0-C-1] o.s.k.l.KafkaMessageListenerContainer    : KAFKA_GROUP_ID_FOURTH: partitions revoked: []
2019-12-03 17:40:57.328  INFO 65835 --- [ntainer#1-0-C-1] o.a.k.c.c.internals.AbstractCoordinator  : [Consumer clientId=consumer-4, groupId=KAFKA_GROUP_ID_FOURTH] (Re-)joining group
2019-12-03 17:40:57.340  INFO 65835 --- [ntainer#1-0-C-1] o.a.k.c.c.internals.AbstractCoordinator  : [Consumer clientId=consumer-4, groupId=KAFKA_GROUP_ID_FOURTH] Successfully joined group with generation 1
2019-12-03 17:40:57.341  INFO 65835 --- [ntainer#1-0-C-1] o.a.k.c.c.internals.ConsumerCoordinator  : [Consumer clientId=consumer-4, groupId=KAFKA_GROUP_ID_FOURTH] Setting newly assigned partitions: TOPIC_SECOND_MESSAGE-0, TOPIC_SECOND_MESSAGE-1
2019-12-03 17:40:57.342  INFO 65835 --- [ntainer#1-0-C-1] o.a.k.c.c.internals.ConsumerCoordinator  : [Consumer clientId=consumer-4, groupId=KAFKA_GROUP_ID_FOURTH] Found no committed offset for partition TOPIC_SECOND_MESSAGE-0
2019-12-03 17:40:57.342  INFO 65835 --- [ntainer#1-0-C-1] o.a.k.c.c.internals.ConsumerCoordinator  : [Consumer clientId=consumer-4, groupId=KAFKA_GROUP_ID_FOURTH] Found no committed offset for partition TOPIC_SECOND_MESSAGE-1
2019-12-03 17:40:57.352  INFO 65835 --- [  restartedMain] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2019-12-03 17:40:57.353  INFO 65835 --- [ntainer#1-0-C-1] o.a.k.c.c.internals.SubscriptionState    : [Consumer clientId=consumer-4, groupId=KAFKA_GROUP_ID_FOURTH] Resetting offset for partition TOPIC_SECOND_MESSAGE-0 to offset 6.
2019-12-03 17:40:57.353  INFO 65835 --- [ntainer#1-0-C-1] o.a.k.c.c.internals.SubscriptionState    : [Consumer clientId=consumer-4, groupId=KAFKA_GROUP_ID_FOURTH] Resetting offset for partition TOPIC_SECOND_MESSAGE-1 to offset 6.
2019-12-03 17:40:57.354  INFO 65835 --- [ntainer#1-0-C-1] o.s.k.l.KafkaMessageListenerContainer    : KAFKA_GROUP_ID_FOURTH: partitions assigned: [TOPIC_SECOND_MESSAGE-0, TOPIC_SECOND_MESSAGE-1]

 

消费者没有设置自动提交消费信息会出现什么情况

参数:spring.kafka.consumer.enable-auto-commit

  • 1、当消费者把自动提交消费消息设置成false:当消费者那个线程会一直拉取数据,只有在第一次的时候才会从kafka那获取消费的offset(0.9版本的kafka把消费记录保存在kafka服务器上,以前保存在zk上),之后在线程不重启的情况下,都不会在去获取消费的offset,也不会跟新消费的offset到kafka上,下次在启动在重新消费一次。
  • 2、当消费者把自动提交消费消息设置成true:消费了,之后就会提交offset到kafka。

 

手动提交offset

虽然自动提交offset十分简介便利,但由于其实基于时间提交的。开发人员难以把握offset提交的时机,因此kafka还提供了手动提交的offset的api。

手动我提交offset的方法有两种,分别是commitSync(同步提交)和commitAsync(异步提交);两者的相同是,都会将本次poll的一批数据最高的偏移量提交;不同点是,commitSync阻塞当前线程,一直到提交成功,并且自动失败重试(由不可控因素导致,也会出现提交失败);而commitAsync则没有失败重试机制,故有可能提交失败。

 

自定义存储offset

kafka0.9版本之前,offset储存在zookeeper,0.9版本以及之后,默认将存储在kafka的一个内置的topic中。除此之外,kafka还可以选择自定义存储的offset。

offset的维护是相当于繁琐的,因此需要考虑到消费者的rebalace。

当有新的消费者加入消费者组,已有的消费者推出消费者组成或者所订阅的主题的分区丰盛变化,就会触发到分区的重新分配,重新分配的过程叫做rebalance

消费者发生rebalace之后,每个消费者消费的分区就会变化。因此消费者要首先获取自己被重新分配的分区,并且定位到每个分区最近提交的offset位置继续消费。要实现自定义的offset,需要借助ConsumerRebalaceListener;

 

自定义拦截器Interceptor

拦截器原理:

producer拦截器(interceptor)是在kafka0.10版本引入的,主要用于实现clients端的定制化控制逻辑。

对于producer而言,interceptor使得用户在消息发送前以及producer回调逻辑前有机会对消息做一些定制化需求,比如修改消息等。同时,producer允许用户指定多个interceptor按序作用于同一条消息从而形成一个拦截器链(interceptor chain)。interceptor的实现接口是org.apache.kafka.clients.producer.ProducerInterceptor,定义的方法包含:

下面就是实现拦截器的方式:

@Component
public class KafkaProducerInterceptor implements ProducerInterceptor {

    @Override
    public ProducerRecord onSend(ProducerRecord record) {
        System.out.println("-----KafkaProducerInterceptor---->拦截器record"+ JSON.toJSONString(record));
        return record;
    }

    @Override
    public void onAcknowledgement(RecordMetadata metadata, Exception exception) {

    }

    @Override
    public void close() {

    }

    @Override
    public void configure(Map<String, ?> configs) {

    }
}

1、configure(Map<String, ?> configs)

获取配置的信息和初始化数据时调用

2、close()

关闭interceptor,主要用于执行一些资源清理工作

3、onAcknowledgement(RecordMetadata metadata, Exception exception)

该方法会在消息从RecordAccumulator成功发送到kafka broker之后,或者在发送过程中失败调用。并且通常都是在producer回调逻辑触发之前,onAcknowledgement运行在producer的io线程中,因此不要在该方法中放入很重的逻辑,否则会拖慢producer的消息发送效率。

4、ProducerRecord onSend(ProducerRecord record)

该方法封装进kafkaProducer.send方法中,即它运行在用户主线程中。producer确保在消息被序列化以及计算分区前调用该方法。用户可以在该方法中对消息做任何操作,但最好保证不要修改消息所属但topic和分区,否则会影响目标分区的计算

如前所述,interceptor可能被运行在多个线程中,因此在具体实现时需要用户自行确保线程安全。另外倘若指定了多个interceptor,则producer将按照指定顺序调用它们,并仅仅是捕获每个interceptor可能抛出的异常记录到日志中而非在向上传递。这在使用过程中要特别注意。

 

Kafka的监控:

eagle安装,需要的时候在进行查询。

很好的,可以监控消费的情况,消费失败,重试次数等等

 

kafka压力测试

kafka官方自带压力测试脚本(kafka-consumer-perf-test.sh、kafka-producer-perf-test.sh)

kafka压力测试时,可以查看那个地方出现了瓶颈(cpu,内存,网络IO)。一般都是网络IO达到瓶颈

kafka的机器数量

  • kafka机器数量=2*(峰值生产速度*副本数/100)+1

kafka的日志保存时间

  • 168h=7天,这个是可以修改的

kafka消息数据积压,kafka消费能力不足怎么处理?

  • 1、如果是kafka消费能力不足,则可以考虑topic的分区数,并且同时提升消费组的消费者数量,消费者数=分区数。(两者缺一不可)
  • 2、如果是下游的数据处理不及时:提高每批次拉取的数量。批次拉取数据过少(拉取数据/处理时间<生产速度),使处理的数据小于生产的数据,也会造成数据积压。max.poll.records=500(默认拉取的值)

 

下面是一些问题

1、kafka中的ISR(InSyncRepli)、OSR(OutSyncRepli)、AR(AllRepli)代表什么?

  • AR=OSR+ISR

2、Kafka中的HW、LEO等代表什么?

  • HW:highwater,代表的是kafka集群里面最小的offset的值,也代表了消费者能消费的最大值offset
  • LEO:log end offset每个kafka的broker的最大offset

3、kafka中是怎么体现消息顺序性的?

  • 分区内有序

4、kafka中分区器,序列化器,拦截器是否了解?它们之间的处理顺序是什么?

  • 首先执行的是拦截器-》序列化器-》分区器

5、kafka生产者客户端的整体结构是什么样子的?使用了几个线程来处理?分别是什么?

是两个线程来处理的,一个是main线程,另外一个是send线程。

6、“消费组中的消费者个数如果超过topic的分区,那么就会有消费者消费不到数据”这句话是否正确?

  • 正确。

7、消费者提交位移时提交的是当前消费者的最新消息的offset还是offset+1?

  • offset+1

8、有那些情形会造成重复消费

  • 先处理数据,后提交offset

9、那些情景会造成消息漏消费?

  • 先提交offset,后处理数据

10、当你使用kafka-topic.sh 创建(删除)了一个topic之后,kafka背后会执行什么逻辑?

  • 10.1、会在zookeeper中的/brokers/topics节点下创建一个新的topic节点,如:/brokers/topics/one
  • 10.2、触发controller的监听程序
  • 10.3、kafka controller负责topic的创建工作,并更新metadata cache

11、topic的分区数可不可以增加?如果可以怎么增加?如果不可以,那又是为什么?

  • 可以增加

12、topic的分区数可不可以减少?如果可以怎么减少?如果不可以,那又是为什么?

  • 不可以减少,因为老的数据没有办法处理

13、kafka又内部的topic吗?如果有是什么?有什么作用?

  • 有,__consumer_offset,给普通的消费者来存储消费的offset

14、kafka分区分配的概念

  • range这个是按照主题来分的,这个是默认的,它是分配给订阅过这个topic的消费组来消费
  • roundRabin是按照组来分的,轮询的分配到不同的组

15、简述kafka的日志目录结构

  • topic--》多个protation-》segment->.log文件(存储数据的)和.index文件
  • 首先我们找到index文件,通过二分查找法找到具体log文件里面的偏移量offset

16、聊一聊kafka controller的作用?

17、kafka中有些地方需要选举?这些地方的选举策略有那些?

  • 17.1、kafka的controller的选举,也就是kafka集群服务器的leader,选举方式:抢资源,谁抢到就是谁的
  • 17.2、kafka里面partation分区的leader选举,这个是在ISR里面进行选举的

18、失效副本是指什么?有那些应对措施?

19、kafka的那些设计让它如此高的性能?

  • 分布式的、顺序写磁盘、零拷贝

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值