API
Producer API
此處只簡介一個procedure的例子
生產類是用來創建新消息的主題和可選的分區。
如果使用Java你需要包括幾個包和支持類:
import kafka.javaapi.producer.Producer;
import kafka.producer.KeyedMessage;
import kafka.producer.ProducerConfig;
第一步首先定義producer如何找到集羣,如何序列化消息和爲消息選擇適合的分區。下面吧這些定義在一個標準的JAVA Properties類中
Properties props = new Properties(); props.put("metadata.broker.list","broker1:9092,broker2:9092"); props.put("serializer.class","kafka.serializer.StringEncoder"); props.put("partitioner.class","example.producer.SimplePartitioner"); props.put("request.required.acks","1"); ProducerConfig config = new ProducerConfig(props);
1. metadata.broker.list 定義了生產者如何找到一個或多個Broker去確定每個topic的Leader。這不需要你設置集羣中全套的brokers但至少應包括兩個,第一個經紀人不可用可以替換。不需要擔心需要指出broker爲主題的領袖(分區),生產者知道如何連接到代理請求元數據並連接到正確的broker。
2. 第二個屬性“序列化類定義“。定義使用什麼序列化程序傳遞消息。在我們的例子中,我們使用一個卡夫卡提供的簡單的字符串編碼器。請注意,encoder必須和下一步的keyedmessage使用相同的類型
可以適當的定義"key.serializer.class"根據key改變序列化類。默認的是與"serializer.class"相同
3. 第三個屬性partitioner.class 定義了決定topic中的分區發送規則。這個屬性是可選的,但是對於你的特殊的分區實現是重要的。如果存在key將使用kafka默認的分組規則,如果key爲null 則使用隨機的分區發送策略。
4. 最後一個屬性“request.required.acks”將設置kafka知否需要broker的迴應。如果不設置可能將導致數據丟失。
1.1 此處可以設置爲0 生產者不等待broker的迴應。會有最低能的延遲和最差的保證性(在服務器失敗後會導致信息丟失)
1.2 此處可以設置爲1生產者會收到leader的迴應在leader寫入之後。(在當前leader服務器爲複製前失敗可能會導致信息丟失)
1.3 此處可以設置爲-1生產者會收到leader的迴應在全部拷貝完成之後。
之後可以定義生產者
Producer<String, String> producer =new Producer<String, String>(config);
此處泛型的第一個type是分區的key的類型。第二個是消息的類型。與上面Properties中定義的對應。
現在定義messgae
Random rnd = new Random(); long runtime = new Date().getTime(); String ip = “192.168.2.” +rnd.nextInt(255); String msg = runtime + “,www.example.com,”+ ip;
此處模擬一個website的訪問記錄。之後想broker中寫入信息.
KeyedMessage<String, String> data =new KeyedMessage<String, String>("page_visits",
ip, msg);
producer.send(data);
此處的“page_visits”是要寫入的Topic。此處我們將IP設置爲分區的key值。注意如果你沒有設置鍵值,即使你定義了一個分區類,kafka也將使用隨機發送.
Full Code:
import java.util.*; import kafka.javaapi.producer.Producer; import kafka.producer.KeyedMessage; import kafka.producer.ProducerConfig; public class TestProducer { public static void main(String[] args) { long events = Long.parseLong(args[0]); Random rnd = new Random(); Properties props = new Properties(); props.put("metadata.broker.list","broker1:9092,broker2:9092 "); props.put("serializer.class","kafka.serializer.StringEncoder"); props.put("partitioner.class","example.producer.SimplePartitioner"); props.put("request.required.acks", "1"); ProducerConfig config = new ProducerConfig(props); Producer<String, String> producer = new Producer<String,String>(config); for (long nEvents = 0; nEvents < events; nEvents++) { long runtime = newDate().getTime(); String ip = “192.168.2.” +rnd.nextInt(255); String msg = runtime +“,www.example.com,” + ip; KeyedMessage<String,String> data = new KeyedMessage<String, String>("page_visits",ip(key), msg); producer.send(data); } producer.close(); } } Partitioning Code: (分區函數) import kafka.producer.Partitioner; import kafka.utils.VerifiableProperties; public class SimplePartitioner implementsPartitioner<String> { public SimplePartitioner (VerifiableProperties props) { } public int partition(String key, int a_numPartitions) { int partition = 0; int offset = key.lastIndexOf('.'); if (offset > 0) { partition = Integer.parseInt( key.substring(offset+1)) %a_numPartitions; } return partition; } }
上面分區的作用是相同的IP將發送至相同的分區。但此時你的消費者需要知道如何去處理這樣的規則消息。
使用前需要建立topic
bin/kafka-create-topic.sh --topicpage_visits --replica 3 --zookeeper localhost:2181 --partition 5
可以使用下面的工具驗證你發送的消息
bin/kafka-console-consumer.sh --zookeeperlocalhost:2181 --topic page_visits --from-beginning
High Level Consumer API
頂層接口:
class Consumer { /** * 創建java的消費者與kafka的connect * @param config 至少需要提供consumer的groupId和zookeeper.connect. */ public statickafka.javaapi.consumer.ConsumerConnector createJavaConsumerConnector(config:ConsumerConfig); } ConsumerConnector: public interfacekafka.javaapi.consumer.ConsumerConnector { /** * 爲每一個主題創建一個泛型的消息流 * @param topicCountMap 提供topic和Stream的一一對應 * @param decoder 解析器 * @return Map <topic ,List<#streams>> * 此處的KafkaStream提供對內容的Iterable讀取 */ public <K,V> Map<String, List<KafkaStream<K,V>>> createMessageStreams(Map<String,Integer> topicCountMap, Decoder<K> keyDecoder, Decoder<V>valueDecoder); /** * 同上. */ public Map<String, List<KafkaStream<byte[], byte[]>>>createMessageStreams(Map<String, Integer> topicCountMap); /** * 建一個匹配的通配符主題的消息流的List * @param topicFilter一個topicfilter指定Consumer訂閱的話題( * 包含了一個白名單和黑名單). * @param numStreams messagestreams的數量 * @param keyDecoder message key解析器 * @param valueDecoder a message解析器 * @return 同上 */ public <K,V> List<KafkaStream<K,V>> createMessageStreamsByFilter(TopicFilter topicFilter, int numStreams,Decoder<K> keyDecoder, Decoder<V> valueDecoder); ………………………….(其餘接口類似,是上述方法的重載方法) /** * 提交本連接器所連接的所有分區和主題 */ public void commitOffsets(); /** * 停止當前Consumer */ public void shutdown(); }
e.g example
1. 爲什使用高級消費者(High Level Consumer)
有時消費者從卡夫卡讀取消息不在乎處理消息的偏移量邏輯,只是消費消息內部的信息。高級消費者提供了消費信息的方法而屏蔽了大量的底層細節。
首先要知道的是,高級消費者從zookeeper的特殊分區存儲最新偏離。這個偏移當kafka啓動時準備完畢。這一般是指消費者羣體(Consumer group)[此處的意思,kafka中的消息是發送到Consumer group中的任一個consumer上的,kafka保存的是整體的偏移。此處不知是否理解正確請大蝦指點。]
請小心,對於kafka集羣消費羣體的名字是全局的,任何的“老”邏輯的消費者應該被關閉,然後運行新的代碼。當一個新的進程擁有相同的消費者羣的名字,卡夫卡將會增加進程的線程消費topic並且引發的“重新平衡(reblannce)”。在這個重新平衡中,卡夫卡將分配現有分區到所有可用線程,可能移動一個分區到另一個進程的消費分區。如果此時同時擁有舊的的新的代碼邏輯,將會有一部分邏輯進入舊得Consumer而另一部分進入新的Consumer中的情況.
2. Designing a High Level Consumer
瞭解使用高層次消費者的第一件事是,它可以(而且應該!)是一個多線程的應用。線程圍繞在你的主題分區的數量,有一些非常具體的規則:
1. 如果你提供比在主題分區多的線程數量,一些線程將不會看到消息
2. 如果你提供的分區比你擁有的線程多,線程將從多個分區接收數據
3. 如果你每個線程上有多個分區,對於你以何種順序收到消息是沒有保證的。舉個栗子,你可能從分區10上獲取5條消息和分區11上的6條消息,然後你可能一直從10上獲取消息,即使11上也擁有數據。
4. 添加更多的進程/線程將使卡夫卡重新平衡,可能改變一個分區到線程的分配。
這裏是一個簡單的消費者栗子:
package com.test.groups; import kafka.consumer.ConsumerIterator; import kafka.consumer.KafkaStream; public class ConsumerTest implements Runnable { privateKafkaStream m_stream; private intm_threadNumber; publicConsumerTest(KafkaStream a_stream, int a_threadNumber) { m_threadNumber = a_threadNumber; m_stream =a_stream; } public void run() { ConsumerIterator<byte[], byte[]> it = m_stream.iterator(); while(it.hasNext()) System.out.println("Thread " + m_threadNumber+ ": " + new String(it.next().message())); System.out.println("Shutting down Thread: " + m_threadNumber); } }
在這裏有趣的是,(it.hasnext())。這個代碼將從卡夫卡讀取直到你停下來。
3. Config
不像simpleconsumer高層消費者爲你很多的提供需要bookkeeping(?)和錯誤處理。但是你要告訴卡夫卡這些信息。下面的方法定義了創建高級消費者基礎配置:
private static ConsumerConfigcreateConsumerConfig(String a_zookeeper, String a_groupId) { Propertiesprops = new Properties(); props.put("zookeeper.connect", a_zookeeper); props.put("group.id", a_groupId); props.put("zookeeper.session.timeout.ms", "400"); props.put("zookeeper.sync.time.ms", "200"); props.put("auto.commit.interval.ms", "1000"); return newConsumerConfig(props); }
zookeeper.connect 指定zookeeper集羣中的一個實例,kafka利用zookeeper儲存topic的分區偏移值。
Groupid 消費者所屬的Consumer Group(消費者羣體)
zookeeper.session.timeout.ms zookeeper的超時處理
auto.commit.interval.ms 屬性自動提交的間隔。這將替代消息被消費後提交。如果發生錯誤,你將從新獲得未更新的消息。
4.使用線程池處理消息
public void run(int a_numThreads) { Map<String, Integer> topicCountMap = new HashMap<String,Integer>(); topicCountMap.put(topic, new Integer(a_numThreads)); Map<String, List<KafkaStream<byte[], byte[]>>>consumerMap = consumer.createMessageStreams(topicCountMap); List<KafkaStream<byte[], byte[]>> streams =consumerMap.get(topic); // now launch all the threads executor = Executors.newFixedThreadPool(a_numThreads); // now create an object to consume the messages int threadNumber = 0; for (final KafkaStream stream : streams) { executor.submit(new ConsumerTest(stream, threadNumber)); threadNumber++; } }
首先我們創建一個map,告訴kafka提供給哪個topic多少線程。consumer.createmessagestreams是我們如何把這個信息傳遞給卡夫卡。返回的是一個包含kafkastream 的以topic 爲鍵list的map結合。(注意,這裏我們只向卡夫卡註冊一個話題,但我們可以爲map中多添加一個元素的)
最後,我們創建的線程池和通過一項新的consumertest對象,每個線程運轉我們的業務邏輯。
5. 清理和異常處理
Kafka在每次處理後不會立即更新zookeeper上的偏移值,她會休息上一段時間後提交。在這段時間內,你的消費者可能已經消費了一些消息,但並沒有提交到zookeeper上。這樣你可能會重複消費數據。
同時一些時候,broker失敗從新選取leader是也可能會導致重複消費消息。
爲了避免這種情況應該清理完成後再關閉,而不是直接使用kill命令。
e.g
try { Thread.sleep(10000); } catch (InterruptedException ie) { } example.shutdown();
full code
package com.test.groups; import kafka.consumer.ConsumerConfig; import kafka.consumer.KafkaStream; importkafka.javaapi.consumer.ConsumerConnector; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Properties; importjava.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ConsumerGroupExample { private final ConsumerConnector consumer; private final String topic; private ExecutorService executor; public ConsumerGroupExample(String a_zookeeper, String a_groupId, Stringa_topic) { consumer = kafka.consumer.Consumer.createJavaConsumerConnector( createConsumerConfig(a_zookeeper, a_groupId)); this.topic = a_topic; } public void shutdown() { if (consumer != null) consumer.shutdown(); if (executor != null) executor.shutdown(); } public void run(int a_numThreads) { Map<String, Integer> topicCountMap = new HashMap<String,Integer>(); topicCountMap.put(topic, new Integer(a_numThreads)); Map<String, List<KafkaStream<byte[], byte[]>>>consumerMap = consumer.createMessageStreams(topicCountMap); List<KafkaStream<byte[], byte[]>> streams =consumerMap.get(topic); // now launch all the threads // executor = Executors.newFixedThreadPool(a_numThreads); // now create an object to consume the messages // int threadNumber = 0; for (final KafkaStream stream : streams) { executor.submit(new ConsumerTest(stream, threadNumber)); threadNumber++; } } private static ConsumerConfig createConsumerConfig(String a_zookeeper,String a_groupId) { Properties props = new Properties(); props.put("zookeeper.connect", a_zookeeper); props.put("group.id", a_groupId); props.put("zookeeper.session.timeout.ms", "400"); props.put("zookeeper.sync.time.ms", "200"); props.put("auto.commit.interval.ms", "1000"); return new ConsumerConfig(props); } public static void main(String[] args) { String zooKeeper = args[0]; String groupId = args[1]; String topic = args[2]; int threads = Integer.parseInt(args[3]); ConsumerGroupExample example = new ConsumerGroupExample(zooKeeper,groupId, topic); example.run(threads); try { Thread.sleep(10000); } catch (InterruptedException ie) { } example.shutdown(); } }
此處的啓動命令需提供
server01.myco.com1:2181 group3 myTopic 4
1. server01.myco.com1:2181 zookeeper 的端口和地址
2. group3 Consumer Group Name
3. myTopic consumer消費消息的message
4. 消費topic的線程數