返回文章列表

Kafka 叢集效能最佳化:從 Linux 系統調優到 G1GC 垃圾收集的完整實戰指南

深入探討 Kafka 叢集的全方位效能最佳化策略,從 Linux 核心參數調整、虛擬記憶體與髒頁管理、XFS 檔案系統配置,到網路堆疊調校、G1GC 垃圾收集器調優,以及機架感知部署與 ZooKeeper 整合的完整實務指南

分散式系統 訊息佇列 效能調優

Linux 系統層級的 Kafka 效能基礎

當台灣的企業建置大規模的訊息處理平台時,Kafka 往往成為核心的訊息佇列系統。然而許多團隊在部署 Kafka 叢集後發現效能無法滿足預期,訊息延遲時高時低,吞吐量遠低於硬體規格應有的表現。這些問題的根源常常不在 Kafka 本身的配置,而是在於底層的 Linux 作業系統沒有針對 Kafka 的工作負載特性進行最佳化。Kafka 作為一個高度依賴磁碟順序寫入與頁面快取的分散式系統,對於作業系統的記憶體管理、檔案系統選擇與網路堆疊配置都有特殊的要求。

虛擬記憶體管理是影響 Kafka 效能的第一個關鍵因素。Linux 核心的虛擬記憶體子系統透過 swappiness 參數控制記憶體頁面與交換空間之間的平衡策略。在早期的核心版本中,將 swappiness 設定為 0 表示只在記憶體嚴重不足時才使用交換空間,但從核心版本 3.5-rc1 開始,這個語義被改變為完全停用交換。對於 Kafka 這種對延遲敏感的應用,任何記憶體頁面被交換到磁碟都會造成嚴重的效能衰退。當 Kafka Broker 的記憶體頁面被交換出去時,後續存取這些頁面會觸發磁碟 I/O,導致訊息處理延遲突然飆高。因此現代 Linux 系統上運行 Kafka 時,建議將 swappiness 設定為 1 而非 0,這個值表示核心會盡可能避免使用交換空間,但在極端記憶體壓力下仍保留使用交換的能力,避免系統因為記憶體不足而觸發 OOM Killer 殺死程序。

髒頁管理機制直接影響 Kafka 的寫入效能與生產者延遲。Kafka 透過作業系統的頁面快取實現高效的批次寫入,生產者發送的訊息首先寫入到記憶體中的頁面快取,核心會在背景將這些髒頁同步到磁碟。dirty_background_ratio 參數控制當髒頁數量達到系統總記憶體的多少百分比時,核心開始在背景執行 pdflush 程序將髒頁寫回磁碟。預設值通常是 10% 到 20%,但對於擁有大量記憶體的現代伺服器,這個比例可能意味著數 GB 的髒頁累積。將 dirty_background_ratio 降低到 5% 能夠讓核心更積極地在背景清理髒頁,避免突發性的大量磁碟寫入。dirty_ratio 參數則定義了當髒頁達到這個閾值時,所有寫入程序都會被阻塞,強制等待髒頁被清理。適當提高這個值到 60% 到 80% 可以允許更多的記憶體用於緩衝,但也增加了系統突然當機時資料遺失的風險。在實務中,需要根據系統記憶體大小、磁碟 I/O 能力與業務對資料安全性的要求來平衡這兩個參數。

#!/bin/bash
# Linux 系統調優腳本
# 針對 Kafka Broker 的核心參數最佳化

# 虛擬記憶體參數調整
# 設定 swappiness 為 1,避免記憶體交換影響效能
# 值為 1 表示只在極端情況下使用交換空間
# 避免設定為 0,因為在新版核心中這會完全停用交換
sudo sysctl -w vm.swappiness=1

# 髒頁管理參數
# dirty_background_ratio:背景開始清理髒頁的閾值
# 設定為 5% 讓核心更積極地在背景寫入磁碟
# 避免累積過多髒頁造成突發性寫入
sudo sysctl -w vm.dirty_background_ratio=5

# dirty_ratio:強制同步寫入的閾值
# 設定為 60% 允許更多記憶體用於緩衝
# 但需要考慮當機時的資料遺失風險
# 生產環境可根據需求調整為 60-80 之間
sudo sysctl -w vm.dirty_ratio=60

# 檔案描述符與記憶體映射限制
# max_map_count:程序可建立的記憶體映射區域數量上限
# Kafka 為每個日誌段檔案建立記憶體映射
# 擁有大量分區的 Broker 需要更高的限制
# 預設值通常是 65536,建議提升到 262144
sudo sysctl -w vm.max_map_count=262144

# overcommit_memory:記憶體過度配置策略
# 0:核心根據可用記憶體估算是否允許分配(預設,建議)
# 1:總是允許過度配置(風險較高)
# 2:不允許超過 swap + (overcommit_ratio% * 實體記憶體)
sudo sysctl -w vm.overcommit_memory=0

# 網路堆疊參數調整
# 提升 socket 緩衝區大小以改善網路吞吐量

# 預設的 socket 接收緩衝區大小
# 設定為 128 KiB (131072 bytes)
sudo sysctl -w net.core.rmem_default=131072

# 預設的 socket 發送緩衝區大小
# 設定為 128 KiB
sudo sysctl -w net.core.wmem_default=131072

# socket 接收緩衝區的最大值
# 設定為 2 MiB (2097152 bytes)
# 允許應用程式配置更大的接收緩衝區
sudo sysctl -w net.core.rmem_max=2097152

# socket 發送緩衝區的最大值
# 設定為 2 MiB
sudo sysctl -w net.core.wmem_max=2097152

# TCP 相關參數調整
# tcp_wmem:TCP 發送緩衝區的 min/default/max 值
# 格式:最小值 預設值 最大值
# 4096(4KB) 65536(64KB) 2048000(約2MB)
sudo sysctl -w net.ipv4.tcp_wmem="4096 65536 2048000"

# tcp_rmem:TCP 接收緩衝區的 min/default/max 值
sudo sysctl -w net.ipv4.tcp_rmem="4096 65536 2048000"

# 啟用 TCP 視窗縮放
# 允許 TCP 使用超過 64KB 的視窗大小
# 提升長距離、高頻寬網路的效能
sudo sysctl -w net.ipv4.tcp_window_scaling=1

# TCP SYN 請求的最大佇列長度
# 提升同時連線數的處理能力
# 對於高併發的 Kafka 叢集需要更大的值
sudo sysctl -w net.ipv4.tcp_max_syn_backlog=4096

# 網路裝置接收佇列的最大長度
# 處理網路流量突發時的緩衝
# 避免封包遺失
sudo sysctl -w net.core.netdev_max_backlog=5000

# 檔案系統相關參數
# 系統範圍的檔案描述符限制
# Kafka 為每個日誌段、每個連線都需要檔案描述符
# 擁有大量分區與客戶端連線的 Broker 需要很高的限制
echo "* soft nofile 100000" | sudo tee -a /etc/security/limits.conf
echo "* hard nofile 100000" | sudo tee -a /etc/security/limits.conf

# 將變更寫入 /etc/sysctl.conf 使其永久生效
sudo sysctl -p

# 驗證設定
echo "=== 當前系統參數 ==="
echo "vm.swappiness = $(sysctl -n vm.swappiness)"
echo "vm.dirty_background_ratio = $(sysctl -n vm.dirty_background_ratio)"
echo "vm.dirty_ratio = $(sysctl -n vm.dirty_ratio)"
echo "vm.max_map_count = $(sysctl -n vm.max_map_count)"
echo "net.core.rmem_max = $(sysctl -n net.core.rmem_max)"
echo "net.core.wmem_max = $(sysctl -n net.core.wmem_max)"
echo "net.ipv4.tcp_window_scaling = $(sysctl -n net.ipv4.tcp_window_scaling)"

# 注意事項:
# 1. 這些參數需要 root 權限才能修改
# 2. 建議在測試環境驗證後再應用到生產環境
# 3. 不同的硬體配置可能需要不同的參數值
# 4. 定期監控系統效能指標以驗證調優效果
# 5. 考慮使用配置管理工具(如 Ansible)自動化部署

檔案系統的選擇與配置對 Kafka 的效能有著深遠的影響。Kafka 的設計充分利用了作業系統的頁面快取與順序寫入特性,訊息資料以日誌段檔案的形式儲存在磁碟上。XFS 檔案系統因其優異的效能表現與較少的調優需求,已成為許多 Linux 發行版的預設選擇,也是 Kafka 官方推薦的檔案系統。相較於傳統的 Ext4 檔案系統,XFS 在處理大檔案與高並發寫入時表現更好,並且能夠更有效地利用現代磁碟的效能。掛載選項的配置同樣重要,noatime 選項停用了檔案存取時間的更新,避免每次讀取檔案時都觸發額外的寫入操作。這對於 Kafka 這種頻繁讀寫日誌檔案的應用特別有價值,能夠減少不必要的磁碟 I/O,提升整體效能。

JVM 垃圾收集器的深度調優

Kafka Broker 是一個運行在 JVM 上的 Java 應用程式,垃圾收集器的行為直接影響訊息處理的延遲與吞吐量。傳統的並行垃圾收集器(Parallel GC)或 CMS(Concurrent Mark Sweep)收集器在處理大堆記憶體時常常出現長時間的 Stop-The-World 暫停,導致 Kafka Broker 無法處理新的訊息請求,客戶端超時重試引發連鎖反應。G1GC(Garbage-First Garbage Collector)自 Java 7 引入後經過多個版本的改進,在 JDK 8 與 JDK 11 中達到了生產可用的成熟度,現在已成為 Kafka 官方推薦的垃圾收集器。

G1GC 的設計理念與傳統收集器有根本性的不同。它將堆記憶體劃分為多個大小相等的區域(Region),每次垃圾收集只處理部分區域而非整個堆,從而實現可預測的暫停時間。G1GC 會優先收集垃圾最多的區域(因此得名 Garbage-First),在有限的暫停時間內最大化垃圾回收量。MaxGCPauseMillis 參數讓開發者能夠指定期望的最大暫停時間,G1GC 會盡力在這個時間限制內完成垃圾收集。對於 Kafka Broker,將這個值設定為 20 毫秒能夠確保垃圾收集暫停不會影響訊息處理的即時性。InitiatingHeapOccupancyPercent 參數控制何時觸發並發標記週期,預設值 45% 在某些場景下可能過於保守,調低到 35% 能讓垃圾收集更早開始,避免堆記憶體被快速填滿導致 Full GC。

#!/bin/bash
# Kafka Broker JVM 參數配置
# G1GC 垃圾收集器調優

# 設定 Kafka JVM 效能相關參數
export KAFKA_JVM_PERFORMANCE_OPTS="\
-server \
-Xmx6g \
-Xms6g \
-XX:MetaspaceSize=96m \
-XX:+UseG1GC \
-XX:MaxGCPauseMillis=20 \
-XX:InitiatingHeapOccupancyPercent=35 \
-XX:G1HeapRegionSize=16M \
-XX:MinMetaspaceFreeRatio=50 \
-XX:MaxMetaspaceFreeRatio=80 \
-XX:+ExplicitGCInvokesConcurrent"

# 參數說明:
# -server
# 啟用伺服器模式的 JVM,針對長時間運行的應用最佳化
# 提供更積極的 JIT 編譯與最佳化

# -Xmx6g -Xms6g
# 設定堆記憶體的最大值與初始值都為 6GB
# 使兩者相等避免執行時期動態調整堆大小
# 減少記憶體分配的開銷
# 6GB 是中等規模 Broker 的建議值
# 實際值應根據伺服器記憶體大小調整
# 建議為實體記憶體的 25-50%

# -XX:MetaspaceSize=96m
# 設定 Metaspace 的初始大小
# Metaspace 儲存類別的元資料
# 適當的初始值避免頻繁的 Metaspace GC

# -XX:+UseG1GC
# 啟用 G1 垃圾收集器
# 適合大堆記憶體與低延遲需求的應用
# 提供可預測的暫停時間

# -XX:MaxGCPauseMillis=20
# 設定垃圾收集的目標最大暫停時間為 20 毫秒
# G1GC 會盡力達成這個目標
# 較低的值可能導致更頻繁的 GC
# 需要根據實際負載與延遲需求調整
# Kafka 建議值為 20-50 毫秒

# -XX:InitiatingHeapOccupancyPercent=35
# 當堆記憶體使用率達到 35% 時觸發並發標記週期
# 預設值為 45%,調低可以更早開始 GC
# 避免堆記憶體快速填滿導致 Full GC
# 需要根據訊息流量特性調整

# -XX:G1HeapRegionSize=16M
# 設定 G1 堆區域的大小為 16MB
# G1 將堆劃分為多個固定大小的區域
# 區域大小影響 GC 的粒度
# 對於大堆記憶體,較大的區域可以減少管理開銷
# 值必須是 2 的冪次,範圍 1MB-32MB

# -XX:MinMetaspaceFreeRatio=50
# Metaspace GC 後至少保留 50% 的空閒空間
# 避免 Metaspace 頻繁擴展

# -XX:MaxMetaspaceFreeRatio=80
# Metaspace GC 後最多保留 80% 的空閒空間
# 避免 Metaspace 過度擴展浪費記憶體

# -XX:+ExplicitGCInvokesConcurrent
# 當應用程式呼叫 System.gc() 時
# 使用並發 GC 而非 Full GC
# 減少對應用程式的影響

# 額外的 GC 日誌參數(用於監控與除錯)
export KAFKA_GC_LOG_OPTS="\
-Xlog:gc*:file=/var/log/kafka/gc.log:time,tags:filecount=10,filesize=100M"

# GC 日誌參數說明:
# -Xlog:gc*:記錄所有 GC 相關事件
# file=/var/log/kafka/gc.log:日誌檔案路徑
# time,tags:包含時間戳與標籤資訊
# filecount=10,filesize=100M:日誌輪轉,保留 10 個 100MB 的檔案

# 啟動 Kafka Broker
# 這些環境變數會被 Kafka 啟動腳本讀取
# kafka-server-start.sh /path/to/server.properties

# 監控與調整建議:
# 1. 定期檢查 GC 日誌,分析暫停時間與頻率
# 2. 使用 JMX 監控堆記憶體使用情況
# 3. 觀察 Broker 的訊息處理延遲指標
# 4. 根據實際負載調整參數
# 5. 避免 Full GC,若頻繁發生需要增加堆記憶體或調整參數
@startuml
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 14
skinparam minClassWidth 100

package "Kafka 叢集效能最佳化架構" {
    component "Linux 系統層" {
        [虛擬記憶體管理\nswappiness=1] as vm
        [髒頁控制\ndirty_ratio=60] as dirty
        [檔案系統\nXFS + noatime] as fs
        [網路堆疊\nTCP 緩衝區調優] as network
    }
    
    component "JVM 層" {
        [G1GC 收集器\nMaxGCPauseMillis=20ms] as g1gc
        [堆記憶體管理\n6GB Heap] as heap
        [Metaspace 配置\n96MB 初始值] as metaspace
    }
    
    component "Kafka Broker 層" {
        [訊息接收\nNetwork Thread] as receive
        [訊息處理\nI/O Thread] as process
        [日誌寫入\nLog Segment] as log
        [頁面快取\nPage Cache] as cache
    }
    
    component "硬體資源層" {
        [CPU 核心\n多執行緒處理] as cpu
        [記憶體\nPage Cache] as memory
        [磁碟\n順序寫入] as disk
        [網路介面\n高頻寬] as nic
    }
}

vm --> memory: 記憶體分配策略
dirty --> cache: 髒頁寫回控制
fs --> disk: 檔案操作最佳化
network --> nic: 網路效能提升

g1gc --> heap: 垃圾收集管理
heap --> memory: 記憶體使用
metaspace --> memory: 元資料儲存

receive --> network: 接收訊息
process --> cache: 利用頁面快取
log --> fs: 寫入日誌檔案
cache --> memory: 快取訊息資料

cpu --> process: 執行處理邏輯
memory --> cache: 提供快取空間
disk --> log: 持久化儲存
nic --> receive: 接收網路封包

note right of vm
  避免記憶體交換
  保持低延遲
end note

note right of g1gc
  可預測暫停時間
  適合大堆記憶體
end note

note right of cache
  減少磁碟 I/O
  提升讀寫效能
end note

@enduml

上方架構圖清楚呈現了 Kafka 效能最佳化的多層次架構。從最底層的硬體資源,到 Linux 系統層的核心參數調整,再到 JVM 層的垃圾收集器配置,最後到 Kafka Broker 層的執行機制,每一層都對整體效能有關鍵影響。系統調優的核心理念是讓各層協同工作,最大化硬體資源的利用效率。

分散式部署的機架感知策略

當 Kafka 叢集規模擴展到多個 Broker 時,實體部署的拓撲結構直接影響系統的可用性與容錯能力。機架感知(Rack Awareness)配置是 Kafka 提供的一項重要特性,讓 Broker 能夠感知彼此的實體位置關係,在分配分區副本時避免將所有副本放置在相同的故障域中。在傳統的資料中心環境中,機架通常共享電源供應與網路交換機,當某個機架的電源故障或網路交換機失效時,該機架上的所有伺服器都會同時離線。若 Kafka 的某個分區的所有副本都位於同一個機架,這種硬體故障會導致該分區完全不可用,影響業務運作。

實施機架感知需要在硬體層面與軟體層面同時進行規劃。硬體層面建議為每台伺服器配置雙電源供應,連接到不同的配電單元(PDU),確保單一電源故障不會影響伺服器運作。網路方面建議配置雙網路介面,透過 bonding 或 teaming 連接到不同的網路交換機,實現網路層面的冗餘。軟體層面則是在 Kafka Broker 的配置檔中設定 broker.rack 屬性,標識該 Broker 所屬的機架或故障域。Kafka 的副本分配演算法會優先將同一分區的不同副本分散到不同的機架,確保即使整個機架離線,仍有其他機架上的副本可以提供服務。

ZooKeeper 作為 Kafka 的協調服務,儲存了叢集的元資料資訊,包含 Broker 列表、主題配置、分區分配、控制器選舉等關鍵資料。Kafka Broker 與 ZooKeeper 之間保持持續的心跳連線,任何網路延遲或連線中斷都可能導致 Broker 被錯誤地標記為離線,觸發不必要的分區重新分配。因此 ZooKeeper 叢集本身需要謹慎配置,確保低延遲與高可用性。在資源充足的情況下,建議為 Kafka 部署專屬的 ZooKeeper 叢集,避免與其他應用共享。若需要多個 Kafka 叢集共享 ZooKeeper,可以透過設定不同的 chroot 路徑實現邏輯隔離,每個 Kafka 叢集在 ZooKeeper 中使用獨立的命名空間。

// Kafka Producer 配置與使用範例
// 展示如何建立高效能的訊息生產者

import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.apache.kafka.clients.producer.Callback;
import org.apache.kafka.common.serialization.StringSerializer;

import java.util.Properties;
import java.util.concurrent.Future;

public class KafkaProducerExample {
    
    public static void main(String[] args) {
        // 建立 Kafka Producer 配置
        Properties props = new Properties();
        
        // bootstrap.servers:Kafka 叢集的初始連線位址
        // 格式:host1:port1,host2:port2,...
        // 不需要列出所有 Broker,但建議至少配置 2-3 個
        // 確保即使某個 Broker 離線仍能連接到叢集
        props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, 
                  "broker1.example.com:9092,broker2.example.com:9092,broker3.example.com:9092");
        
        // client.id:客戶端識別字串
        // 用於日誌記錄與監控,協助追蹤訊息來源
        props.put(ProducerConfig.CLIENT_ID_CONFIG, "kafka-producer-example");
        
        // key.serializer:鍵的序列化器類別
        // 將 Java 物件轉換為位元組陣列以便網路傳輸
        // StringSerializer 用於字串類型的鍵
        props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, 
                  StringSerializer.class.getName());
        
        // value.serializer:值的序列化器類別
        // 必須與 key.serializer 搭配使用
        props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, 
                  StringSerializer.class.getName());
        
        // acks:訊息確認機制
        // 0:不等待確認(最快但可能遺失訊息)
        // 1:等待 Leader 確認(平衡效能與可靠性)
        // all/-1:等待所有同步副本確認(最安全但較慢)
        // 金融交易等關鍵應用建議使用 all
        props.put(ProducerConfig.ACKS_CONFIG, "all");
        
        // retries:訊息發送失敗時的重試次數
        // 設定為較大的值(如 Integer.MAX_VALUE)
        // 搭配 max.in.flight.requests.per.connection=1 確保順序
        props.put(ProducerConfig.RETRIES_CONFIG, 3);
        
        // batch.size:批次大小(位元組)
        // Producer 會將發送到同一分區的多個訊息批次處理
        // 較大的批次可以提升吞吐量但增加延遲
        // 預設 16384 (16KB),高吞吐量場景可增加到 32KB-64KB
        props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384);
        
        // linger.ms:批次延遲時間(毫秒)
        // Producer 會等待這段時間以累積更多訊息形成批次
        // 0 表示立即發送,增加到 10-100ms 可提升批次效率
        // 需要在延遲與吞吐量之間權衡
        props.put(ProducerConfig.LINGER_MS_CONFIG, 10);
        
        // buffer.memory:Producer 緩衝記憶體總大小(位元組)
        // 用於暫存等待發送的訊息
        // 預設 33554432 (32MB)
        // 高吞吐量場景建議增加到 64MB-128MB
        props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432);
        
        // compression.type:訊息壓縮類型
        // none:不壓縮
        // gzip:高壓縮率但 CPU 消耗較高
        // snappy:平衡壓縮率與 CPU 消耗
        // lz4:低 CPU 消耗,適合即時場景
        // zstd:新型壓縮演算法,效能優異(需 Kafka 2.1+)
        props.put(ProducerConfig.COMPRESSION_TYPE_CONFIG, "lz4");
        
        // max.in.flight.requests.per.connection:
        // 單一連線允許的未確認請求數量
        // 設定為 1 確保訊息順序,但降低吞吐量
        // 設定為 5 可以提升吞吐量(需要 Kafka 1.0+)
        props.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, 5);
        
        // enable.idempotence:啟用冪等性
        // 確保訊息不會重複(需要 Kafka 0.11+)
        // 自動設定 acks=all, retries=Integer.MAX_VALUE
        // max.in.flight.requests.per.connection<=5
        props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true);
        
        // 建立 KafkaProducer 實例
        // 泛型參數指定鍵與值的類型
        KafkaProducer<String, String> producer = new KafkaProducer<>(props);
        
        try {
            // 建立訊息記錄
            // ProducerRecord 包含主題、分區(選用)、鍵、值
            ProducerRecord<String, String> record = new ProducerRecord<>(
                "user-events",           // 主題名稱
                "user-123",              // 訊息鍵(用於分區選擇)
                "User login event"       // 訊息值
            );
            
            // 非同步發送訊息
            // send() 方法立即返回 Future 物件
            // 實際網路傳輸在背景執行
            Future<RecordMetadata> future = producer.send(record, new Callback() {
                @Override
                public void onCompletion(RecordMetadata metadata, Exception exception) {
                    // 回呼函式在訊息發送完成後執行
                    if (exception != null) {
                        // 發送失敗處理
                        System.err.println("訊息發送失敗: " + exception.getMessage());
                        exception.printStackTrace();
                        
                        // 可以實作重試邏輯或記錄到死信佇列
                    } else {
                        // 發送成功處理
                        System.out.println("訊息發送成功:");
                        System.out.println("  主題: " + metadata.topic());
                        System.out.println("  分區: " + metadata.partition());
                        System.out.println("  偏移量: " + metadata.offset());
                        System.out.println("  時間戳: " + metadata.timestamp());
                    }
                }
            });
            
            // 若需要同步等待結果,可以呼叫 get()
            // RecordMetadata metadata = future.get();
            // 但這會阻塞當前執行緒,降低吞吐量
            
        } catch (Exception e) {
            System.err.println("發送訊息時發生異常: " + e.getMessage());
            e.printStackTrace();
        } finally {
            // 關閉 Producer 釋放資源
            // flush() 確保所有緩衝的訊息都被發送
            // close() 會自動呼叫 flush()
            producer.close();
        }
    }
}

// 最佳實務建議:
// 1. 重用 Producer 實例,避免頻繁建立與銷毀
// 2. 使用非同步發送搭配回呼處理結果
// 3. 根據業務需求調整 acks、retries、batch.size 等參數
// 4. 啟用冪等性確保訊息不重複
// 5. 監控 Producer 指標(如 record-send-rate、request-latency)
// 6. 處理異常並實作適當的錯誤處理邏輯
// 7. 在應用程式關閉時正確關閉 Producer

透過 Linux 系統層級的深度調優、JVM 垃圾收集器的精細配置,以及分散式部署的機架感知策略,台灣企業能夠建構高效能且高可用的 Kafka 訊息佇列叢集。從虛擬記憶體管理到髒頁控制,從 XFS 檔案系統選擇到網路堆疊調校,從 G1GC 參數調整到堆記憶體規劃,每個環節都對整體效能有著關鍵影響。無論是電商平台需要處理海量訂單訊息、金融系統需要確保交易資料的可靠傳輸,還是物聯網平台需要接收數百萬裝置的遙測資料,這些最佳化策略都能提供堅實的技術支撐。掌握 Kafka 效能調優的完整知識體系,將協助團隊在大數據時代建構穩定可靠的訊息處理基礎設施。