天天看點

Kafka java api-消費者代碼與消費分析、生産者消費者配置檔案詳解

1、消費者代碼

用到消費者,是以也必須先把前面寫過的生産者代碼也貼一下吧

生産者代碼與自定義partition

使用maven導包

<dependencies>
      <dependency>
           <groupId>com.alibaba.jstorm</groupId>
           <artifactId>jstorm-core</artifactId>
           <version>2.1.1</version>
           <exclusions>
               <exclusion>
                   <groupId>org.slf4j</groupId>
                   <artifactId>slf4j-nop</artifactId>
               </exclusion>
               <exclusion>
                   <groupId>org.slf4j</groupId>
                   <artifactId>slf4j-jdk14</artifactId>
               </exclusion>
           </exclusions>
       </dependency>
       <dependency>
           <groupId>org.apache.kafka</groupId>
           <artifactId>kafka_2.8.2</artifactId>
           <version>0.8.1</version>
           <exclusions>
               <exclusion>
                   <artifactId>jmxtools</artifactId>
                   <groupId>com.sun.jdmk</groupId>
               </exclusion>
               <exclusion>
                   <artifactId>jmxri</artifactId>
                   <groupId>com.sun.jmx</groupId>
               </exclusion>
               <exclusion>
                   <artifactId>jms</artifactId>
                   <groupId>javax.jms</groupId>
               </exclusion>
               <exclusion>
                   <groupId>org.apache.zookeeper</groupId>
                   <artifactId>zookeeper</artifactId>
               </exclusion>
               <exclusion>
                   <groupId>org.slf4j</groupId>
                   <artifactId>slf4j-log4j12</artifactId>
               </exclusion>
               <exclusion>
                   <groupId>org.slf4j</groupId>
                   <artifactId>slf4j-api</artifactId>
               </exclusion>
           </exclusions>
       </dependency>
   </dependencies>
           
/**
 * 這是一個簡單的Kafka producer代碼
 * 包含兩個功能:
 * 1、資料發送
 * 2、資料按照自定義的partition政策進行發送
 *
 *
 * KafkaSpout的類
 */
public class KafkaProducerSimple {
    public static void main(String[] args) {
        /**
         * 1、指定目前kafka producer生産的資料的目的地
         *  建立topic可以輸入以下指令,在kafka叢集的任一節點進行建立。
         *  bin/kafka-topics.sh --create --zookeeper mini1:2181 --replication-factor 2 --partitions 3 --topic test
         */

        String TOPIC = "orderMq";
        /**
         * 2、讀取配置檔案
         */
        Properties props = new Properties();
        /*
         * key.serializer.class預設為serializer.class  key的序列化使用哪個類
         */
        props.put("serializer.class", "kafka.serializer.StringEncoder");
        /*
         * kafka broker對應的主機,格式為host1:port1,host2:port2
         */
        props.put("metadata.broker.list", "mini1:9092,mini2:9092,mini3:9092");
        /*
         * request.required.acks,設定發送資料是否需要服務端的回報,有三個值0,1,-1
         * 0,意味着producer永遠不會等待一個來自broker的ack,這就是0.7版本的行為。
         * 這個選項提供了最低的延遲,但是持久化的保證是最弱的,當server挂掉的時候會丢失一些資料。
         * 1,意味着在leader replica已經接收到資料後,producer會得到一個ack。
         * 這個選項提供了更好的持久性,因為在server确認請求成功處理後,client才會傳回。
         * 如果剛寫到leader上,還沒來得及複制leader就挂了,那麼消息才可能會丢失。
         * -1,意味着在所有的ISR都接收到資料後,producer才得到一個ack。
         * 這個選項提供了最好的持久性,隻要還有一個replica存活,那麼資料就不會丢失
         */
        props.put("request.required.acks", "1");
        /*
         * 可選配置,如果不配置,則使用預設的partitioner partitioner.class
         * 預設值:kafka.producer.DefaultPartitioner
         * 用來把消息分到各個partition中,預設行為是對key進行hash。
         */
        props.put("partitioner.class", "com.scu.kafka.MyLogPartitioner");
//        props.put("partitioner.class", "kafka.producer.DefaultPartitioner");
        /**
         * 3、通過配置檔案,建立生産者
         */
        Producer<String, String> producer = new Producer<String, String>(new ProducerConfig(props));
        /**
         * 4、通過for循環生産資料
         */
        for (int messageNo = ; messageNo < ; messageNo++) {
            /**
             * 5、調用producer的send方法發送資料
             * 注意:這裡需要指定 partitionKey,用來配合自定義的MyLogPartitioner進行資料分發
             */
            producer.send(new KeyedMessage<String, String>(TOPIC, messageNo + "", "appid" + UUID.randomUUID() + "itcast"));
        }
    }
}
           
public class MyLogPartitioner implements Partitioner {
    private static Logger logger = Logger.getLogger(MyLogPartitioner.class);

    public MyLogPartitioner(VerifiableProperties props) {
    }

    /**
     *
     * @param obj 傳來的key 用它來進行hash分到partition
     * @param numPartitions 幾個partition 如果叢集中已存在該topic,那麼partition數為原本存在數,否則預設是2
     * @return 生産到哪個partition
     */
    public int partition(Object obj, int numPartitions) {
        return Integer.parseInt(obj.toString())%numPartitions;
    }

}
           

注:orderMq這個topic很早就通過指令行建立好了,指定了partition是3個。

下面是消費者代碼

public class KafkaConsumerSimple implements Runnable {
    public String title;
    public KafkaStream<byte[], byte[]> stream;
    public KafkaConsumerSimple(String title, KafkaStream<byte[], byte[]> stream) {
        this.title = title;
        this.stream = stream;
    }
    public void run() {
        System.out.println("開始運作 " + title);
        ConsumerIterator<byte[], byte[]> it = stream.iterator();
        /**
         * 不停地從stream讀取新到來的消息,在等待新的消息時,hasNext()會阻塞
         * 如果調用 `ConsumerConnector#shutdown`,那麼`hasNext`會傳回false
         * */
        while (it.hasNext()) {
            MessageAndMetadata<byte[], byte[]> data = it.next();
            String topic = data.topic();
            int partition = data.partition();
            long offset = data.offset();
            String msg = new String(data.message());
            System.out.println(String.format(
                    "Consumer: [%s],  Topic: [%s],  PartitionId: [%d], Offset: [%d], msg: [%s]",
                    title, topic, partition, offset, msg));
        }
        System.out.println(String.format("Consumer: [%s] exiting ...", title));
    }

    public static void main(String[] args) throws Exception{
        Properties props = new Properties();
        props.put("group.id", "dashujujiagoushi");//消費組組組名,任意取
        props.put("zookeeper.connect", "mini1:2181,mini2:2181,mini3:2181");//zookeeper連接配接
        props.put("auto.offset.reset", "largest");//最新位置開始消費
        props.put("auto.commit.interval.ms", "1000");
        props.put("partition.assignment.strategy", "roundrobin");//分區配置設定政策
        ConsumerConfig config = new ConsumerConfig(props);
        String topic1 = "orderMq";
        String topic2 = "paymentMq";
        //隻要ConsumerConnector還在的話,consumer會一直等待新消息,不會自己退出
        ConsumerConnector consumerConn = Consumer.createJavaConsumerConnector(config);
        //定義一個map
        Map<String, Integer> topicCountMap = new HashMap<String, Integer>();
        topicCountMap.put(topic1, );
        //Map<String, List<KafkaStream<byte[], byte[]>> 中String是topic, List<KafkaStream<byte[], byte[]>是對應的流
        Map<String, List<KafkaStream<byte[], byte[]>>> topicStreamsMap = consumerConn.createMessageStreams(topicCountMap);
        //取出 `kafkaTest` 對應的 streams
        List<KafkaStream<byte[], byte[]>> streams = topicStreamsMap.get(topic1);
        //建立一個容量為4的線程池
        ExecutorService executor = Executors.newFixedThreadPool();
        //建立20個consumer threads
        for (int i = ; i < streams.size(); i++)
            executor.execute(new KafkaConsumerSimple("消費者" + (i + ), streams.get(i)));
    }
}
           

測試:

先執行消費者程式,盡管partition目錄裡面的segment檔案是有以前生成的資料,但是不會列印出來而是一直提示(已經标記為消費狀态的就不再消費了,預設情況就是這樣,可以自己設定從0開始消費)

:: [main-SendThread(mini1:)] DEBUG org.apache.zookeeper.ClientCnxn - Got ping response for sessionid:  after ms
:: [main-SendThread(mini1:)] DEBUG org.apache.zookeeper.ClientCnxn - Got ping response for sessionid:  after ms
           

需要進行生産,是以再執行生産者程式,控制台列印如下:

...
Consumer: [消費者1],  Topic: [orderMq],  PartitionId: [1], Offset: [17857], msg: [appidc977abb2-f0bc-41da-9daa-6b080321947bitcast]
Consumer: [消費者2],  Topic: [orderMq],  PartitionId: [0], Offset: [17724], msg: [appid9101368e-ac81-4bbf-b2b5-8f2facd41f54itcast]
Consumer: [消費者1],  Topic: [orderMq],  PartitionId: [1], Offset: [17858], msg: [appidb145da08-bb61-42e7-b140-9fed576c2faeitcast]
Consumer: [消費者1],  Topic: [orderMq],  PartitionId: [1], Offset: [17859], msg: [appid909a90ae-c0fb-42ac-97de-6d7438895e07itcast]
Consumer: [消費者3],  Topic: [orderMq],  PartitionId: [2], Offset: [17713], msg: [appid157754b5-6958-4286-9c25-ff67ccc61a42itcast]
Consumer: [消費者3],  Topic: [orderMq],  PartitionId: [2], Offset: [17714], msg: [appidb93b9355-4713-4e22-823a-756b4fe75bdfitcast]
Consumer: [消費者3],  Topic: [orderMq],  PartitionId: [2], Offset: [17715], msg: [appidf82ca658-528a-4f40-a023-8a155c15eaa1itcast]
...
           

精簡下如下

Consumer: [消費者1],  Topic: [orderMq],  PartitionId: [1], Offset: [17857], msg: [appidc977abb2-f0bc-41da-9daa-6b080321947bitcast]
Consumer: [消費者2],  Topic: [orderMq],  PartitionId: [0], Offset: [17724], msg: [appid9101368e-ac81-4bbf-b2b5-8f2facd41f54itcast]
Consumer: [消費者3],  Topic: [orderMq],  PartitionId: [2], Offset: [17713], msg: [appid157754b5-6958-4286-9c25-ff67ccc61a42itcast]
           

能看到三個消費者對應消費的partition。

那麼考慮以下問題

在建立orderMq的時候指定partition是3個,那麼如果此時我指定建立5個KafkaStream,那麼會怎麼消費呢?

消費者代碼修改兩次如下

topicCountMap.put(topic1, );
ExecutorService executor = Executors.newFixedThreadPool();
           

再次同上一樣執行,輸出結果能看到隻有3個消費者,是以指定KafkaStream比partition多是沒用的,隻會有對應數量的消費者去消費對應的partition上的資料。

Consumer: [消費者2],  Topic: [orderMq],  PartitionId: [2], Offset: [26420], msg: [appid4b778b51-33c7-42de-83c2-5b85f8f2428aitcast]
Consumer: [消費者3],  Topic: [orderMq],  PartitionId: [0], Offset: [26423], msg: [appid86045c25-7b3f-4c82-ad2a-3e8e11958b28itcast]
Consumer: [消費者4],  Topic: [orderMq],  PartitionId: [1], Offset: [26562], msg: [appid213b5a91-a7bf-4a39-b585-456d95748566itcast]
           

如果指定的KafkaStream隻有2呢?不做測試了,結果是其中一個消費者會消費2個partition,另外一個消費1個partition中的資料。

生産者,消費者配置檔案解釋

用java api不管是寫生産者代碼還是消費者代碼都使用配置檔案,那麼下面列出了生産者和消費者配置檔案介紹

生産者配置檔案解釋

#指定kafka節點清單,用于擷取metadata,不必全部指定
metadata.broker.list=kafka01:,kafka02:

# 指定分區處理類。預設kafka.producer.DefaultPartitioner,表通過key哈希到對應分區
#partitioner.class=kafka.producer.DefaultPartitioner

# 是否壓縮,預設0表示不壓縮,1表示用gzip壓縮,2表示用snappy壓縮。壓縮後消息中會有頭來指明消息壓縮類型,故在消費者端消息解壓是透明的無需指定。
compression.codec=none

# 指定序列化處理類
serializer.class=kafka.serializer.DefaultEncoder

# 如果要壓縮消息,這裡指定哪些topic要壓縮消息,預設empty,表示不壓縮。
#compressed.topics=

# 設定發送資料是否需要服務端的回報,有三個值0,1,-1
# 0: producer不會等待broker發送ack
# 1: 當leader接收到消息之後發送ack
# -1: 當所有的follower都同步消息成功後發送ack.
request.required.acks=

# 在向producer發送ack之前,broker允許等待的最大時間 ,如果逾時,broker将會向producer發送一個error ACK.意味着上一次消息因為某種原因未能成功(比如follower未能同步成功)
request.timeout.ms=

# 同步還是異步發送消息,預設“sync”表同步,"async"表異步。異步可以提高發送吞吐量,
也意味着消息将會在本地buffer中,并适時批量發送,但是也可能導緻丢失未發送過去的消息
producer.type=sync

# 在async模式下,當message被緩存的時間超過此值後,将會批量發送給broker,預設為5000ms
# 此值和batch.num.messages協同工作.
queue.buffering.max.ms = 

# 在async模式下,producer端允許buffer的最大消息量
# 無論如何,producer都無法盡快的将消息發送給broker,進而導緻消息在producer端大量沉積
# 此時,如果消息的條數達到閥值,将會導緻producer端阻塞或者消息被抛棄,預設為10000
queue.buffering.max.messages=

# 如果是異步,指定每次批量發送資料量,預設為200
batch.num.messages=

# 當消息在producer端沉積的條數達到"queue.buffering.max.meesages"後
# 阻塞一定時間後,隊列仍然沒有enqueue(producer仍然沒有發送出任何消息)
# 此時producer可以繼續阻塞或者将消息抛棄,此timeout值用于控制"阻塞"的時間
# -1: 無阻塞逾時限制,消息不會被抛棄
# 0:立即清空隊列,消息被抛棄
queue.enqueue.timeout.ms=-


# 當producer接收到error ACK,或者沒有接收到ACK時,允許消息重發的次數
# 因為broker并沒有完整的機制來避免消息重複,是以當網絡異常時(比如ACK丢失)
# 有可能導緻broker接收到重複的消息,預設值為3.
message.send.max.retries=

# producer重新整理topic metada的時間間隔,producer需要知道partition leader的位置,以及目前topic的情況
# 是以producer需要一個機制來擷取最新的metadata,當producer遇到特定錯誤時,将會立即重新整理
# (比如topic失效,partition丢失,leader失效等),此外也可以通過此參數來配置額外的重新整理機制,預設值600000
topic.metadata.refresh.interval.ms=
           

消費者配置檔案解釋

# zookeeper連接配接伺服器位址
zookeeper.connect=zk01:,zk02:,zk03:

# zookeeper的session過期時間,預設5000ms,用于檢測消費者是否挂掉
zookeeper.session.timeout.ms=

#當消費者挂掉,其他消費者要等該指定時間才能檢查到并且觸發重新負載均衡
zookeeper.connection.timeout.ms=

# 指定多久消費者更新offset到zookeeper中。注意offset更新時基于time而不是每次獲得的消息。一旦在更新zookeeper發生異常并重新開機,将可能拿到已拿到過的消息
zookeeper.sync.time.ms=

#指定消費組
group.id=xxx

# 當consumer消費一定量的消息之後,将會自動向zookeeper送出offset資訊
# 注意offset資訊并不是每消費一次消息就向zk送出一次,而是現在本地儲存(記憶體),并定期送出,預設為true
auto.commit.enable=true

# 自動更新時間。預設60 * 1000
auto.commit.interval.ms=

# 目前consumer的辨別,可以設定,也可以有系統生成,主要用來跟蹤消息消費情況,便于觀察
conusmer.id=xxx

# 消費者用戶端編号,用于區分不同用戶端,預設用戶端程式自動産生
client.id=xxxx

# 最大取多少塊緩存到消費者(預設10)
queued.max.message.chunks=

# 當有新的consumer加入到group時,将會reblance,此後将會有partitions的消費端遷移到新 的consumer上,如果一個consumer獲得了某個partition的消費權限,那麼它将會向zk注冊 "Partition Owner registry"節點資訊,但是有可能此時舊的consumer尚沒有釋放此節點, 此值用于控制,注冊節點的重試次數.
rebalance.max.retries=

# 擷取消息的最大尺寸,broker不會像consumer輸出大于此值的消息chunk 每次feth将得到多條消息,此值為總大小,提升此值,将會消耗更多的consumer端記憶體
fetch.min.bytes=

# 當消息的尺寸不足時,server阻塞的時間,如果逾時,消息将立即發送給consumer
fetch.wait.max.ms=
socket.receive.buffer.bytes=

# 如果zookeeper沒有offset值或offset值超出範圍。那麼就給個初始的offset。有smallest、largest、anything可選,分别表示給目前最小的offset、目前最大的offset、抛異常。預設largest
auto.offset.reset=smallest

# 指定序列化處理類
derializer.class=kafka.serializer.DefaultDecoder