Kafka 是一個java開發的mq中間件,依賴于zookeper,有高可用,高吞吐量等特點。
優勢
- 可靠性:partition機制和replication機制,使消息的傳遞有著很高的可靠性
- 穩定性,支持集群
- 高性能,高吞吐量,即使在TB的數據存儲情況下,仍然表現出很好的穩定性
- 支持消息廣播和單播,可以根據重設offset實現消息的重復消費
角色
Broker:kafka集群由一個或多個kafka server組成,每個server即Broker。
Topic:邏輯概念。kafka對消息保存時根據Topic進行歸類,一個Topic可以認為是一類消息。
Partition:物理概念。每個topic將被分成一到多個partition(分區),每個partition在存儲層面就是一個append log文件。一個非常大的topic可以分成多個partition,分布到多個broker上。kafka只保證按一個partition中的順序將消息發給consumer,不保證一個topic的整體(多個partition間)的順序。
offset:任何發布到Partition的消息都會被直接追加到log文件的尾部,每條消息在文件中的位置稱為offset(偏移量),offset為一個long型數字,它是唯一標記一條消息。kafka并沒有提供其他額外的索引機制來存儲offset,因此在kafka中幾乎不允許對消息進行“隨機讀寫”。
Producer:生成者。Producer將消息發布到指定的Topic,也可以指定Partition。
Consumer:消費者。Consumer采用pull的形式從Producer拉取消息
Consumer Group:每個 consumer 屬于一個特定的 consumer group(若不指定 group name 則屬于默認的 group)。一個 topic可以有多個CG,topic的消息會分發到所有的CG,但每個CG只會把消息發給該CG中的一個 consumer。如果所有的consumer都具有相同的group, 即單播,消息將會在consumers之間負載均衡;如果所有的consumer都具有不同的group,那這就是"發布-訂閱",每條消息將會廣播給所有的consumer。
分區機制和文件存儲機制
如圖,kafka中的消息是以topic進行分類的,生產者通過topic向kafka broker發送消息,消費者通過topic讀取消息。然而topic在物理層面上又能夠以partition進行分組,同一個topic下有多個不同的partition,每個partiton在物理上對應一個目錄(文件夾),以topic名稱+有序序號的形式命名(序號從0開始計,最大為partition數-1)。partition是實際物理上的概念,而topic是邏輯上的概念。Patition 的設計使得Kafka的吞吐率可以水平擴展。
每個分區文件夾下存儲這個分區的所有消息(.log)和索引文件(.index)。“.index”索引文件存儲大量的元數據,“.log”數據文件存儲大量的消息,索引文件中的元數據指向對應數據文件中message的物理偏移地址。其中以“.index”索引文件中的元數據[3, 348]為例,在“.log”數據文件表示第3個消息,即在全局partition中表示170410+3=170413個消息,該消息的物理偏移地址為348。
那么如何從partition中通過offset查找message呢?以上圖為例,讀取offset=170418的消息,首先查找segment文件,其中 00000000000000000000.index為最開始的文件,第二個文件為00000000000000170410.index(起始偏移為170410+1=170411),而第 三個文件為00000000000000239430.index(起始偏移為239430+1=239431),所以這個offset=170418就落到了第二個文件之中。其他 后續文件可以依次類推,以其實偏移量命名并排列這些文件,然后根據二分查找法就可以快速定位到具體文件位置。其次根據 00000000000000170410.index文件中的[8,1325]定位到00000000000000170410.log文件中的1325的位置進行讀取。
Kafka中topic的每個partition有一個預寫式的日志文件,雖然partition可以繼續細分為若干個segment文件,但是對于上層應用來說可以將 partition看成最小的存儲單元(一個有多個segment文件拼接的“巨型”文件),每個partition都由一些列有序的、不可變的消息組成,這些消息被連續的追加到partition中。
那如何保證消息均勻的分布到不同的partition中?
生產者在生產數據的時候,可以為每條消息指定Key,這樣消息被發送到broker時,會根據分區規則選擇被存儲到哪一個分區中,如果分區規則設置的合理,那么所有的消息將會被均勻的分布到不同的分區中,這樣就實現了負載均衡和水平擴展。分區規則可以自定義,比如將消息的key做了hashcode,然后和分區數(numPartitions)做模運算,使得每一個key都可以分布到一個分區中。
高可用(High availability)
kafka的高可用就是依賴于上面的文件存儲結構的,kafka能保證HA的策略有 data replication和leader election。
leader 機制
為了提高消息的可靠性,Kafka每個topic的partition有N個副本(replicas),其中N(大于等于1)是topic的復制因子(replica fator)的個數。這個時候每個 partition下面就有可能有多個 replica(replication機制,相當于是partition的副本但是有可能存儲在其他的broker上),但是這多個replica并不一定分布在一個broker上,而這時候為了更好的在replica之間復制數據,此時會選出一個leader,這個時候 producer會push消息到這個leader(leader機制),consumer也會從這個leader pull 消息,其他的 replica只是作為follower從leader復制數據,leader負責所有的讀寫;如果沒有一個leader的話,所有的follower都去進行讀寫 那么NxN(N+1個replica之間復制消息)的互相同步數據就變得很復雜而且數據的一致性和有序性不能夠保證。
如何將所有Replica均勻分布到整個集群
為了實現更高的可用性,推薦在部署kafka的時候,能夠保證一個topic的partition數量大于broker的數量,而且還需要把follower均勻的分布在所有的broker上,而不是只分布在一個 broker上。zookeeper 會對partition的leader follower等進行管理。
Kafka分配Replica的算法如下:
將所有Broker(假設共n個Broker)和待分配的Partition排序
將第i個Partition分配到第(i mod n)個Broker上
將第i個Partition的第j個Replica分配到第((i + j) mod n)個Broke
leader election
當Leader宕機了,怎樣在Follower中選舉出新的Leader?
一種非常常用的Leader Election的方式是“Majority Vote”(“少數服從多數”),但Kafka并未采用這種方式。
Kafka在Zookeeper中動態維護了一個ISR(in-sync replicas),這個ISR里的所有Replica都跟上了leader,只有ISR里的成員才有被選為Leader的可能。
那么如何選取出leader:
最簡單最直觀的方案是(誰寫進去誰就是leader),所有Follower都在Zookeeper上設置一個Watch,一旦Leader宕機,其對應的ephemeral znode會自動刪除,此時所有Follower都嘗試創建該節點,而創建成功者(Zookeeper保證只有一個能創建成功)即是新的Leader,其它Replica即為Follower。
Data Replication
消息commit
kafka在處理傳播消息的時候,Producer會發布消息到某個partition上,先通知找到這個partition的leader replica,無論這個partition的 Replica factor是多少,Producer 先把消息發送給replica的leader,然后Leader在接受到消息后會寫入到Log,這時候這個leader的其余follower都會去leader pull數據,這樣可保證follower的replica的數據順序和leader是一致的,follower在接受到消息之后寫入到Log里面(同步),然后向leader發送ack確認,一旦Leader接收到了所有的ISR(與leader保持同步的Replica列表)中的follower的ack消息,這個消息就被認為是 commit了,然后leader增加HW并且向producer發送ack消息,表示消息已經發送完成。但是為了提高性能,每個follower在接受到消息之后就會直接返回給leader ack消息,而并非等數據寫入到log里(異步),所以,可以認為對于已經commit的數據,只可以保證消息已經存在與所有的replica的內存中,但是不保證已經被持久化到磁盤中,所以進而也就不能保證完全發生異常的時候,該消息能夠被consumer消費掉,如果異常發生,leader 宕機,而且內存數據消失,此時重新選舉leader就會出現這樣的情況,但是由于考慮大這樣的情況實屬少見,所以這種方式在性能和數據持久化上做了一個相對的平衡,consumer讀取消息也是從 leader,并且只有已經commit之后的消息(offset小于HW)才會暴露給consumer。
消息確認
kafka的存活條件包括兩個條件:
- kafka必須維持著與zookeeper的session(這個通過zookeeper的heartbeat機制來實現)
- follower必須能夠及時的將數據從leader復制過去 ,不能夠“落后太多”。leader會跟蹤與其保持著同步的replica列表簡稱ISR,(in-sync replica),如果一個follower宕機或是落后太多,leader就會把它從ISR中移除掉。這里指的落后太多是說 follower復制的消息落后的超過了預設值,(該值可在
KAFKA_HOME/config/server.properties中通過replica.lag.time.max.ms來配置,其默認值是10000)沒有向leader發起fetch請求。
一條消息只有被ISR里的所有Follower都從Leader復制過去才會被認為已提交。這樣就避免了部分數據被寫進了Leader,還沒來得及被任何Follower復制就宕機了,而造成數據丟失(Consumer無法消費這些數據)。而對于Producer而言,它可以選擇是否等待消息commit,這可以通過request.required.acks來設置。
0---表示不進行消息接收是否成功的確認;
1---表示當Leader接收成功時確認;
-1---表示Leader和Follower都接收成功時確認;
持久性
kafka使用文件存儲消息,這就直接決定kafka在性能上嚴重依賴文件系統的本身特性。且無論任何 OS 下,對文件系統本身的優化幾乎沒有可能。文件緩存/直接內存映射等是常用的手段。 因為 kafka 是對日志文件進行 append 操作,因此磁盤檢索的開支是較小的;同時為了減少磁盤寫入的次數,broker會將消息暫時buffer起來,當消息的個數(或尺寸)達到一定閥值時,再flush到磁盤,這樣減少了磁盤IO調用的次數。
producer
指定partition
producer將會和Topic下所有partition leader保持socket連接;消息由producer直接通過socket發送到broker,中間不會經過任何"路由層".事實上,消息被路由到哪個partition上,有producer決定.比如可以采用"random""key-hash""輪詢"等,如果一個topic中有多個partitions,那么在producer端實現"消息均衡分發"是必要的.
異步發送
producer.type的默認值是sync,即同步的方式。這個參數指定了在后臺線程中消息的發送方式是同步的還是異步的。如果設置成異步的模式,可以運行生產者以batch的形式push數據,這樣會極大的提高broker的性能,但是這樣會增加丟失數據的風險。
對于異步模式,還有4個配套的參數,如下:
- queue.buffering.max.ms 5000 啟用異步模式時,producer緩存消息的時間。比如我們設置成1000時,它會緩存1s的數據再一次發送出去,這樣可以極大的增加broker吞吐量,但也會造成時效性的降低。
- queue.buffering.max.messages 10000 啟用異步模式時,producer緩存隊列里最大緩存的消息數量,如果超過這個值,producer就會阻塞或者丟掉消息。
- queue.enqueue.timeout.ms -1 當達到上面參數時producer會阻塞等待的時間。如果設置為0,buffer隊列滿時producer不會阻塞,消息直接被丟掉;若設置為-1,producer會被阻塞,不會丟消息。
- batch.num.messages 200 啟用異步模式時,一個batch緩存的消息數量。達到這個數值時,producer才會發送消息。(每次批量發送的數量)
以batch的方式推送數據可以極大的提高處理效率,kafka producer可以將消息在內存中累計到一定數量后作為一個batch發送請求。batch的數量大小可以通過producer的參數(batch.num.messages)控制。通過增加batch的大小,可以減少網絡請求和磁盤IO的次數,當然具體參數設置需要在效率和時效性方面做一個權衡。在比較新的版本中還有batch.size這個參數。
consumer
consumer 采用pull的方式 從broker拉取數據。采用pull方式的優點有consumer端可以根據自己的消費能力適時的去fetch消息并處理,且可以控制消息消費的進度(offset);此外,消費者可以良好的控制消息消費的數量,batch fetch.
consumer端向broker發送fetch請求,并告知其獲取消息的offset;此后consumer將會獲得一定條數的消息;consumer端也可以重置offset來重新消費消息.
kafka和JMS(Java Message Service)實現(activeMQ)不同的是:即使消息被消費,消息仍然不會被立即刪除.日志文件將會根據broker中的配置要求,保留一定的時間之后刪除;比如log文件保留2天,那么兩天后,文件會被清除,無論其中的消息是否被消費.kafka通過這種簡單的手段,來釋放磁盤空間,以及減少消息消費之后對文件內容改動的磁盤IO開支。
對于consumer而言,它需要保存消費消息的offset,對于offset的保存和使用,有consumer來控制;當consumer正常消費消息時,offset將會"線性"的向前驅動,即消息將依次順序被消費.事實上consumer可以使用任意順序消費消息,它只需要將offset重置為任意值,offset將會保存在zookeeper中。
kafka集群幾乎不需要維護任何consumer和producer狀態信息,這些信息有zookeeper保存;因此producer和consumer的客戶端實現非常輕量級,它們可以隨意離開,而不會對集群造成額外的影響。
at most once: 消費者fetch消息,然后保存offset,然后處理消息;當client保存offset之后,但是在消息處理過程中出現了異常,導致部分消息未能繼續處理.那么此后"未處理"的消息將不能被fetch到,這就是"at most once".
at least once: 消費者fetch消息,然后處理消息,然后保存offset.如果消息處理成功之后,但是在保存offset階段zookeeper異常導致保存操作未能執行成功,這就導致接下來再次fetch時可能獲得上次已經處理過的消息,這就是"at least once",原因offset沒有及時的提交給zookeeper,zookeeper恢復正常還是之前offset狀態.
消息的順序性
Kafka分布式的單位是partition,同一個partition用一個log文件(追加寫、offset讀),所以可以保證FIFO的順序。但是在多個Partition時,不能保證Topic級別的數據有序性,除非創建Topic只指定1個partition,但這樣做就磨滅kafka高吞吐量的優秀特性。
kafka為了提高Topic的并發吞吐能力,可以提高Topic的partition數,并通過設置partition的replica來保證數據高可靠。
Kafka 中發送1條消息的時候,可以指定(topic, partition, key) 3個參數,業務放使用producer插入數據時,可以控制同一Key發到同一Partition,從而保證消息有序性。一個partition的消息只能被一個consumer消費。
安裝
詳情參見官網http://kafka.apache.org/
安裝會依賴java、zookeeper。
brew install kafka
//安裝的配置文件位置
/usr/local/etc/kafka/server.properties
/usr/local/etc/kafka/zookeeper.properties
//啟動zookeeper -daemon 守護模式
zookeeper-server-start /usr/local/etc/kafka/zookeeper.properties &
//啟動kafka
kafka-server-start /usr/local/etc/kafka/server.properties &
//創建topic 創建單分區單副本的 topic test:
kafka-topics --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic test
//查看創建的topic
kafka-topics --list --zookeeper localhost:2181
//發送消息客戶端
bin/kafka-console-producer.sh --broker-list localhost:9092 --topic test
//消費消息
kafka-console-consumer --bootstrap-server localhost:9092 --topic test --from-beginning
參考文章:https://blog.csdn.net/gongzhiyao3739124/article/details/79688813