實戰錄 | Kafka-0.10 Consumer源碼解析

實戰錄》導語

前方高能!請注意本期攻城獅幽默細胞爆表,坐地鐵的拉好把手,喝水的就建議暫時先別喝了:)本期分享人爲雲端衛士大數據工程師韓寶君,將帶來Kafka-0.10 Consumer源碼解析。本文3346字,大約需要花費8-10分鐘時間閱讀。

Kafka在0.9版本之後,對Consumer進行了重新設計,本人也在網上看了一些Consumer源碼解析博客,發現講的都不是很詳細,看過之後自己嘗試去看源碼的時候還是很費勁,本編將對kafka Consumer模塊進行詳細解析,一行一行代碼的和大家講解,內容很多,可能會分好幾篇和大家分享。

看源碼其實也是一件很耗時的事情,要想從代碼裏看懂那些大牛們的意圖,肯定要費一番功夫,大家撐住勁!畢竟堅持、不懈纔是硬道理嘛!

來,衝上一杯咖啡,撒一點香菜,犯困的在和點辣椒油。我們開始:

1、從Apache網站下載源碼包或github上直接Checkout。2、搭建源碼閱讀環境。

以上兩步估計喫過奶的人都會(AD鈣奶,吃了身體棒),真要是沒喫過,就先別看源碼了,先回家喫奶吧!

啓動

源碼包中examples目錄下有一個消費者的demo,0.10版本和之前的稍微有些差別,但改動不大,使用起來更方便,如下:

我們直接進入到第39行代碼:new一個KafkaConsumer消費者實例,進入下面的構造方法中:

點擊this:

在該構造方法中,首先會new一個ConsumerConfig的實例,並把key和value反序列化類的信息添加到properties中。

新建ConsumerConfig實例

在新建ConsumerConfig實例時:

1、首先會執行static靜態代碼塊,在靜態代碼塊中會新建一個ConfigDef實例,通過調用ConfigDef的define方法把一些Consumer端的一些配置信息包括權限配置信息等放入ConfigDef實例的configKeys中存儲2、再次會調用其父類AbstractConfig的構造方法。

1.1.1 static靜態代碼塊

這個靜態代碼塊的代碼很長,就貼一點意思意思:意思意思是啥意思?【西北風跑得快,我想和你談戀愛】的意思。

點擊define方法,一路狂點,最終會到這個方法:

說明:

1、如果configKeys中已經包含了配置名,報錯(該配置被定義兩次)。2、group:配置屬於哪個組,在該靜態代碼塊中,49個配置的group都沒有定義,最終ConfigDef實例中[update]groups集合爲empty。3、defaultValue:默認值,49個配置基本都定義了默認值,如果Type是String或List類型,一般默認值爲””,如果Typ[update]e是Boolean類型,一般都會有初始值true或false,如果Type是int或Long類型,都會有默認值。4、new ConfigKey實例:ConfigKey的構造方法其實比較簡單,唯一有點迷糊的有可能就是Validator,它是一個接口,有2個實現類Range、ValidString,大家發現沒,在define方法中會調用atLeas[add]t和in兩個方法,分別生成Range和ValidString對象的,大家可以自己點擊進去看下,比較簡單,就不貼代碼了。

1.1.2執行父類構造方法

說明:Map<?, ?> originalsfor其實就是我們剛開始定義的properties

1、循環裏是檢查properties裏的key類型,如果不是String,報錯,key必須[add]爲String類型。

2、最重要的是第55行代碼,調用ConfigDef的parse方法將Map<String, ConfigKey>configKeys 解析成Map<String, Object> values。

說明:

2.1、第407行調用undefinedDependentConfigs;方法,該方法是校驗每個配置(configKeys裏的49個配置)所依賴的其他配置是否定義,在define方法中,那49個配置都沒有聲明依賴其他的配置,所以此處該方法的返回值[update]是empty。2.2、下面的邏輯就是遍歷configKeys,把properties裏用戶的配置覆蓋原始的配置。2.2.1、如果我們自定義的properties中包含key.name,會將key.name在properties中對應的值解析成相應的類型,最後放入到map中,ma[add]p中的value就是根據我們在define方法中定義的配置類型調用parseType解析而來的值,有的是List,有的是Boolean等。2.2.2、Validator是一個接口,它有兩個實現類,Range和ValidString,大家可以自己看一下這兩個類的ensureValid方法,實現都比較簡單。3、used:實例化一個線程安全的HashSet,該集合中存儲哪些key被使用過[add]。4、logAll:打印配置。

添加反序列化類信息

調用ConsumerConfig的靜態方法addDeserializerToConfig把key和value反序列化類信息添加到properties中。

至此,ConsumerConfig配置這塊的代碼邏輯已經結束,是不是很簡單,下面就是決定你是否能更深入的關鍵部分了。

執行KafkaConsumer構造方法

接下來會進入KafkaConsumer重載的構造方法:

從596-607行,很簡單,直接從config中取值,看不懂的回家喫奶補補。

1.3.1 Metrics

說明:

1、實例化一個LinkedHashMap,並把clientId存入map中2、new一個MetricConfig實例,MetricConfig構造方法中會初始化一些成員變量

this.quota = null;this.samples = 2; //實例個數this.eventWindow = Long.MAX_VALUE; //事件窗口//時間窗口this.timeWindowMs = TimeUnit.MILLISECONDS.convert(30, TimeUnit.SECONDS);this.tags = new LinkedHashMap<>;

接下來調用:

samples方法:修改this.samples的值,返回MetricConfig當前對象timeWindow:修改時間窗口(this.timeWindowMs)的值,返回MetricConfig當前對象tags:就是把那個有clientId的map複製給this.tags

3、因爲我們並沒有定義metric.reporters,該配置的默認值也是””,所以此時reporters是empty,默認加一個JmxReporter(這個類其實也可以不瞭解,在kafka調優的時候會把JMX監控關掉),其實我們也可以自定義一個reporter implements MetricsReporter{},這和Storm的差不多,我曾經寫過把Storm的metrics統計信息發送到Ganglia用圖形化顯示出來,kafka也是一樣,我們也可以在自定義的reporter中把kafka有關的metrics 信息存到數據庫或寫到文件或發送到Ganglia

4、實例化Metrics,這裏面其實還是有一些邏輯的,代碼就不貼了,都是圖片也不好,看着會很亂。

4.1、找到Metrics類的第129行,對reporter進行初始化,我們以JmxReporter爲例。其實這時候它什麼都沒幹,浪一圈就回來了,因爲此時參數metrics爲empty,但addAttribute、reregister這兩個方法還是講一下。

4.1.1、addAttribute:

  • 調用KafkaMetric的metricName方法,返回KafkaMetric實例的成員變量MetricName。

  • 調用getMBeanN[add]ame方法構造一個組件名稱,這個方法很普通,大家都沒問題。

  • 判斷mbeans中是否包含剛纔的組件名稱,如果不包含,創建一個 KafkaMbean實例put到mbeans中,把KafkaMetric實例存到KafkaMbean實例實例的map中。

4.1.2、reregister:先取消註冊,在註冊,用到的是jdk自帶的api,感興趣的可以自行百度

4.2、boolean enableExpiration:metrics實例是否可以垃圾收集超時的sensors,如果可以就啓動一個單線程的定時器任務去[update]移除超時的sensors,包括他的子sensors和KafkaMetric,如果不可以,定時器賦值爲null。

4.3、

  • 首先構造MetricName實例(點擊metricName方法,一路看下去,代碼很簡單,關鍵一點是MetricName實例中的tags中存的就是Metrics實例config中tags中的值)

  • 構造一個匿名對象,該對象實現了Measurable接口並重寫了measure方法

  • 接下來就是最重要的addMetric方法,一路點下去你會走到代碼的第344行,KafkaMetric的構造方法比較簡單,都是直接賦值。registerMetric方法就是把KafkaMetric放到metrics中存儲,遍歷reporters,以JmxReporter爲例,調用JmxReporter的met[add]ricChange方法,在metricChange方法中,直接調用了addAttribute和reregister兩個方法,請參考上面4.1.1、4.1.2

1.3.2 Metadata

說明:

1、retry.backoff.ms:在發送一個失敗的請求給一個topic後,試圖重試發送請求之前等待的時間量。2、Metadata的構造方法中基本上都是直接賦值,除了cluster之外。3、點擊Cluster.empty;方法:最終會走到Cluster類的第52行(Cluster類的構造器),這裏的邏輯其實是能看懂的,就是對map的操作,還是簡單講一下吧。

3.1、根據參數節點結合nodes構造Node的id到Node的映射(nodesById)3.2、根據參數分區信息partitions構造TopicPartition到PartitionInfo的映射(partitionsByTopicPartition)3.3、接下來創建partsForTopic和partsForNode兩個map實例,遍歷參數nodes和partitions,將對應的值(你看代碼就知道)存入兩個map中3.4、剩下的邏輯差不多,就是對map的操作,邏輯不難。提一下這個方法,有可能有的同學還不清楚,Collections.unmodifiableList(copy);意思是說:把copy這個list變成不可修改的集合。

4、metadata.update方法

4.1、Cluster.bootstrap(addresses):

遍歷addresses,構造Node實例放入list中接下來就是1.3.2中第3節的邏輯,此時nodes是有值的

4.2、update:更新cluster的元數據信息

4.2.1、該方法中對成員變量進行賦值操作

this.needUpdate = false;this.lastRefreshMs = now;this.lastSuccessfulRefreshMs = now;this.version += 1;

4.2.2、遍歷listeners,此時該集合爲empty,不會做任何操作,後續在ConsumerCoordinator類中,會增加metadata的listener,如下:

4.2.3、needMetadataForAllTopics:該metadata實例是否只保存該metadata中topics集合中所有的topic的元數據信息。

  • 如果不是,直接賦值,保留cluster中所有topic的元數據信息。

  • 如果是,調用getClusterForCurrentTopics,該方法會移除不在metadata的topics中的cluster中存儲的未經授權的topic,遍歷topics獲得所有topic對應的PartitionInfo,重新new一個Cluster對象複製給this.cluster。

構造Cluster的邏輯請參考1.3.2的1、2、3

1.3.3 ConsumerNetworkClient

說明:

1、創建一個ChannelBuilder實例,最後很簡單的new了一個PlaintextChannelBuilder實例,調用channelBuilder的configure方法,在configure方法中最終創建了的是 DefaultPrincipalBuilder實例。2、創建一個Selector實例,在Selector的構造方法中,或新建一個java nio的選擇器實例和SelectorMetrics實例(請參考1.3.5中4.1、4.2、4.3)。3、創建一個NetworkClient實例,在NetworkClient的構造方法中,會創建DefaultMetadataUpdater、InFlightRequests、ClusterConnectionStates 3個對象,都比較簡單。4、創建一個ConsumerNetworkClient實例。

ConsumerNetworkClient的構造方法實現比較簡單,但這個類很重要,向服務器發 送響應請求和獲取服務器的響應處理邏輯基本上都在這裏。

1.3.4 OffsetResetStrategy

說明:同學們看到這個是不是心情猛一得勁,這個是最簡單的,估計大家都喜歡,我也喜歡,但這個類很重要,consumer訂閱topic和fetch數據都會用到它。

1、調用valueOf方法把一個字符串變成枚舉類型

2、SubscriptionState的構造方法也很簡單,都是直接賦值或創建對象,不用多說

1.3.5 ConsumerCoordinator

說明:

爲了解決之前版本的High Level Consumer存在Herd Effect和Split Brain的問題,新的Consumer使用了中心協調器(Coordinator),在所有的Broker中選舉出一個Broker作爲 Coordinator,由它在Zookeeper上設置Watch,從而判斷是否有Partition或者Consumer的增減,然後生成Rebalance命令,並檢查是否這些Rebalance 在所有相關的Consumer 中被執行成功,如果不成功則重試,若成功則認爲此次Rebalance成功,這個過程跟Replication Controller非常類似。

1、調用ConsumerConfig的getConfiguredInstance方法通過反射機制創PartitionAssignor實例,PartitionAssignor爲接口,該接口是定義用於爲消費者分配分區的算法,此處是創建了它的子類RangeAssignor的實例,另外還有RoundRobinAssignor、MockPartitionAssignor等,後面會詳細講解。2、由於沒有給interceptor.classes賦值對應的class的信息,通過調用ConsumerConfig的getConfiguredInstance方法時返回爲empty,所以interceptors爲null。3、創建DefaultOffsetCommitCallback實例對象,提交offset完成之後會調用該對象的onComplete方法4、執行ConsumerCoordinator父類AbstractCoordinator的構造方法。再該構造方法中會 創建Heartbeat、HeartbeatTask、GroupCoordinatorMetrics 3個實例,前2個比較簡單。重點講一下第3個GroupCoordinatorMetrics。

4.1、調用metrics的sensor方法返回一個Sensor的實例,點擊sensor方法,一路點下去最終會走到這裏,該方法是Metrics累的方法,藍色這行是從sensors這個map中根據名稱找出對應的Sensor,如果爲null,new一個實例並放入sensors中,如果parents不爲null,遍歷parents,找出每個parent的子Sensor的集合,並把剛纔所創建的Sensor的實例放入集合中。

4.2、調用metrics的metricName方法返回一個MetricName的實例,這個就比較簡單了,構造方法中也是直接賦值。

4.3、調用Sensor的add方法把剛纔創建的MetricName實例和SampledStat類型的實例作爲參數,在這裏說一下SampledStat,就是metrics的一些統計,它的子 類有Max、Min、Count、Avg等,和Storm的CountMetric、ReducedMetric等很類似,kafka要比Storm的稍微麻煩一點。最終會走到這個方法:

首先構造一個KafkaMetric的實例,KafkaMetric的構造方法很簡單,就是給成員變量直接賦值其次註冊metric,調用Metrics的registerMetric方法把KafkaMetric註冊到map中存儲,實現統計。剩下兩行自己看看。

4.4、該構造方法中剩下的代碼和上面的幾乎一模一樣,請參照4.1、4.2、4.3,包括最後的metrics.addMetric方法和4.3邏輯一樣

5、執行ConsumerCoordinator的構造方法

5.1、創建MetadataSnapshot實例,不用說大家都能看懂5.2、給元數據增加listener:addMetadataListener,具體Listener中的onMetadataUpdate方法什麼時候會被調用,後面會詳細解析,其實這裏的邏輯是很簡單的,只是你現在還不清楚是什麼意思5.3、創建ConsumerCoordinatorMetrics實例,請參考4.1、4.2、4.3

1.3.6 實例化key/value反序列化實例

說明:1、如果keyDeserializer==null,調用ConsumerConfig的getConfiguredInstance方法通過反射機制創建keyDeserializer對象(IntegerDeserializer),調用IntegerDeserializer的configure方法,其實什麼都沒有做2、如果keyDeserializer!=null,調用ConsumerConfig的ignore方法,把參數key放入used Set集合中3、如果valueDeserializer==null,調用ConsumerConfig的getConfiguredInstance方法通過反射機制創建valueDeserializer對象(StringDeserializer),調用StringDeserializer的configure方法,獲取編碼,默認爲utf-84、如果keyDeserializer!=null,調用ConsumerConfig的ignore方法,把參數key放入used Set集合中

1.3.7 Fetcher

說明:

1、構造Fetcher實例2、在Fetcher的構造方法中會創建FetchManagerMetrics實例3、FetchManagerMetrics構造方法中的邏輯請參考1.3.5中4.1、4.2、4.3

1.3.8 其他

說明:1、logUnused方法:打印我們自己定義的properties中沒有使用到的配置2、registerAppInfo方法:

  • 實例化一個ObjectName實例

  • 實例化一個AppInfo實例

  • 註冊組件,用到的是jdk自帶的api,感興趣的可以自行百度

總結

本編只是大致講解了下創建Consumer的流程,我相信至少流程上大家應該很清楚了,這樣就可以了,一些細節暫時不明白沒關係,後面還會更詳細更全面的解析,包括Consumer的整體架構、負載均衡等。

由於時間有限,這邊文章寫的也很倉促,一些流程圖、架構圖也沒有畫,若有不對的地方歡迎指正,如果有時間,下一編會講Consumer訂閱topic,儘量講的更詳細一些。讓大家看着舒服、愉悅、看着就想要!如果不在這裏寫,也會在我的博客中發佈(http://blog.csdn.net/u012749737),謝謝大家!

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