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