Kafka 分割槽備份實戰

哥不是小蘿莉發表於2016-11-04

1.概述

  在 Kafka 叢集中,我們可以對每個 Topic 進行一個或是多個分割槽,併為該 Topic 指定備份數。這部分後設資料資訊都是存放在 Zookeeper 上,我們可以使用 zkCli 客戶端,通過 ls 和 get 命令來檢視後設資料資訊。通過 log.dirs 屬性控制訊息存放路徑,每個分割槽對應一個資料夾,資料夾命名方式為:TopicName-PartitionIndex,該資料夾下存放這該分割槽的所有訊息和索引檔案,如下圖所示:

2.內容

  Kafka 叢集在生產訊息入庫的時候,通過 Key 來進行分割槽儲存,按照相應的演算法,生產分割槽規則,讓所生產的訊息按照該規則分佈到不同的分割槽中,以達到水平擴充套件和負載均衡。而我們在消費這些訊息的時候,可以使用多執行緒來消費該 Topic 下的所有分割槽中的訊息。

  分割槽規則的制定,通過實現 kafka.producer.Partitioner 介面,該介面我們可以進行重寫,按照自己的方式去實現分割槽規則。如下,我們按照 Key 的 Hash 值,然後取模得到分割槽索引,程式碼如下所示:

package cn.hadoop.hdfs.kafka.partition;

import kafka.producer.Partitioner;
import kafka.utils.VerifiableProperties;

/**
 * @Date Nov 3, 2016
 *
 * @Author dengjie
 *
 * @Note 先 Hash 再取模,得到分割槽索引
 */
public class CustomerPartitioner implements Partitioner {

    public CustomerPartitioner(VerifiableProperties props) {
    }

    public int partition(Object key, int numPartitions) {
        int partition = 0;
        String k = (String) key;
        partition = Math.abs(k.hashCode()) % numPartitions;
        return partition;
    }

}

  在建立 Topic 的時候,若按照上述規則建立分割槽,分割槽數最後為 Brokers 的整數倍,這樣才能發揮其負載均衡的作用,比如:當前我們叢集節點由 3 個 Broker 組成,如下圖所示:

2.1 建立分割槽

  我們在建立分割槽的時候,可以通過 Kafka 提供的客戶端命令進行建立,如下,我們建立一個6分割槽,3備份的一個 Topic,命令如下所示:

./kafka-topics.sh --create --zookeeper k1:2181,k2:2181,k3:2181 --replication-factor 3 --partitions 6 --topic ke_test

  這裡需要注意的是,指定備份數的時候,備份數要小於等於 Brokers 數。否則建立失敗。在建立分割槽的時候,假設,我們只建立 2 個分割槽,而我們上述圖中, Brokers 有 3 個,會造成有一個 Broker 上沒有該 Topic 的分割槽,以致分佈不均。

2.2 分割槽入庫

  一般,我們在入庫訊息的時候,都有使用 Kafka 的 API,如下,我們使用生產 API ,按照上述的 Hash 取模規則,進行分割槽入庫,程式碼如下所示:

package cn.hadoop.hdfs.kafka.partition;

import java.util.List;
import java.util.Properties;

import cn.hadoop.hdfs.kafka.partition.data.FileRead;
import kafka.javaapi.producer.Producer;
import kafka.producer.KeyedMessage;
import kafka.producer.ProducerConfig;

/**
 * @Date Nov 3, 2016
 *
 * @Author dengjie
 *
 * @Note 按照先 Hash 再取模的規則,進行分割槽入庫
 */
public class PartitionerProducer {
    public static void main(String[] args) {
        producerData();
    }

    private static void producerData() {
        Properties props = new Properties();
        props.put("serializer.class", "kafka.serializer.StringEncoder");
        props.put("metadata.broker.list", "k1:9092,k2:9092,k3:9092");
        props.put("partitioner.class", "cn.hadoop.hdfs.kafka.partition.CustomerPartitioner");
        Producer<String, String> producer = new Producer<String, String>(new ProducerConfig(props));
        String topic = "ke_test";
        List<String> list = FileRead.readData();
        for (int i = 0; i < list.size(); i++) {
            String k = "key" + i;
            String v = new String(list.get(i));
            producer.send(new KeyedMessage<String, String>(topic, k, v));
            if (i == (list.size() - 1)) {
                return;
            }
        }
        producer.close();
    }
}

  這裡,我們分析發現,生產者在生產訊息入庫時,會按照 CustomerPartitioner 的規則,進行分割槽入庫,在入庫時,將 Key 先做 Hash,然後分割槽數取模(這裡分割槽數是 6).我們計算可以得到一下資訊:

hashCode("key0") % 6 = 1
hashCode("key1") % 6 = 2
hashCode("key2") % 6 = 3
hashCode("key3") % 6 = 4
hashCode("key4") % 6 = 5
hashCode("key5") % 6 = 0
// ... 以此迴圈

  按照該表述規則進行分割槽入庫。

2.3 分割槽入庫驗證

  接下里,我們通過 Kafka 的消費者 API 來驗證,在消費時,消費 Topic 各分割槽的詳情,程式碼如下所示:

package cn.hadoop.hdfs.kafka.partition;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import kafka.consumer.Consumer;
import kafka.consumer.ConsumerConfig;
import kafka.consumer.ConsumerIterator;
import kafka.consumer.KafkaStream;
import kafka.javaapi.consumer.ConsumerConnector;
import kafka.message.MessageAndMetadata;

/**
 * @Date Nov 3, 2016
 *
 * @Author dengjie
 *
 * @Note 通過 Kafka 的消費者 API 驗證分割槽入庫的訊息
 */
public class PartitionerConsumer {
    public static void main(String[] args) {
        String topic = "ke_test";
        ConsumerConnector consumer = Consumer.createJavaConsumerConnector(createConsumerConfig());
        Map<String, Integer> topicCountMap = new HashMap<String, Integer>();
        topicCountMap.put(topic, new Integer(1));
        Map<String, List<KafkaStream<byte[], byte[]>>> consumerMap = consumer.createMessageStreams(topicCountMap);
        KafkaStream<byte[], byte[]> stream = consumerMap.get(topic).get(0);
        ConsumerIterator<byte[], byte[]> it = stream.iterator();
        while (it.hasNext()) {
            MessageAndMetadata<byte[], byte[]> mam = it.next();
            System.out.println("consume: Partition [" + mam.partition() + "] Message: [" + new String(mam.message())
                    + "] ..");
        }

    }

    private static ConsumerConfig createConsumerConfig() {
        Properties props = new Properties();
        props.put("group.id", "group1");
        props.put("zookeeper.connect", "zk1:2181,zk2:2181,zk3:2181");
        props.put("zookeeper.session.timeout.ms", "40000");
        props.put("zookeeper.sync.time.ms", "200");
        props.put("auto.commit.interval.ms", "1000");
        props.put("auto.offset.reset", "smallest");
        return new ConsumerConfig(props);
    }
}

  這裡筆者只是驗證消費資料,若在實際生產線上,需將上述單執行緒消費改造成多執行緒消費,來提升處理訊息的能力。

2.4 驗證結果

  這裡,我們線執行生產者,讓其生產訊息,並分割槽入庫;然後,在啟動消費者,消費訊息驗證其結果,如下圖所示:

3.總結

  需要注意的是,分割槽數建議為 Brokers 的整數倍,讓其達到均勻分佈;備份數必須小於等於 Brokers。以及,多執行緒消費的控制,其執行緒數建議和分割槽數相等。

4.結束語

  這篇部落格就和大家分享到這裡,如果大家在研究學習的過程當中有什麼問題,可以加群進行討論或傳送郵件給我,我會盡我所能為您解答,與君共勉!

相關文章