提到訊息系統,目前最火熱的非 Kafka 莫屬,公司也打算利用 Kafka 進行各業務日誌統一收集,這裡結合自己的實踐來分享一下具體的配置及使用。Kafka 版本 0.10.0.1
更新記錄 2016.08.15: 初稿 介紹
作為雲端運算大資料的套件,Kafka 是一個分布式的、可分區的、可複製的訊息系統。該有的功能基本都有,而且有自己的特色: 以 topic 為單位進行訊息歸納 向 topic 發布訊息的是 producer 從 topic 擷取訊息的是 consumer 叢集方式運行,每個服務叫 broker 用戶端和伺服器通過 TCP 進行通訊
在Kafka叢集中,沒有“中心主節點”的概念,叢集中所有的伺服器都是對等的,因此,可以在不做任何配置的更改的情況下實現伺服器的的添加與刪除,同樣的訊息的生產者和消費者也能夠做到隨意重啟和機器的上下線。
對每個 topic 來說,Kafka 會對其進行分區,每個分區都由一系列有序的、不可變的訊息組成,這些訊息被連續的追加到分區中。分區中的每個訊息都有一個連續的序號叫做 offset,用來在分區中唯一的標識這個訊息。
發布訊息通常有兩種模式:隊列模式(queuing)和發布-訂閱模式(publish-subscribe)。隊列模式中,consumers 可以同時從服務端讀取訊息,每個訊息只被其中一個 consumer 讀到;發布-訂閱模式中訊息被廣播到所有的 consumer 中。更常見的是,每個 topic 都有若干數量的 consumer 組,每個組都是一個邏輯上的『訂閱者』,為了容錯和更好的穩定性,每個組由若干 consumer 組成。這其實就是一個發布-訂閱模式,只不過訂閱者是個組而不是單個 consumer。
通過分區的概念,Kafka可以在多個consumer組並發的情況下提供較好的有序性和負載平衡。將每個分區分只分發給一個consumer組,這樣一個分區就只被這個組的一個consumer消費,就可以順序的消費這個分區的訊息。因為有多個分區,依然可以在多個consumer組之間進行負載平衡。注意consumer組的數量不能多於分區的數量,也就是有多少分區就允許多少並發消費。
Kafka 只能保證一個分區之內訊息的有序性,在不同的分區之間是不可以的,這已經可以滿足大部分應用的需求。如果需要 topic 中所有訊息的有序性,那就只能讓這個 topic 只有一個分區,當然也就只有一個 consumer 組消費它。 單機配置
按照下列步驟即可(來自官網教程)
1. 下載 Kafka 下載 wget http://apache.01link.hk/kafka/0.10.0.0/kafka_2.11-0.10.0.0.tgz 或者 wget http://ftp.cuhk.edu.hk/pub/packages/apache.org/kafka/0.10.0.0/kafka_2.11-0.10.0.0.tgz(看哪個源比較快) 解壓 tar -xzf kafka_2.11-0.10.0.0.tgz 進入檔案夾 cd kafka_2.11-0.10.0.0/
2. 啟動服務 啟動 ZooKeeper bin/zookeeper-server-start.sh config/zookeeper.properties &(利用 &放到後台方便繼續操作) 啟動 Kafka bin/kafka-server-start.sh config/server.properties &
3. 建立一個叫做 dawang 的 topic,它只有一個分區,一個副本 建立 bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic dawang 查看 bin/kafka-topics.sh --list --zookeeper localhost:2181 還可以配置 broker 讓它自動建立 topic
4. 發送訊息。Kafka 使用一個簡單的命令列producer,從檔案中或者從標準輸入中讀取訊息並發送到服務端。預設的每條命令將發送一條訊息。 發送訊息 bin/kafka-console-producer.sh --broker-list localhost:9092 --topic dawang(然後可以隨意輸入內容,斷行符號可以發送,ctrl+c 退出)
5. 啟動 consumer。可以讀取訊息並輸出到標準輸出: 接收訊息 bin/kafka-console-consumer.sh --zookeeper localhost:2181 --topic dawang --from-beginning 在一個終端中運行 consumer 命令列,另一個終端中運行 producer 命令列,就可以在一個終端輸入訊息,另一個終端讀取訊息。這兩個命令都有自己的選擇性參數,可以在啟動並執行時候不加任何參數可以看到協助資訊。
6. 搭建一個多個 broker 的叢集,啟動有 3 個 broker 組成的叢集,這些 broker 節點也都在本機
首先複製一下設定檔:cp config/server.properties config/server-1.properties 和 cp config/server.properties config/server-2.properties
兩個檔案需要改動的內容為:
config/server-1.properties: broker.id=1 listeners=PLAINTEXT://:9093 log.dir=/tmp/kafka-logs-1 config/server-2.properties: broker.id=2 listeners=PLAINTEXT://:9094 log.dir=/tmp/kafka-logs-2
這裡我們把 broker id, 連接埠號碼和日誌地址配置成和之前不一樣,然後我們啟動這兩個 broker:
bin/kafka-server-start.sh config/server-1.properties & bin/kafka-server-start.sh config/server-2.properties &
然後建立一個複製因子為 3 的 topic
bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 3 --partitions 1 --topic oh3topic
可以使用 describe 命令來顯示 topic 詳情
> bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic oh3topic Topic:oh3topic PartitionCount:1 ReplicationFactor:3 Configs: Topic: oh3topic Partition: 0 Leader: 0 Replicas: 0,1,2 Isr: 0,1,2
這裡簡單解釋一下 Leader 是給定分區的節點編號,每個分區的部分資料會隨機指定不同的節點 Replicas 是該日誌會儲存的複製 Isr 表示正在同步的複製
我們也可以來看看之前的另一個 topic 的情況
> bin/kafka-topics.sh --describe --zookeeper localhost:2181 --topic dawang Topic:dawang PartitionCount:1 ReplicationFactor:1 Configs: Topic: dawang Partition: 0 Leader: 0 Replicas: 0 Isr: 0
最後我們可以按照同樣的方法來生產和消費訊息,例如
# 生產 bin/kafka-console-producer.sh --broker-list localhost:9092 --topic oh3topic # 消費 bin/kafka-console-consumer.sh --zookeeper localhost:2181 --from-beginning --topic oh3topic
開倆終端就可以一邊生產訊息,一邊消費訊息了。 注意事項
如果要配置自訂連接埠,server.properties 中 listeners 一定要配置成為 IP 位址;如果配置為 localhost 或伺服器的 hostname,在使用 java 發送資料時就會拋出異
# 建立 topic bin/kafka-topics.sh --create --zookeeper bi03:2181 --replication-factor 1 --partitions 1 --topic logs # 生產訊息 bin/kafka-console-producer.sh --broker-list localhost:13647 --topic logs # 消費訊息 # bin/kafka-console-consumer.sh --zookeeper localhost:2181 --topic logs
如果 Zookeeper 出現 fsync-ing the write ahead log in SyncThread:1 took 2243ms which will adversely effect operation latency. See the ZooKeeper troubleshooting guide,是因為 FOLLOWER 在跟 LEADER 同步時,fsync 操作時間過長,導致逾時。增加 tickTime 或者 initLimit 和 syncLimit 的值即可 叢集配置
kafka 使用 ZooKeeper 用於管理、協調代理。每個 Kafka 代理通過 Zookeeper 協調其他 Kafka 代理。當 Kafka 系統中新增了代理或某個代理失效時,Zookeeper 服務將通知生產者和消費者。生產者與消費者據此開始與其他代理協調工作。 安裝 Java
先給兩台機子安裝 Java
sudo add-apt-repository -y ppa:webupd8team/java sudo apt-get update sudo apt-get -y install oracle-java8-installer 更新 Hosts
這裡用兩台機器做例子(理論上最好是 3 台起步,偶數個不是不可以的,但是zookeeper叢集是以宕機個數過半才會讓整個叢集宕機的,所以奇數個叢集更佳),分別配置 /etc/hosts 檔案為
127.0.0.1 localhost 10.1.1.164 bi03 10.1.1.44 bi02 修改 Zookeeper 設定檔
修改 config/zookeeper.properties 為
dataDir=/data/home/logger/kafka_2.11-0.10.0.0/zookeeper-logs/ clientPort=2181 # maxClientCnxns=0 tickTime=2000 initLimit=5 syncLimit=2 server.1=bi03:13645:13646 server.2=bi02:13645:13646
參數的意義為: initLimit: zookeeper叢集中的包含多台 server,其中一台為 leader,叢集中其餘的 server 為 follower。initLimit 參數配置初始化串連時,follower 和 leader 之間的最長心跳時間。此時該參數設定為 5,說明時間限制為 5 倍 tickTime,即 5*2000=10000ms=10s syncLimit: 該參數配置 leader 和 follower 之間發送訊息,請求和應答的最大時間長度。此時該參數設定為 2,說明時間限制為 2 倍 tickTime,即 4000ms server.X=A:B:C 其中 X 是一個數字, 表示這是第幾號 server。A 是該 server 所在的 IP 位址。B 配置該 server 和叢集中的 leader 交換訊息所使用的連接埠。C 配置選舉 leader 時所使用的連接埠。 給伺服器編號
在 dataDir 目錄下建立一個 myid 檔案,分別為
# server.1 echo 1 > myid # server.2 echo 2 > myid 啟動 Zookeeper
然後在每台機子上啟動 zookeeper 服務
bin/zookeeper-server-start.sh config/zookeeper.properties &
所有機子的 zookeeper 都啟動之前會報錯,這都是正常的
如果不想要任何輸出
nohup bin/zookeeper-server-start.sh config/zookeeper.properties & 修改 Kafka 設定檔
修改 config/server.properties,幾個要改的部分是
# 允許刪除 topic delete.topic.enable=true broker.id=0 # 這裡不能重複 listeners=PLAINTEXT://bi03:13647 # 這裡要配置成原生 host name # 這裡需要配置成外網能夠訪問的地址及連接埠 advertised.listeners=PLAINTEXT://external.ip:8080 log.dirs=/data/home/logger/kafka_2.11-0.10.0.0/kafka-logs num.partitions=2 zookeeper.connect=bi03:2181,bi02:2181 啟動 Kafka
在每個節點上執行
bin/kafka-server-start.sh config/server.properties &
如果不想要任何輸出
nohup bin/kafka-server-start.sh config/server.properties & 驗證安裝
建立一個 topic
bin/kafka-topics.sh --create --zookeeper bi03:2181,bi02:2181 --replication-factor 2 --partitions 1 --topic test
查看叢集狀態
bin/kafka-topics.sh --describe --zookeeper bi03:2181,bi02:2181 --topic test
生產訊息,這裡注意要生產到前面設定的監聽連接埠,而不是 zookeeper 的連接埠
bin/kafka-console-producer.sh --broker-list bi03:13647,bi02:13647 --topic test
消費訊息,這裡注意是 zookeeper 的連接埠,而不是 kafka 的連接埠
bin/kafka-console-consumer.sh --zookeeper bi03:2181,bi02:2181 --from-beginning --topic test
顯示 topic 列表
bin/kafka-topics.sh --zookeeper bi03:2181,bi02:2181 --list
刪除 topic
bin/kafka-topics.sh --zookeeper bi03:2181,bi02:2181 --delete --topic hello 其他配置
Kafka 使用索引值對的屬性檔案格式來進行配置,比如 config/server.properties,具體的值可以從檔案中讀取,或者在代碼中進行指定。最重要的三個屬性是: broker.id: broker 的編號,不能相同 log.dirs: 日誌儲存的檔案夾,預設為 /tmp/kafka-logs zookeeper.connect: zookeeper 的 host
其他一些我覺得比較有用的屬性為 auto.create.topics.enable 是否允許自動建立 topic,boolean 值,預設為 true auto.leader.rebalance.enable 是否允許 leader 進行自動平衡,boolean 值,預設為 true background.threads 後台進程數目,int 值,預設為 10 個 compression.type 指定 topic 的壓縮方式,string 值,可選有 gzip, snappy, lz4 壓縮方法 uncompressed 不壓縮 producer 跟隨 producer 的壓縮方式 delete.topic.enable 是否允許刪除 topic,boolean 值,預設為 false(主要用於控制 admin 介面中的控制) leader.imbalance.check.interval.seconds 檢查是否平衡的時間間隔,long 值,預設為 300 leader.imbalance.per.broker.percentage 允許的不平衡的百分比,超出則會進行重平衡,int 值,預設為 10 log.flush.interval.messages 攢了多少條訊息之後會把資料刷入磁碟,long 值,預設是 9223372036854775807 log.flush.interval.ms 每條訊息在儲存到磁碟中前會在記憶體中待多久,單位毫秒,long 值,如果不設定,預設使用 log.flush.scheduler.interval.ms,也就是 9223372036854775807
更多的配置可以參考這裡,以上的配置均針對 broker,因為目前我只用 broker 的部分 基本操作
所有的工具都可以在 bin/ 檔案夾下查看,如果不帶任何參數,就會給出所有命令的列表說明,這裡只簡要說明一些常用的命令 建立和移除 topic
可以手動建立 topic,或在資料進來時自動建立不存在的 topic,如果是自動建立的話,可能需要根據這裡來進行對應調整。
建立 topic
bin/kafka-topics.sh --zookeeper zk_host:port/chroot --create --topic my_topic_name --partitions 20 --replication-factor 3 --config x=y
replication-factor 控制複製的份數,建議 2-3 份來兼顧容錯和效率。partitions 控制該 topic 將被分區的數目,partitions 的數目最好不要超過伺服器的個數(因為分區的意義是增加並行效率,而伺服器數量決定了並行的數量,假設只有 2 台伺服器,分 4 個區和 2 個區其實差別不大)。另外,topic 的名稱不能超過 249 個字元
修改 topic
bin/kafka-topics.sh --zookeeper zk_host:port/chroot --alter --topic my_topic_name --partitions 40
這裡需要注意,即使修改了分區的個數,已有的資料也不會進行變動,Kafka 不會做任何自動重分布
增加配置
bin/kafka-topics.sh --zookeeper zk_host:port/chroot --alter --topic my_topic_name --config x=y
移除配置
bin/kafka-topics.sh --zookeeper zk_host:port/chroot --alter --topic my_topic_name --delete-config x
刪除 topic
bin/kafka-topics.sh --zookeeper zk_host:port/chroot --delete --topic my_topic_name
這個需要 delete.topic.enable=true,目前 Kafka 不支援減少 topic 的分區數目 優雅關閉
Kafka 會自動檢測 broker 的狀態並根據機器狀態選舉出新的 leader。但是如果需要進行配置更改停機的時候,我們就需要使用優雅關閉了,好處在於: 會把所有的日誌同步到磁碟上,避免重啟之後的日誌恢複,減少重啟時間 會在關閉前把以這台機為 leader 的分區資料移轉到其他節點,會減少停用時間
但是這個需要開啟 controlled.shutdown.enable=true。
剛重啟之後的節點不是任何分區的 leader,所以這時候需要進行重新分配:
bin/kafka-preferred-replica-election.sh --zookeeper zk_host:port/chroot
這裡需要開啟 auto.leader.rebalance.enable=true
然後可以使用指令碼 bin/kafka-server-stop.sh
注意,如果設定檔中沒有 auto.leader.rebalance.enable=true,就還需要重新平衡 深入理解
這裡只是一部分摘錄,更多內容可查閱參考連結(尤其是美團技術部落格的那篇) 檔案系統
Kafka 大量依賴檔案系統去儲存和緩衝訊息。而檔案系統最終會放在硬碟上,不過不用擔心,很多時候硬碟的快慢完全取決於使用它的方式。設計良好的硬碟架構可以和記憶體一樣快。
所以與傳統的將資料緩衝在記憶體中然後刷到硬碟的設計不同,Kafka直接將資料寫到了檔案系統的日誌中,因此也避開了 JVM 的劣勢——Java 對象佔用空間巨大,資料量增大後記憶體回收有困難。使用檔案系統,即使系統重啟了,也不需要重新整理資料,也簡化了維護資料一致性的邏輯。
對於主要用於Tlog的訊息系統,資料的持久化可以簡單的通過將資料追加到檔案中實現,讀的時候從檔案中讀就好了。這樣做的好處是讀和寫都是 O(1) 的,並且讀操作不會阻塞寫操作和其他動作。這樣帶來的效能優勢是很明顯的,因為效能和資料的大小沒有關係了。
既然可以使用幾乎沒有容量限制(相對於記憶體來說)的硬碟空間建立訊息系統,就可以在沒有效能損失的情況下提供一些一般訊息系統不具備的特性。比如,一般的訊息系統都是在訊息被消費後立即刪除,Kafka卻可以將訊息儲存一段時間(比如一星期),這給consumer提供了很好的機動性和靈活性。 事務定義
資料轉送的事務定義通常有以下三種層級: 最多一次: 訊息不會被重複發送,最多被傳輸一次,但也有可能一次不傳輸。 最少一次: 訊息不會被漏發送,最少被傳輸一次,但也有可能被重複傳輸. 精確的一次(Exactly once): 不會漏傳輸也不會重複傳輸,每個訊息都傳輸被一次而且僅僅被傳輸一次,這是大家所期望的。
Kafka 的機制和 git 有點類似,有一個 commit 的概念,一旦提交且 broker 在工作,那麼資料就不會丟失。如果 producer 發布訊息時發生了網路錯誤,但又不確定實在提交之前發生的還是提交之後發生的,這種情況雖然不常見,但是必須考慮進去,現在Kafka版本還沒有解決這個問題,將來的版本正在努力嘗試解決。
並不是所有的情況都需要“精確的一次”這樣高的層級,Kafka 允許 producer 靈活的指定層級。比如 producer 可以指定必須等待訊息被提交的通知,或者完全的非同步發送訊息而不等待任何通知,或者僅僅等待 leader 聲明它拿到了訊息(followers沒有必要)。
現在從 consumer 的方面考慮這個問題,所有的副本都有相同的記錄檔和相同的offset,consumer 維護自己消費的訊息的 offset。如果 consumer 崩潰了,會有另外一個 consumer 接著消費訊息,它需要從一個合適的 offset 繼續處理。這種情況下可以有以下選擇: consumer 可以先讀取訊息,然後將 offset 寫入記錄檔中,然後再處理訊息。這存在一種可能就是在儲存 offset 後還沒處理訊息就 crash 了,新的 consumer 繼續從這個 offset 處理,那麼就會有些訊息永遠不會被處理,這就是上面說的『最多一次』 consumer 可以先讀取訊息,處理訊息,最後記錄o ffset,當然如果在記錄 offset 之前就 crash 了,新的 consumer 會重複的消費一些訊息,這就是上面說的『最少一次』 『精確一次』可以通過將提交分為兩個階段來解決:儲存了 offset 後提交一次,訊息處理成功之後再提交一次。但是還有個更簡單的做法:將訊息的 offset 和訊息被處理後的結果儲存在一起。比如用 Hadoop ETL 處理訊息時,將處理後的結果和 offset 同時儲存在 HDFS 中,這樣就能保證訊息和 offser 同時被處理了 效能最佳化
Kafka 在提高效率方面做了很大努力。Kafka 的一個主要使用情境是處理網站活動紀錄,輸送量是非常大的,每個頁面都會產生好多次寫操作。讀方面,假設每個訊息只被消費一次,讀的量的也是很大的,Kafka 也盡量使讀的操作更輕量化。
線性讀寫的情況下影響磁碟效能問題大約有兩個方面:太多的瑣碎的 I/O 操作和太多的位元組拷貝。I/O 問題發生在用戶端和服務端之間,也發生在服務端內部的持久化的操作中。
訊息集(message set)
為了避免這些問題,Kafka 建立了訊息集(message set)的概念,將訊息組織到一起,作為處理的單位。以訊息集為單位處理訊息,比以單個的訊息為單位處理,會提升不少效能。Producer 把訊息集一塊發送給服務端,而不是一條條的發送;服務端把訊息集一次性的追加到記錄檔中,這樣減少了瑣碎的 I/O 操作。consumer 也可以一次性的請求一個訊息集。
另外一個效能最佳化是在位元組拷貝方面。在低負載的情況下這不是問題,但是在高負載的情況下它的影響還是很大的。為了避免這個問題,Kafka 使用了標準的二進位訊息格式,這個格式可以在 producer, broker 和 producer 之間共用而無需做任何改動。
zero copy
Broker 維護的訊息日誌僅僅是一些目錄檔案,訊息集以固定隊的格式寫入到記錄檔中,這個格式 producer 和 consumer 是共用的,這使得 Kafka 可以一個很重要的點進行最佳化:訊息在網路上的傳遞。現代的 unix 作業系統提供了高效能的將資料從頁面緩衝發送到 socket 的系統函數,在 linux 中,這個函數是 sendfile
為了更好的理解 sendfile 的好處,我們先來看下一般將資料從檔案發送到 socket 的資料流向: 作業系統把資料從檔案拷貝核心中的頁緩衝中 應用程式從頁緩衝從把資料拷貝自己的記憶體緩衝中 應用程式將資料寫入到核心中 socket 緩衝中 作業系統把資料從 socket 緩衝中拷貝到網卡介面緩衝,從這裡發送到網路上。
這顯然是低效率的,有 4 次拷貝和 2 次系統調用。sendfile 通過直接將資料從頁面緩衝發送網卡介面緩衝,避免了重複拷貝,大大的最佳化了效能。
在一個多consumers的情境裡,資料僅僅被拷貝到頁面緩衝一次而不是每次消費訊息的時候都重複的進行拷貝。這使得訊息以近乎網路頻寬的速率發送出去。這樣在磁碟層面你幾乎看不到任何的讀操作,因為資料都是從頁面緩衝中直接發送到網路上去了。
資料壓縮
很多時候,效能的瓶頸並非CPU或者硬碟而是網路頻寬,對於需要在資料中心之間傳送大量資料的應用更是如此。當然使用者可以在沒有 Kafka 支援的情況下各自壓縮自己的訊息,但是這將導致較低的壓縮率,因為相比於將訊息單獨壓縮,將大量檔案壓縮在一起才能起到最好的壓縮效果。
Kafka 採用了端到端的壓縮:因為有『訊息集』的概念,用戶端的訊息可以一起被壓縮後送到服務端,並以壓縮後的格式寫入記錄檔,以壓縮的格式發送到 consumer,訊息從 producer 發出到 consumer 拿到都被是壓縮的,只有在 consumer 使用的時候才被解壓縮,所以叫做『端到端的壓縮』。Kafka支援GZIP和Snappy壓縮協議。 參考連結 Kafka學習整理六(server.properties配置實踐) Apache Kafka Quick Start Kafka入門經典教程 Apache kafka 工作原理介紹 事無巨細 Apache Kafka 0.9.0.1 叢集環境搭建 kafka叢集搭建 Kafka檔案儲存體機制那些事 kafka原理以及設計實現思想 kafka設計原理介紹 Kafka叢集操作指南 What is the actual role of ZooKeeper in Kafka? from: http://wdxtub.com/2016/08/15/kafka-guide/