深入淺出系列之 -- Kafka核心設計與實踐原理

一、基礎概念 

體系結構

  • Producer:生產者

  • Consumber:消費者

  • Broker:服務代理節點(kafka實例)

消息存儲

  • 主題(Topic):kafka消息以topic爲單位進行歸類,邏輯概念

  • 分區(Partition):

    • Topic-Partition爲一對多

    • 分區在存儲層面可看做是一個可追加的日誌文件

    • 消息在追加到分區時會分配一個特定的偏移量(offset)作爲在此分區的唯一標示

    • kafka通過offset保證消息在分區內的順序性,但只保證分區有序而不保證主題有序

    • 每條消息發送到broker前,會根據分區規則分配到具體的哪個分區

容災設計

  • 多副本機制(Replica):

    • 一個分區會在多個副本中保存相同的消息

    • 副本之間是一主多從關係

    • leader副本負責讀寫操作,follower副本只負責同步消息(主動拉取)

    • leader副本故障時,從follower副本重新選舉新leader

同步狀態

  • 分區中所有副本統稱爲 AR(Assigned Replicas)

  • 所有與leader副本保持一定程度同步的副本(包括leader)組成 ISR(In-Sync Replicas)

  • 同步之後過多的副本組成 OSR(Out-of-Sync Replicas)

 

特殊偏移量

  • LEO(Log End Offset):標識當前分區下一條代寫入消息的offset

  • HW(High Watermark):高水位,標識了一個特定的offset,消費者只能拉渠道這個offset之前的消息(不含HW)

  • 所有副本都同步了的消息才能被消費,HW的位置取決於所有follower中同步最慢的分區的offset

 

二、生產者

客戶端開發

  • 消息發送步驟

    • 配置生產者客戶端參數及創建相應的生產者實例

    • Properties

    • KafkaProducer

    • 構建待發送的消息:ProducerRecord

    • 發送消息:send( ),flush( )

    • 關閉生產者實例:close( )

  • 必要參數配置:

    • bootstrap.servers:設置kafka集羣地址,並非需要所有broker地址,因爲生產者會從給定的broker中獲取其他broker信息

    • key.serializer、value.serializer:轉換字節數組到所需對象的序列化器,填寫全限類名

  • 發送模式

    • 發後即忘(fire-and-forget):只管往kafka發送而不關心消息是否正確到達,不對發送結果進行判斷處理;

    • 同步(sync):KafkaProducer.send()返回的是一個Future對象,使用Future.get()來阻塞獲取任務發送的結果,來對發送結果進行相應的處理;

    • 異步(async):向send()返回的Future對象註冊一個Callback回調函數,來實現異步的發送確認邏輯。

  • 攔截器

    • 實現ProducerInterceptor接口,在消息發送的不同階段調用

    • configure():完成生產者配置時

    • onSend():調用send()後,消息序列化和計算分區之前

    • onAcknowledgement():消息被應答之前或消息發送失敗時

    • close():生產者關閉時

    • 通過 interceptor.classes 配置指定

  • 序列化

    • 自定義序列化器:實現Serializer接口

  • 分區器

    • 在消息發送到kafka前,需要先計算出分區號,默認使用DefaultPartitioner(採用MurmurHash2算法)

    • 自定義分區器:實現Partitioner接口

    • 通過partitioner.class配置指定

 

原理分析

  • 主線程KafkaProducer創建消息,通過可能的攔截器、序列化器和分區器之後緩存到消息累加器(RecordAccumulatro)

  • 消息在RecordAccumulator被包裝成ProducerBatch,以便Sender線程可以批量發送,緩存的消息發送過慢時,send()方法會被阻塞或拋異常

  • 緩存的大小通過buffer.memory配置,阻塞時間通過max.block.ms配置

  • Kafka生產者客戶端中,通過ByteBuffer實現消息內存的創建和釋放,而RecordAccumulator內部有一個BufferPool用來實現ByteBuffer的複用

  • Sender從RecordAccumulator中獲取緩存的消息後,將ProducerBatch按Node分組,Node代表broker節點。也就是說sender只向具體broker節點發送消息,而不關注屬於哪個分區,這裏是應用邏輯層面到網絡層面的轉換

  • Sender發往Kafka前,還會保存到InFlightRequests中,其主要作用是緩存已經發出去但還沒收到相應的請求,也是以Node分組。

  • 每個連接最大緩存未響應的請求數通過max.in.flight.requests.per.connection配置(默認5)

元數據的更新

  • InFlightRequests可以獲得leastLoadedNode,即所有Node中負載最小的。leastLoadedNode一般用於元數據請求、消費者組播協議等交互。

  • 當客戶端中沒有需要使用的元數據信息或唱過metadata.max.age.ms沒有更新元數據時,就會引起元數據更新操作。


重要的生產者參數

  • acks:用來指定分區中有多少個副本收到這條消息,生產者才認爲寫入成功(默認”1")

  • “1":leader寫入即成功、“0”:不需要等待服務端相應、”-1”/“all":ISR所有副本都寫入才收到響應

  • max.request.size:限制生產者客戶端能發送的消息的最大值(默認1048576,即1m)

  • retries、retry.backoff.ms:生產者重試次數(默認0)和兩次重試之間的間隔(默認100)

  • compression.type:消息壓縮方式,可配置爲”gzip”、”snappy”、”lz4”(默認”none”)

  • connections.max.idle.ms:多久後關閉閒置的連接(默認540000,9分鐘)

  • linger.ms:生產者發送ProducerBatch等待更多消息加入的時間(默認爲0)

  • receive.buffer.bytes:Socket接收消息緩衝區的大小(默認32768,32k)

  • send.buffer.bytes:Socket發送消息緩衝區的大小(默認131072,128k)

  • request.timeout.ms:Producer等待請求響應的最長時間(默認30000ms),這個值需要比broker參數replica.lag.time.max.ms大

 

三、消費者

消費者與消費組

  • 每個分區只能被一個消費組的一個消費者消費

  • 消費者數大於分區數時,會有消費者分配不到分區而無法消費任何消息

  • 消費者並非邏輯上的概念,它是實際的應用實例,它可以是一個錢程,也可以是一個進程。

 

客戶端開發

  • 消費步驟

    • 配置消費者客戶端參數及創建KafkaConsumer實例

    • 訂閱主題

    • 拉取消息並消費

    • 提交消費位移

    • 關閉實例

  • 必要的參數配置

    • bootstrap.servers:集羣broker地址清單

    • group.id:消費組名稱

    • key.deserializer、value.deserializer`:反序列化器

  • 訂閱主題和分區

    • subscribe():訂閱主題

    • assign():訂閱指定主題分區

    • 通過partitionFor()方法先獲取分區列表

    • unsubscribe():取消訂閱

  • 消息消費

    • poll():返回的是所訂閱的主題(分區)上的一組消息,可設定timeout參數來控制阻塞時間

  • 位移提交

    • 提交的offset爲 lastConsumedOffset + 1

    • lastConsumedOffset:上一次poll拉取到的最後一條消息的offset

  • 控制或關閉消費

    • pause()、resume():暫停和恢復某分區的消費

  • 指定位移消費

    • seek():指定offset消費

    • beginingOffsets(),endOffsetes(),offstesForTimes():獲取開頭、末尾或指定時間的offset

    • seekToBeginning、seekToEnd():從開頭、末尾開始消費

  • 再均衡

    • 在subcribe()時,可以註冊一個實現ConsumerRebalanceListener接口的監聽器

    • onPartionsRevoked():消費者停止讀取消息之後,再均衡開始之前

    • onPartitionsAssigned():重新分配分區後,開始讀取消費前

  • 攔截器

    • 實現ConsumerInterceptor接口

    • poll()返回之前,會調用onConsume()方法,提交完offset後會調用onCommit()方法

  • 多線程實現

    • KafkaProducer是線程安全的,但KafkaConsumer是非線程安全的,acquire()方法可檢測當前是否只有一個線程在操作,否則拋出異常

    • 推薦使用單線程消費,而消息處理用多線程

重要的消費者參數

  • fetch.min.bytes:一次請求能拉取的最小數據量(默認1b)

  • fetch.max.bytes:一次請求能拉取的最大數據量(默認52428800b,50m)

  • fetch.max.wait.ms:與min.bytes有關,指定kafka拉取時的等待時間(默認500ms)

  • max.partition.fetch.bytes:從每個分區裏返回Consumer的最大數據量(默認1048576b,1m)

  • max.poll.records:一次請求拉取的最大消息數(默認500)

  • connections.max.idle.ms:多久後關閉閒置連接,默認(540000,9分鐘)

  • receive.buffer.bytes:Socket接收消息緩衝區的大小(默認65536,64k)

  • send.buffer.bytes:Socket發送消息緩衝區的大小(默認131072,128k)

  • request.timeout.ms:Consumer等待請求響應的最長時間(默認30000ms)

  • metadata.max.age.ms:元數據過期時間(默認30000,5分鐘)

  • reconnect.backoff.ms:嘗試重新連接指定主機前的等待時間(默認50ms)

  • retry.backoff.ms:嘗試重新發送失敗請求到指定主題分區的等待時間(默認100ms)

  • isolation.level:消費者的事務隔離級別(具體查看進階篇:事務)

 

四、主題與分區

主題的管理

  • 創建

    • broker設置auto.create.topics.enable=true時,生產者發送消息時會自動創建分區數爲num.partitions(默認1),副本因子爲default.replication.factor(默認1)的主題

    • 通過kafka-topics.sh創建:create指令

      • kafka-topics.sh --zookeeper <zkpath> --create --topic <topic> --partitions <N> --replication-factor <N>

      • 手動分配副本:--replica-assignment

        • --replica-assignment 2:0:1,1:2:0,0:1:2

        • partion1 AR:2,0,1

        • partion2 AR:1:2:0

        • partion3 AR:0:1:2

    • 設定參數:--config <key=value>

  • 分區副本的分配

    • 使用kafka-topics.sh創建主題內部分配邏輯按機架信息劃分兩種策略:

      • 未指定機架信息分配策略:assignReplicasToBrokersRackUnaware()方法

      • 指定機架分配策略:assignReplicasToBrokersRackAware()方法

    • 當創建一個主題時,不管用什麼方式,實質上是在zk的/broker/topics節點下創建與該主題對應的子節點並寫入分區副本分配方案,並且在/config/topics節點下創建與該主題相關的子節點並寫入主題配置信息

  • 查看:kafka-topics.sh腳本的 list、describe指令

  • 修改:kafka-topics.sh腳本的 alter指令

  • 配置管理:kafka-configs.sh腳本

  • 刪除:kafka-topics.sh腳本的 delete指令

 

初識KafkaAdminClient

  • KafkaAdminClient可實現以調用API的方式對Kafka進行管理

  • 主題合法性

    • 通過KafkaAdminClient創建主題可能不符合規範,可以在broker端設置create.topic.policy.class.name來指定一個類驗證主題創建時的合法性,這個類需要實現ClreateTopicPolicy接口,放入Kafka源碼,並重新編譯

 

分區的管理

  • 優先副本(preferred replica/preferred leader)

    • 優先副本即 AR 集合中的第一個副本,當分區leader出現故障時,會直接使用優先副本作爲新的leader

    • kafka-perferred-replica-election.sh可進行優先副本選舉操作

  • 分區重分配

    • 解決問題:

    • 將某節點上的分區副本遷移至其他節點:宕機遷移失效副本、有計劃下線節點遷移副本

    • 注意,下線前最好先關閉或重啓此broker,保證不是leader節點,減少了節點間流量複製

    • 向新增節點分配原有主題分區副本

    • 集羣中新增節點時,只有新創建的主題分區纔有可能分配到新節點上,需要把老主體的分區分配到新節點上

    • 可使用kafka-reassign-partitions.sh腳本

  • 複製限流

    • 數據複製會佔用額外的資源,如果重分配的量太大必然會嚴重影響整體的性能。可以通過對副本間的複製流量加以限制來保證重分配期間整體服務不會受太大的影響,可分別限制follower副本複製速度和leader副本傳輸速度

    • 通過kafka-config.sh或 kafka-reassign-partitions.sh配置

    • broker級別:follower/leader.replication.throttled.rate=N

    • topic級別:follower/leader.replication.throttled.replicas=N

    • 分區重分配過程中的臨時限流策略

    • 原AR會應用leader限流配置

    • 分區移動的目的地會應用follower限流配置

    • 重分配所需的數據複製完成後,臨時限流策略會被移除

  • 修改副本因子

    • 通過kafka-reassign-partitions.sh配置

    • 如何選擇合適的分區數

  • 性能測試工具

    • 生產者性能測試:kafka-producer-perf-test.sh腳本

    • 消費者性能測試:kafka-consumer-perf-test.sh腳本

  • 分區數和吞吐量的關係

    • 在一定限度內,吞吐量隨分區數增加而上升,但由於磁盤、文件系統、I/O調度策略等影響,到一定程度時吞吐量會存在瓶頸或有所下降

  • 考量因素

    • 如果分區數過多,當集羣中某個broker宕機,就會有大量分區需要進行leader角色切換,這個過程會耗費一定的時間,並且在此期間這些分區不可用。分區數越多,kafka的正常啓動和關閉耗時也會越長,同時也會增加日誌清理的耗時

    • 建議將分區數設定爲broker的倍數

 

 

 

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