Steam測試版遊戲安裝頁面可顯示所需儲存空間
一個典型的Kafka叢集中包含若干Producer(可以是web前端產生的Page View,或者是伺服器日誌,系統CPU、Memory等),若干broker(Kafka支援水平擴充套件,一般broker數量越多,叢集吞吐率越高),若干Consumer Group,以及一個Zookeeper叢集。Kafka通過Zookeeper管理叢集配置,選舉leader,以及在Consumer Group發生變化時進行rebalance。Producer使用push模式將訊息釋出到broker,Consumer使用pull模式從broker訂閱並消費訊息。
Topics和Partition
Topic在邏輯上可以被認為是一個queue
對於傳統的message queue而言,一般會刪除已經被消費的訊息,而Kafka叢集會保留所有的訊息,無論其被消費與否。當然,因為磁碟限制,不可能永久保留所有資料(實際上也沒必要),因此Kafka提供兩種策略刪除舊資料。一是基於時間,二是基於Partition檔案大小。例如可以通過配置$KAFKA_HOME/config/server.properties,讓Kafka刪除一週前的資料,也可在Partition檔案超過1GB時刪除舊資料。
因為Kafka讀取特定訊息的時間複雜度為O(1),即與檔案大小無關,所以這裡刪除過期檔案與提高Kafka效能無關。選擇怎樣的刪除策略只與磁碟以及具體的需求有關。另外,Kafka會為每一個Consumer Group保留一些metadata資訊——當前消費的訊息的position,也即offset。這個offset由Consumer控制。正常情況下Consumer會在消費完一條訊息後遞增該offset。當然,Consumer也可將offset設成一個較小的值,重新消費一些訊息。因為offset由Consumer控制,所以Kafka broker是無狀態的
Kafka 中的訊息以主題為單位進行歸類,生產者負責將訊息傳送到特定的主題(傳送到 Kafka 叢集中的每一條訊息都要指定一個主題),而消費者負責訂閱主題並進行消費。
Producer訊息路由
Producer傳送訊息到broker時,會根據Paritition機制選擇將其儲存到哪一個Partition。如果Partition機制設定合理,所有訊息可以均勻分佈到不同的Partition裡,這樣就實現了負載均衡。如果一個Topic對應一個檔案,那這個檔案所在的機器I/O將會成為這個Topic的效能瓶頸,而有了Partition後,不同的訊息可以並行寫入不同broker的不同Partition裡,極大的提高了吞吐率。可以在$KAFKA_HOME/config/server.properties中通過配置項num.partitions來指定新建Topic的預設Partition數量,也可在建立Topic時通過引數指定,同時也可以在Topic建立之後通過Kafka提供的工具修改。
在傳送一條訊息時,可以指定這條訊息的key,Producer根據這個key和Partition機制來判斷應該將這條訊息傳送到哪個Parition。Paritition機制可以通過指定Producer的paritition. class這一引數來指定,該class必須實現kafka.producer.Partitioner介面。
Consumer Group
使用Consumer high level API時,同一Topic的一條訊息只能被同一個Consumer Group內的一個Consumer消費,但多個Consumer Group可同時消費這一訊息。
這是Kafka用來實現一個Topic訊息的廣播(發給所有的Consumer)和單播(發給某一個Consumer)的手段。一個Topic可以對應多個Consumer Group。如果需要實現廣播,只要每個Consumer有一個獨立的Group就可以了。要實現單播只要所有的Consumer在同一個Group裡。
Kafka的設計理念之一就是同時提供離線處理和實時處理。根據這一特性,可以使用Storm這種實時流處理系統對訊息進行實時線上處理,同時使用Hadoop這種批處理系統進行離線處理,還可以同時將資料實時備份到另一個數據中心,只需要保證這三個操作所使用的Consumer屬於不同的Consumer Group即可。
Push vs. Pull
作為一個訊息系統,Kafka遵循了傳統的方式,選擇由Producer向broker push訊息並由Consumer從broker pull訊息。
push模式很難適應消費速率不同的消費者,因為訊息傳送速率是由broker決定的。push模式的目標是儘可能以最快速度傳遞訊息,但是這樣很容易造成Consumer來不及處理訊息,典型的表現就是拒絕服務以及網路擁塞。而pull模式則可以根據Consumer的消費能力以適當的速率消費訊息。
對於Kafka而言,pull模式更合適。pull模式可簡化broker的設計,Consumer可自主控制消費訊息的速率,同時Consumer可以自己控制消費方式——即可批量消費也可逐條消費,同時還能選擇不同的提交方式從而實現不同的傳輸語義
Kafka delivery guarantee
有這麼幾種可能的delivery guarantee:
At most once 訊息可能會丟,但絕不會重複傳輸
At least one 訊息絕不會丟,但可能會重複傳輸
Exactly once 每條訊息肯定會被傳輸一次且僅傳輸一次,很多時候這是使用者所想要的。
當Producer向broker傳送訊息時,一旦這條訊息被commit,因數replication的存在,它就不會丟。但是如果Producer傳送資料給broker後,遇到網路問題而造成通訊中斷,那Producer就無法判斷該條訊息是否已經commit。雖然Kafka無法確定網路故障期間發生了什麼,但是Producer可以生成一種類似於主鍵的東西,發生故障時冪等性的重試多次,這樣就做到了Exactly once。
接下來討論的是訊息從broker到Consumer的delivery guarantee語義。(僅針對Kafka consumer high level API)。Consumer在從broker讀取訊息後,可以選擇commit,該操作會在Zookeeper中儲存該Consumer在該Partition中讀取的訊息的offset。該Consumer下一次再讀該Partition時會從下一條開始讀取。如未commit,下一次讀取的開始位置會跟上一次commit之後的開始位置相同。當然可以將Consumer設定為autocommit,即Consumer一旦讀到資料立即自動commit。如果只討論這一讀取訊息的過程,那Kafka是確保了Exactly once。但實際使用中應用程式並非在Consumer讀取完資料就結束了,而是要進行進一步處理,而資料處理與commit的順序在很大程度上決定了訊息從broker和consumer的delivery guarantee semantic。
Kafka預設保證At least once,並且允許通過設定Producer非同步提交來實現At most once。而Exactly once要求與外部儲存系統協作,幸運的是Kafka提供的offset可以非常直接非常容易得使用這種方式。
日誌
不考慮多副本的情況,一個分割槽對應一個日誌(Log)。為了防止 Log 過大,Kafka 又引入了日誌分段(LogSegment)的概念,將 Log 切分為多個 LogSegment,相當於一個巨型檔案被平均分配為多個相對較小的檔案,這樣也便於訊息的維護和清理。
Log 在物理上只以資料夾的形式儲存,而每個 LogSegment 對應於磁碟上的一個日誌檔案和兩個索引檔案,以及可能的其他檔案(比如以“.txnindex”為字尾的事務索引檔案)。下圖描繪了主題、分割槽、副本、Log 以及 LogSegment 之間的關係。