Kafka 生產者與可靠性保證ACK(2)

生產者消息發送流程

消息發送的總體流程,生產端主要由兩個線程協調運行。分別是main線程和sender線程(發送線程)。java

在Kafka(2.6.0版本)源碼中,能夠看到。spring

源碼地址: 
kafka\clients\src\main\java\org.apache.kafka.clients.producer.KafkaProducer.java

測試入口:
KafkaProducerTest.testInvalidGenerationIdAndMemberIdCombinedInSendOffsets()

在建立KafkaProducer時,在430建立了一個Sender對象,而且啓動了一個IO線程。apache

this.errors = this.metrics.sensor("errors");
this.sender = newSender(logContext, kafkaClient, this.metadata);
String ioThreadName = NETWORK_THREAD_PREFIX + " | " + clientId;
this.ioThread = new KafkaThread(ioThreadName, this.sender, true);
this.ioThread.start();

interceptor

interceptor的做用是實現消息的定製化,相似:spring Interceptor 、MyBatis的插件、Quartz的監聽器。服務器

@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
     ProducerRecord<K, V> interceptedRecord = this.interceptors.onSend(record);
    return doSend(interceptedRecord, callback);
}

可經過實現org.apache.kafka.clients.producer.ProducerInterceptor接口開發自定義器。網絡

簡單自定義例子:app

public class CustomInterceptor implements ProducerInterceptor<String, String> {
    // 發送消息時觸發
    @Override
    public ProducerRecord<String, String> onSend(ProducerRecord<String, String> record) {
        System.out.println("發送消息時觸發");
        return record;
    }

    // 收到服務端的ACK時觸發
    @Override
    public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
        System.out.println("消息被服務端接收");
    }

    @Override
    public void close() {
        System.out.println("生產者關閉");
    }

    // 用鍵值對配置時觸發
    @Override
    public void configure(Map<String, ?> configs) {
        System.out.println("configure...");
    }
}

// 生產者中添加
List<String> interceptors = new ArrayList<>();
interceptors.add("com.freecloud.plug.kafka.interceptor.CustomInterceptor");
props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG, interceptors);

序列化

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);
}

在kafka針對不一樣的數據類型作了相應的序列化工具。如需自定義實現org.apache.kafka.common.serialization.Serializer接口。分佈式

路由器(分區器)

int partition = partition(record, serializedKey, serializedValue, cluster);

消息累加器

RecordAccumulator.RecordAppendResult result = accumulator.append(tp, timestamp, serializedKey,
                    serializedValue, headers, interceptCallback, remainingWaitMs, true, nowMs);

// RecordAccumulator本質是一個ConcurrentMap:ide

private final ConcurrentMap<TopicPartition, Deque<ProducerBatch>> batches;

一個partition一個Batch。batch滿了以後,會喚醒Sender線程發送消息。工具

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();
}

數據可靠性保證ACK

生產者發送一條消息到服務器如何確保服務器收到消息?若是在發送過程當中網絡出了問題,或者kafka服務器接收的時候出了問題,這個消息發送失敗了,生產者是不知道的。性能

因此kafka服務端須要使用一種響應客戶端的方式,只有在服務端確認之後,生產者才發一下條消息,不然從新發送數據。

那何時纔算接收成功?由於消息存儲在不一樣的broker裏,因此是在寫入到磁盤以後響應生產者。

服務端響應策略

在分佈式場景中,只有一個broker寫入成功仍是不夠的,若是有多個副本,follower也要寫入成功才行。

服務端發送ACK給生產者通常有如下幾種策略。

  1. 只要leader成功接收就能夠,會產生副本與leader不一致狀況,若是leader出問題可能會出現數據丟失風險。客戶端等待時間最短。

  2. 須要半數以上的follower節點完成同步,這種方式客戶端等待的時間比上邊稍長一點,但能夠確保大部分場景不出問題。

  3. 須要全部follwer所有完成同步,客戶端等待時間最長,但若是節點掛掉的影響相對來講最小,由於全部節點的數據都是完整的。

kafka的ACK應答機制就使用了以上三種方式。能夠經過配置acks參數進行配置。

ISR (in-sync replica set)

上邊第三種方式若是保證全部follower同步數據成功?

假設leader接收到數據,全部follower都開始同步數據,可是有一個follower出了問題,沒辦法從leader同步數據,按這個規則,leader就要一直等待,沒法返回ack,成了害羣之馬。

因此咱們該若是解決這個問題呢?接下來咱們把規則修改一下,不是全部follower都有權利讓leader等待,而是隻有那些正常工做的follower同步數據的時候纔會等待。

把那些正常和leader保持同步的副本維護起來,放到一個動態set裏,這個就叫作in-sync replica set (ISR)。只要ISR裏面的follower同步完數據以後,就能夠給客戶端發送ACK。

對於常常出問題的follower能夠設定replica.lag.time.max.ms=30(默認30秒),若是超過配置時間纔會從isr中剔除。

參數 說明
acks = 0 Producer不等待broker的ack,brokder一接收到還沒寫入磁盤就返回,當brokder故障時有可能丟失數據;
acks = 1 Producer等待brokder的ack,partition的leader成功落盤後返回ack,若是在follower同步成功前leader故障,將會丟失數據;
acks = -1 producer等待brokder的ack,partition的leader和follower所有成功落盤後才返回ack;

以上三種機制性能依次遞減(producer吞吐量下降),數據健壯性則依次遞增。實際開發中可根據不一樣場景選擇不一樣的策略。

相關文章
相關標籤/搜索