kafka基礎架構及核心知識

一、kafka介紹以及說明

1.kafka介紹以及名字由來

kafka是一款分佈式的基於發佈/訂閱模式的消息隊列,是目前比較主流的消息中間件,Kafka對消息保存時根據Topic(主題)進行歸類,發送消息者稱爲Producer,消息接受者稱爲Consumer,此外kafka集羣有多個kafka實例組成,每個實例(server)稱爲broker。無論是kafka集羣,還是consumer都依賴於zookeeper集羣保存一些meta信息,來保證系統可用性,所以安裝kafka需要先間搭建zookeeper集羣,至於搭建zookeeper集羣,請看本人zookeeper介紹那章節。之所以取名kafka,據說是因爲開發者非常喜歡奧地利作家卡夫卡,所以以此命名。

 爲什麼需要zookeeper:Kafka集羣中有一個broker會被選舉爲Controller,負責管理集羣broker的上下線,所有topic的分區副本分配和leader選舉等工作。Controller的管理工作都是依賴於Zookeeper的。

2.kafka數據存儲

作爲一款消息中間件,很多人誤以爲寫入kafka數據是存儲在內存中,但是實際上寫入kafka的數據是存儲在磁盤中,很多人都認爲磁盤很慢,爲此,官網專門有一張對此作出了說明(不過聽官網語氣,大意是人們認爲覺得磁盤很慢,但是官網說很快,總結一句話就是:我不要你覺得,我要我覺得)

在官方文檔 4.2章持久化的介紹中,官網第一篇說了這樣一句話:在這裏插入圖片描述
這句話的意思是:

 不要害怕文件系統!(文件系統即磁盤) 
 kafka很大程度上是依賴於文件系統來緩存消息。人們普遍認爲“磁盤速度很慢”,這使得人們懷疑其(kafka)持久化的架構及性能是否具有競爭力。實際上,磁盤的速度比人期望的更快或者更慢取決於他們(指磁盤)如何被使用。正確設計的磁盤結構通常可以和網絡一樣快。

上述的意思就是,磁盤其實並不慢,磁盤的快慢取決於人們如何去使用磁盤,那麼,kafka如何在高效的使用磁盤,文檔中我標記紅框的那部分說了這樣一句話:順序訪問磁盤比隨機訪問內存更快!那麼kafka的高效的原因下面就總結出來了。

3.kafka高效的原因

①.順序寫磁盤:Kafka的producer生產數據,要寫入到log文件中,寫的過程是一直追加到文件末端,爲順序寫。官網有數據表明,同樣的磁盤,順序寫能到600M/s,而隨機寫只有100K/s。這與磁盤的機械機構有關,順序寫之所以快,是因爲其省去了大量磁頭尋址的時間。

②.零拷貝技術:“零拷貝技術”只用將磁盤文件的數據複製到頁面緩存中一次,然後將數據從頁面緩存直接發送到網絡中(發送給不同的訂閱者時,都可以使用同一個頁面緩存),避免了重複複製操作。如果有10個消費者,傳統方式下,數據複製次數爲4*10=40次,而使用“零拷貝技術”只需要1+10=11次,一次爲從磁盤複製到頁面緩存,10次表示10個消費者各自讀取一次頁面緩存。

③.分區:kafka對每個主題進行分區提高了併發,也提高了效率。

4.kafka的特點

①類似於消息隊列和商業的消息系統,kafka提供對流式數據的發佈和訂閱
②kafka提供一種持久的容錯的方式存儲流式數據
③kafka擁有良好的性能,可以及時地處理流式數據
④每條記錄由一個鍵,一個值和一個時間戳組成

5.相關單詞

producer 生產者[prəˈduːsər]
broker 緩存代理[ˈbroʊkər]
consumers 消費者[kənˈsumərz]
topic 主題[ˈtɑːpɪk]
Interceptor 攔截器[ˌɪntərˈseptər]
Partition 分區[pɑːrˈtɪʃn]

二、kafka集羣的安裝與部署

安裝卡夫卡之前,確保已經安裝了zookeeper;進入kafka官網:http://kafka.apache.org/downloads.html 下載
在這裏插入圖片描述
這裏以:kafka_2.11-0.11.0.0.tgz爲例來搭建集羣,例如我有3臺機器,機器名分別爲hadoop101,hadoop102,hadoop103
1.解壓安裝包
tar -zxvf kafka_2.11-0.11.0.0.tgz -C /opt
2.修改解壓後文件夾名稱
mv kafka_2.11-0.11.0.0/ kafka
3.在當前文件夾下創建datas文件夾
mkdir logs
4.修改配置文件
cd conf;
vim server.properties;
配置文件修改如下(以lh01機器爲例):
broker.id=1;
delete.topic.enable=true
log.dirs=/opt/kafka/datas
zookeeper.connect=hadoop101:2181,hadoop102:2181,hadoop103:2181
總共修改4個配置就可以了
5.配置環境變量。將kafka配置到path環境變量下

三、kafka的核心組成

1.Broker

一臺kafka服務器就是一個broker。一個集羣由多個broker組成,每個broker就是一個kafka的實例。

2.Topic

Topic 就是數據主題,kafka建議根據業務系統將不同的數據存放在不同的topic中!Kafka中的Topics總是多訂閱者模式,一個topic可以擁有一個或者多個消費者來訂閱它的數據。一個大的Topic可以分佈式存儲在多個kafka broker中!Topic可以類比爲數據庫中的庫!

3.Interceptor

Interceptor 爲攔截器,當生產者向kafka發送數據時,數據會先經過攔截器進行攔截處理,多個攔截器可以組成攔截器鏈,然後再真正發送數據doSend()。源代碼如下:

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

4.Partition

每個topic可以有多個分區,通過分區的設計,topic可以不斷進行擴展!即一個Topic的多個分區分佈式存儲在多個broker;此外通過分區還可以讓一個topic被多個consumer進行消費!以達到並行處理!分區可以類比爲數據庫中的表!kafka只保證按一個partition中的順序將消息發給consumer,不保證一個topic的整體(多個partition間)的順序。
經過攔截器過濾的代碼後,會被進行分區,如果沒有指定分區,纔會走分區器,所以如果想要自定義分區,不能指定分區,代碼如下:

 /**
     * computes partition for given record.
     * if the record has partition returns the value otherwise
     * calls configured partitioner class to compute the partition.
     */
    private int partition(ProducerRecord<K, V> record, byte[] serializedKey, byte[] serializedValue, Cluster cluster) {
    //這裏嘗試獲取生產者生產數據的分區號
        Integer partition = record.partition();
        return partition != null ?//如果爲null,纔會調用分區器
                partition :
                partitioner.partition(
                        record.topic(), record.key(), serializedKey, record.value(), serializedValue, cluster);
    }

5.Offset

生成者每生產一條數據都會追加到指定分區的log文件中,且存儲的記錄都是有序的,由於不可隨機寫入,所以順序是不變的,這個順序是通過一個稱之爲offset的id來唯一標識。
kafka自動維護消費者消費的主題各個分區的offset,前提是消費者消費的分區是由kafka分配的,在啓動消費者時,只指定了主題,沒有指定分區,kafka會將offset數據保存到一個內置主題爲__consumer_offsets的主題中,如果指定了分區,那麼kafka將不再自動維護offset。

6.Persistence

Persistence即持久化,Kafka 集羣保留所有發佈的記錄,無論他們是否已被消費,都會通過一個可配置的參數:保留期限來控制。舉個例子, 如果保留策略設置爲2天,一條記錄發佈後兩天內,可以隨時被消費,兩天過後這條記錄會被清除並釋放磁盤空間。
Kafka的性能和數據大小無關,所以長時間存儲數據沒有什麼問題

7.Replication

Replication,即副本,每個分區可能會有多個副本,同一個主題每個分區之間的副本都會選出一個leader,而producer與consumer只與leader之間進行交互,其他follower副本則從leader中同步數據。

8.Producer

消息生產者,就是向kafka broker發消息的客戶端。生產者負責將記錄分配到topic的指定 partition(分區)中,如果沒有指定分區,則都卡夫卡依據分區策略進行分配。

9.Consumer

消息消費者,向kafka broker取消息的客戶端。每個消費者都要維護自己讀取數據的offset。低版本0.9之前將offset保存在Zookeeper中,0.9及之後保存在Kafka的“__consumer_offsets”主題中
consumer採用pull(拉)模式從broker中讀取數據
pull模式不足之處是,如果kafka沒有數據,消費者可能會陷入循環中,一直返回空數據。針對這一點,Kafka的消費者在消費數據時會傳入一個時長參數timeout,如果當前沒有數據可供消費,consumer會等待一段時間之後再返回,這段時長即爲timeout。

10.Consumer Group

每個消費者都會使用一個消費組名稱來進行標識。同一個組中的不同的消費者實例,可以分佈在多個進程或多個機器上!
如果所有的消費者實例在同一消費組中,消息記錄會負載平衡到每一個消費者實例(單播)。即每個消費者可以同時讀取一個topic的不同分區!
如果所有的消費者實例在不同的消費組中,每條消息記錄會廣播到所有的消費者進程(廣播)。
如果需要實現廣播,只要每個consumer有一個獨立的組就可以了。要實現單播只要所有的consumer在同一個組。
一個topic可以有多個consumer group。topic的消息會複製(不是真的複製,是概念上的)到所有的CG,但每個partion只會把消息發給該CG中的一個consumer。

四、shell客戶端操作kafka

1.創建主題
kafka-topics.sh --zookeeper lh02:2181 --create --topic hello1 --partitions 2 --replication-factor 2
創建主題必須指定分區與副本數量,副本數量不能超過當前可用的broker數量;如果只指定了分區數與副本數,由kafka採用負載均衡策略進行對副本自動分配
2.查看主題
①查看所有主題
kafka-topics.sh --zookeeper hadoop102:2181 --list
②查看主題詳情
kafka-topics.sh --zookeeper hadoop102:2181 --describe
3.修改主題
kafka-topics.sh --zookeeper hadoop102:2181 --alter --topic first --partitions 6
修改只能修改分區數量以及副本的分配策略,且分區數只能調大,不能調小
4.刪除主題
kafka-topics.sh --zookeeper hadoop102:2181 --delete --topic hello1
刪除主題分區數據不會馬上刪除(zookeeper中的元數據會被刪除),只會標記爲刪除,一段就時間後回收線程會來刪除這些被標記的數據。
5.生產消費數據測試
生產者:kafka-console-producer.sh --broker-list hadoop102:9092 --topic hello3
消費者:kafka-console-consumer.sh --bootstrap-server hadoop102:9092 --topic hello3
消費者默認只會從分區的最後一個數據之後開始消費(默認啓動消費者後,只能接受到新生成的數據),消費時,只能保證分區內部有序,不能保證全局有序,如果希望全局有序,那麼可以只創建一個分區。
6.查看消費者組
kafka-consumer-groups.sh --bootstrap-server hadoop102:9092 --list

五、kafka工作流程與存儲結構

1.存儲結構以及文件滾動策略

Kafka採取了分片和索引機制,一個主題可以分爲多個區,將每個分區(partition)分爲多個片段(segment),每個segment 文件中消息數量不一定相等,每個segment對應兩個文件——“.index”文件和“.log”文件,其中index文件爲索引文件,log文件爲數據文件,segment的log文件大小有配置(log.segment.bytes)決定,默認爲1073741824byte=1G,也就是當每個分區生產的消息超過1G之後,就會滾動,產生新的segment 。當然,你也可以指定多長時間滾動一次,不一定要達到1G才滾動,這個配置爲log.roll.hours=1或者log.roll.ms=3600000(這個配置需要你自己加上去,kafka給的配置文件中是不存在這個的)

2.工作流程

topic是邏輯上的概念,而partition是物理上的概念,每個partition對應於一個log文件,該log文件中存儲的就是producer生產的數據。Producer生產的數據會被不斷追加到該log文件末端,且每條數據都有自己的offset。消費者組中的每個消費者,都會實時記錄自己消費到了哪個offset,以便出錯恢復時,從上次的位置繼續消費。
那麼,消費端是如何知道消費到哪裏了?
如果消費的時候,沒有指定分區,那麼kafka會自動維護offset,當消費端再去消費時,通過offset,根據index索引文件,找到log中對應的位置,然後從下開始繼續消費。

六、分區策略、數據的可靠性與一致性

1.分區策略

爲什麼要分區:
①方便在集羣中擴展,一個topic由多個Partition組成,因此整個集羣就可以適應任意大小的據
②可以提高併發,因爲Partition發送的數據可以以Partition爲單位讀寫

生產數據的分區策略:
首先,生成者發送的數據會被封裝成一個ProducerRecord對象,ProducerRecord對象可以指定多個數據:
1.有partition的情況下,直接將指明的值作爲partition的值
2.沒有指明partition值但有key的情況下,將key的hash值與topic的partition數進行取餘得到partition值
3.既沒有 partition 值又沒有 key 值的情況下,第一次調用時隨機生成一個整數(後面每次調用在這個整數上自增),將這個值與 topic 可用的 partition 總數取餘得到 partition 值,也就是常說的 round-robin 算法,注意,根據第3條,生產者生產數據時候,如果既沒有傳key,也沒有指定分區,那麼第一個數據存放是隨機的,之後的數據依次輪詢放入各個分區。
消費數據的分區策略:
啓動一個消費者組(這個組內有一個或多個消費者),如果消費時只指定了主題,沒有指定分區,系統會自動爲當前消費者組內的多個消費者,自動分配分區,分配策略有兩種:range,Round_robin。
1.range策略, range以消費者組消費的每個主題爲單位,依次列出每個主題當前有多少分區。使用  分區數/消費者數量,如果不能整除,那麼排名靠前的消費者會額外多獲取一個名額。(如果不能整除,當訂閱的分區較多時,排名靠前的消費者壓力大!負載不均衡!)
2.Round_robin: 採用輪詢的策略分配!輪詢採用,先將當前組中所有的消費者訂閱的所有的主機和分區彙總,彙總之後(排序),採取輪詢的策略分配,但是如果分配的這個分區,當前消費者沒有訂閱,那麼就放棄。

如果消費者組內的每個消費者訂閱的主題一致,那麼輪詢相對公平,每個消費者最多消費的分區差1!
訂閱的主題和其他消費者差距較大的消費者負載重!

2.數據的可靠性

首先,數據的可靠性根據不同的業務場景有不同的需要,總的來講就是不丟失數據,如何防止不丟失數據,那麼就需要配置ack參數了。ack有3個機制,分別對應如下

0:當ack設置爲0時,producer不等待broker的ack,這一操作提供了一個最低的延遲,broker一接收到還沒有寫入磁盤就已經返回,當broker故障時有可能丟失數據;
1:當ack設置爲1時,producer等待broker的ack,partition的leader落盤成功後返回ack,如果在follower同步成功之前leader故障,那麼將會丟失數據;
-1(all):當ack設置爲1或all時,這也是kafka的默認策略,producer等待broker的ack,partition的leader和follower全部落盤成功後才返回ack。但是如果在follower同步完成後,broker發送ack之前,leader發生故障,那麼會造成數據重複。

那麼問題來了,無論ack是0,1還是-1,都會存在數據丟失或者重複消費的問題,那麼如何保證數據被精確消費一次呢,根據不同的公司業務邏輯,消費策略也可能不同,但是大體上可以分爲三種消費策略:

at least once:最少消費一次,同一條消息可能保存一次或多次,即ack=-1
at most once:最多一次,同一條消息只可能保存一次或0次,即ack=0或ack=1
exactly once:精確一次,同一條消息只能保存一次

現在要面對的就是如何實現 exactly once,即確保消息只被精確的消費一次,但是我們從ack=-1可知,數據至少不會丟失,只有重複的風險,其實解決重複方法有很多種,其中kafka就提供看一種機制:冪等性機制(idempotent),這種機制需要ack=-1使用,,只需將enable.idempotence屬性設置爲true(其實如果這個參數設置爲true了,ack默認就變爲-1了),並將retries屬性設爲Integer.MAX_VALUE。開啓上述參數之後,kafka在broker端,對來自每個producer的記錄(record)的屬性,進行緩存,緩存<producerId,Partition,SequeceNum(id)>數據啊,每次broker將會對這三個參數進行驗證,如果相同則代表重複,那麼kafka將會不在保存數據。

注意:前提是生產者必須是同一臺機器,

3.數據的一致性

數據的一致性:每個副本保存的數據都應該是一致的,如果leader宕機,無論哪個副本稱爲新的leader,消費者消費數據都應該是一致的,即消費者不管從哪個副本消費,消費數據都是相同的。
那麼問題就產生了:一個主題每個分區可能多個副本,這些副本有一個leader與多個flower,在kafka中,消費者與生產者只與leader進行通信,其他副本則從leader同步數據,假如leader突然宕機了,而其他副本則都有可能稱爲leader,那麼,哪些副本可以稱爲leader了,只有在ISR同步隊列中副本纔有可能稱爲leader

		ISR:同步隊列中的可用副本,Leader維護了一個動態的in-sync replica set (ISR),意爲和leader保持同步的follower集合。當ISR中的follower完成數據的同步之後,leader就會給follower發送ack。如果follower長時間未向leader同步數據,則該follower將被踢出ISR,該時間閾值由replica.lag.time.max.ms參數設定。Leader發生故障之後,就會從ISR中選舉新的leader。此隊列有leader維護。
		OSR: 不在同步隊列的可用副本,如果某個follower遲遲未與leader進行同步,那麼leader就會將此副本移動到OSR隊列.

但是如果在follower延遲時間內,leader突然宕機了(假設此時偏移量爲20),但是消費者消費到了17,但是其他副本存儲的數據的偏移量爲15,當其他副本稱爲leader之後,消費者從新的leader發現,根本找不到上次消費到17的位置(因爲新的leader的最大offset偏移量才15),此時就產生了問題。
其實kafka只會提供集羣中最低offset暴露給消費者,即木桶理論,這裏也就引進了兩個概念:

	LEO:指的是每個副本最大的offset。
	HW(高水位):指的是消費者能見到的最大的offset,ISR隊列中最小的LEO。(類似於木桶理論中最短的那根木頭),1.1之後改稱leader_epoch。

那麼,根據HW這個參數,無論是leader發生故障,還是follower發生故障,都有相應的處理:

	follower故障:follower發生故障後會被臨時踢出ISR,待該follower恢復後,follower會讀取本地磁盤記錄的上次的HW,並將log文件高於HW的部分截取掉,從HW開始向leader進行同步。等該follower的LEO大於等於該Partition的HW,即follower追上leader之後,就可以重新加入ISR了。
	leader故障:leader發生故障之後,會從ISR中選出一個新的leader,之後,爲保證多個副本之間的數據一致性,其餘的follower會先將各自的log文件高於HW的部分截掉,然後從新的leader同步數據。這隻能保證副本之間的數據一致性,並不能保證數據不丟失或者不重複。

總結:數據的一致性即保證消費者消費的一致性,leader只提供整個所有副本中HW的offset給消費者,消費者也只能消費到offset,這樣,無論哪個副本成爲新的leader,消費者都可以依據上次消費的位置,繼續消費。

七.API實例

1.自定義生產者

package com.lh.test1;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.ExecutionException;

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;



public class TestInterceptor {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		 Properties props = new Properties();
		 props.put("bootstrap.servers", "hadoop102:9092");
		 props.put("acks", "all");
		 props.put("retries", 0);
		 props.put("batch.size", 16384);
		 props.put("linger.ms", 1);
		 props.put("buffer.memory", 33554432);
		 props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
		 props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
		 props.put(ProducerConfig.PARTITIONER_CLASS_CONFIG,"com.lh.test1.MyPartition");
		 //設置攔截器
		 List<String> interceptors=new ArrayList<String>();
		 interceptors.add("com.lh.test1.MyCountInterceptor");
		 interceptors.add("com.lh.test1.MyTimeInterceptor");
		 
		 props.put(ProducerConfig.INTERCEPTOR_CLASSES_CONFIG,interceptors);
		 Producer<String, String> producer = new KafkaProducer<>(props);
		 for (int i = 0; i < 10; i++) {
			
			 //異步發送
			 //producer.send(new ProducerRecord<String, String>("hello3", Integer.toString(i)));
			 
			 //同步發送
			 RecordMetadata recordMetadata = producer.send(new ProducerRecord<String, String>("hello3", Integer.toString(i),"hahaha"+i)).get();
		     System.out.println(recordMetadata);
		     System.out.println("第"+i+"條數據發送成功!區號:"+recordMetadata.partition()
		     +"本區偏移量:"+recordMetadata.offset());
		     Thread.sleep(2000);
		     
		     
		}
		 producer.flush();
		 producer.close();
	}
}

注意:向props放入各種k-v值時候,建議使用ProducerConfig類來獲取相應的key,而不是直接使用字符串。

2.自定義消費者

public static void main(String[] args) {
		Properties props = new Properties();
		//建議使用設置key時,使用ConsumerConfig
	     props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "hadoop102:9092");
	     props.put("group.id", "test");
	     //是否自動提交offset
	     props.put("enable.auto.commit", "false");
	     //提交offset間隔
	     props.put("auto.commit.interval.ms", "1000");
	     props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
	     props.put("value.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
	     KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
	     //如果只指定主題,則可以使用subscribe方法,可以指定多個主題
	     //consumer.subscribe(Arrays.asList("hello3"));
	     //如果既要指定主題,又要指定分區,則使用assign方法
	     List<TopicPartition> topsList=new ArrayList<TopicPartition>();
	     TopicPartition topicPartition=new TopicPartition("hello", 0);
	     topsList.add(topicPartition);   
	     consumer.assign(topsList);
	     //從指定offset讀取,如果指定了offset,則提交無論是自動還是手動offset將失效,需要自己維護offset
	     consumer.seek(topicPartition, 0);
	     
	     while (true) {
	    	 //poll從buffer中拉取數據,最多等待10ms
	         ConsumerRecords<String, String> records = consumer.poll(100);
	         for (ConsumerRecord<String, String> record : records) {         
	        	 System.out.printf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), record.value());
	        	 }

	        
	     }
	}

注意:在消費者中,當向props中存入k-v值時,建議使用ConsumerConfig來定義屬性值

3.自定義攔截器

攔截器1:

package com.lh.test1;

import java.util.Map;

import org.apache.kafka.clients.producer.ProducerInterceptor;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;

public class MyTimeInterceptor implements ProducerInterceptor<String, String> {

	@Override
	public void configure(Map<String, ?> configs) {
		//讀取配置文件
	}

	//攔截處理record
	@Override
	public ProducerRecord<String, String> onSend(ProducerRecord<String, String> record) {
		String value = record.value();
		value=System.currentTimeMillis()+","+value;
		return new ProducerRecord<String, String>(record.topic(),record.key(), value);
	}

	//當收到ack通知時調用
	@Override
	public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
		
	}

	//關閉producer時調用
	@Override
	public void close() {
		// TODO Auto-generated method stub
		
	}

}

攔截器2:

package com.lh.test1;

import java.util.Map;

import org.apache.kafka.clients.producer.ProducerInterceptor;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;

public class MyCountInterceptor implements ProducerInterceptor<String, String> {

	private int successCount;
	private int failedCount;
	@Override
	public void configure(Map<String, ?> configs) {
		
	}

	@Override
	public ProducerRecord<String, String> onSend(ProducerRecord<String, String> record) {
		return record;
	}

	@Override
	public void onAcknowledgement(RecordMetadata metadata, Exception exception) {
		if (exception!=null) {
			failedCount++;
		}else {
			successCount++;
		}
	}

	//生成者關閉時輸出統計結果
	@Override
	public void close() {
		System.out.println("消息統計:成功:"+successCount+",失敗:"+failedCount);
	}
	
}

4.自定義分區器

package com.lh.test1;

import java.util.List;
import java.util.Map;

import org.apache.kafka.clients.producer.Partitioner;
import org.apache.kafka.common.Cluster;
import org.apache.kafka.common.PartitionInfo;

public class MyPartition implements Partitioner {

	@Override
	public void configure(Map<String, ?> configs) {
		//獲取配置文件
		System.out.println(configs.get("bootstrap.servers"));
	}

	@Override
	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 (key!=null) {
			int keyInt=Integer.parseInt(key.toString());
			if (keyInt%numPartitions==0) {
				return 0;
			}
			else if (keyInt%numPartitions==1) {
				return 1;
			}
			else {
				return 2;
			}
		}
		else {
			return 0;
		}
	}

	@Override
	public void close() {
		// TODO Auto-generated method stub
		
	}

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章