Kafka是一款分布式消息發(fā)布和訂閱系統(tǒng),它的特點是高性能、高吞吐量。 最早設計的目的是作為LinkedIn的活動流和運營數(shù)據(jù)的處理管道。這些數(shù)據(jù)主要是用來對用戶做用戶畫像分析以及服務器性能數(shù)據(jù)的一些監(jiān)控。 所以kafka一開始設計的目標就是作為一個分布式、高吞吐量的消息系統(tǒng),所以適合運用在大數(shù)據(jù)傳輸場景。 Kafka的應用場景由于kafka具有更好的吞吐量、內(nèi)置分區(qū)、冗余及容錯性的優(yōu)點(kafka每秒可以處理幾十萬消息),讓kafka成為了一個很好的大規(guī)模消息處理應用的解決方案。所以在企業(yè)級應用長,主要會應用于如下幾個方面
Kafka的架構一個典型的kafka集群包含若干Producer(可以是應用節(jié)點產(chǎn)生的消息,也可以是通過Flume收集日志產(chǎn)生的事件),若干個Broker(kafka支持水平擴展)、若干個Consumer Group,以及一個zookeeper集群。kafka通過zookeeper管理集群配置及服務協(xié)同。Producer使用push模式將消息發(fā)布到broker,consumer通過監(jiān)聽使用pull模式從broker訂閱并消費消息。 多個broker協(xié)同工作,producer和consumer部署在各個業(yè)務邏輯中。三者通過zookeeper管理協(xié)調(diào)請求和轉(zhuǎn)發(fā)。這樣就組成了一個高性能的分布式消息發(fā)布和訂閱系統(tǒng)。 圖上有一個細節(jié)是和其他mq中間件不同的點,producer 發(fā)送消息到broker的過程是push,而consumer從broker消費消息的過程是pull,主動去拉數(shù)據(jù)。而不是broker把數(shù)據(jù)主動發(fā)送給consumer。 名詞解釋1)Broker Java中使用kafka進行通信依賴 發(fā)送端代碼 消費端代碼 異步發(fā)送 batch.size生產(chǎn)者發(fā)送多個消息到broker上的同一個分區(qū)時,為了減少網(wǎng)絡請求帶來的性能開銷,通過批量的方式來提交消息,可以通過這個參數(shù)來控制批量提交的字節(jié)數(shù)大小,默認大小是16384byte,也就是16kb,意味著當一批消息大小達到指定的batch.size的時候會統(tǒng)一發(fā)送 linger.msProducer默認會把兩次發(fā)送時間間隔內(nèi)收集到的所有Requests進行一次聚合然后再發(fā)送,以此提高吞吐量,而linger.ms就是為每次發(fā)送到broker的請求增加一些delay,以此來聚合更多的Message請求。這個有點想TCP里面的Nagle算法,在TCP協(xié)議的傳輸中,為了減少大量小數(shù)據(jù)包的發(fā)送,采用了Nagle算法,也就是基于小包的等-停協(xié)議。 一些基礎配置分析group.idconsumer group是kafka提供的可擴展且具有容錯性的消費者機制。既然是一個組,那么組內(nèi)必然可以有多個消費者或消費者實例(consumer instance),它們共享一個公共的ID,即group ID。組內(nèi)的所有消費者協(xié)調(diào)在一起來消費訂閱主題(subscribed topics)的所有分區(qū)(partition)。當然,每個分區(qū)只能由同一個消費組內(nèi)的一個consumer來消費.如下圖所示,分別有三個消費者,屬于兩個不同的group,那么對于firstTopic這個topic來說,這兩個組的消費者都能同時消費這個topic中的消息,對于此時的架構來說,這個firstTopic就類似于ActiveMQ中的topic概念。如右圖所示,如果3個消費者都屬于同一個group,那么此時firstTopic就是一個Queue的概念 enable.auto.commit消費者消費消息以后自動提交,只有當消息提交以后,該消息才不會被再次接收到,還可以配合auto.commit.interval.ms控制自動提交的頻率。 auto.offset.reset這個參數(shù)是針對新的groupid中的消費者而言的,當有新groupid的消費者來消費指定的topic時,對于該參數(shù)的配置,會有不同的語義。 max.poll.records此設置限制每次調(diào)用poll返回的消息數(shù),這樣可以更容易的預測每次poll間隔要處理的最大值。通過調(diào)整此值,可以減少poll間隔 原理分析從前面的整個演示過程來看,只要不是超大規(guī)模的使用kafka,那么基本上沒什么大問題,否則,對于kafka本身的運維的挑戰(zhàn)會很大,同時,針對每一個參數(shù)的調(diào)優(yōu)也顯得很重要。 關于Topic和PartitionTopic 在kafka中,topic是一個存儲消息的邏輯概念,可以認為是一個消息集合。每條消息發(fā)送到kafka集群的消息都有一個類別。物理上來說,不同的topic的消息是分開存儲的, image.png Partition 每個topic可以劃分多個分區(qū)(每個Topic至少有一個分區(qū)),同一topic下的不同分區(qū)包含的消息是不同的。每個消息在被添加到分區(qū)時,都會被分配一個offset(稱之為偏移量),它是消息在此分區(qū)中的唯一編號,kafka通過offset保證消息在分區(qū)內(nèi)的順序,offset的順序不跨分區(qū),即kafka只保證在同一個分區(qū)內(nèi)的消息是有序的。 下圖中,對于名字為test的topic,做了3個分區(qū),分別是p0、p1、p2. image.png Topic&Partition的存儲 Partition是以文件的形式存儲在文件系統(tǒng)中,比如創(chuàng)建一個名為firstTopic的topic,其中有3個partition,那么在kafka的數(shù)據(jù)目錄(/tmp/kafka-log)中就有3個目錄,firstTopic-0~3, 命名規(guī)則是 關于消息分發(fā)kafka消息分發(fā)策略 消息是kafka中最基本的數(shù)據(jù)單元,在kafka中,一條消息由key、value兩部分構成,在發(fā)送一條消息時,我們可以指定這個key,那么producer會根據(jù)key和partition機制來判斷當前這條消息應該發(fā)送并存儲到哪個partition中。我們可以根據(jù)需要進行擴展producer的partition機制。 自定義Partitioner 發(fā)送端代碼添加自定義分區(qū) 消息默認的分發(fā)機制 默認情況下,kafka采用的是hash取模的分區(qū)算法。如果Key為null,則會隨機分配一個分區(qū)。這個隨機是在這個參數(shù)”metadata.max.age.ms”的時間范圍內(nèi)隨機選擇一個。對于這個時間段內(nèi),如果key為null,則只會發(fā)送到唯一的分區(qū)。這個值值哦默認情況下是10分鐘更新一次。 關于Metadata,這個之前沒講過,簡單理解就是Topic/Partition和broker的映射關系,每一個topic的每一個partition,需要知道對應的broker列表是什么,leader是誰、follower是誰。這些信息都是存儲在Metadata這個類里面。 消費端如何消費指定的分區(qū) 通過下面的代碼,就可以消費指定該topic下的0號分區(qū)。其他分區(qū)的數(shù)據(jù)就無法接收 消息的消費原理在實際生產(chǎn)過程中,每個topic都會有多個partitions,多個partitions的好處在于,一方面能夠?qū)roker上的數(shù)據(jù)進行分片有效減少了消息的容量從而提升io性能。另外一方面,為了提高消費端的消費能力,一般會通過多個consumer去消費同一個topic ,也就是消費端的負載均衡機制,也就是我們接下來要了解的,在多個partition以及多個consumer的情況下,消費者是如何消費消息的。 image.png 對于上面這個圖來說,這3個消費者會分別消費test這個topic 的3個分區(qū),也就是每個consumer消費一個partition。
演示結果:consumer1會消費partition0分區(qū)、consumer2會消費partition1分區(qū)、consumer3會消費partition2分區(qū)
consumer和partition的數(shù)量建議
什么是分區(qū)分配策略通過前面的案例演示,我們應該能猜到,同一個group中的消費者對于一個topic中的多個partition,存在一定的分區(qū)分配策略。 RangeAssignor(范圍分區(qū)) Range策略是對每個主題而言的,首先對同一個主題里面的分區(qū)按照序號進行排序,并對消費者按照字母順序進行排序。 假設我們有10個分區(qū),3個消費者,排完序的分區(qū)將會是0, 1, 2, 3, 4, 5, 6, 7, 8, 9;消費者線程排完序?qū)荂1-0, C2-0, C3-0。然后將partitions的個數(shù)除于消費者線程的總數(shù)來決定每個消費者線程消費幾個分區(qū)。如果除不盡,那么前面幾個消費者線程將會多消費一個分區(qū)。在我們的例子里面,我們有10個分區(qū),3個消費者線程, 10 / 3 = 3,而且除不盡,那么消費者線程 C1-0 將會多消費一個分區(qū). 假如我們有11個分區(qū),那么最后分區(qū)分配的結果看起來是這樣的: 假如我們有2個主題(T1和T2),分別有10個分區(qū),那么最后分區(qū)分配的結果看起來是這樣的: 可以看出,C1-0 消費者線程比其他消費者線程多消費了2個分區(qū),這就是Range strategy的一個很明顯的弊端 RoundRobinAssignor(輪詢分區(qū)) 輪詢分區(qū)策略是把所有partition和所有consumer線程都列出來,然后按照hashcode進行排序。最后通過輪詢算法分配partition給消費線程。如果所有consumer實例的訂閱是相同的,那么partition會均勻分布。 在我們的例子里面,假如按照 hashCode 排序完的topic-partitions組依次為T1-5, T1-3, T1-0, T1-8, T1-2, T1-1, T1-4, T1-7, T1-6, T1-9,我們的消費者線程排序為C1-0, C1-1, C2-0, C2-1,最后分區(qū)分配的結果為: 使用輪詢分區(qū)策略必須滿足兩個條件:
StrickyAssignor 分配策略 kafka在0.11.x版本支持了StrickyAssignor, 翻譯過來叫粘滯策略,它主要有兩個目的:
當兩者發(fā)生沖突時, 第 一 個目標優(yōu)先于第二個目標。 鑒于這兩個目標, StickyAssignor分配策略的具體實現(xiàn)要比RangeAssignor和RoundRobinAssi gn or這兩種分配策略要復雜得多,假設我們有這樣一個場景: 假設消費組有3個消費者:C0,C1,C2,它們分別訂閱了4個Topic(t0,t1,t2,t3),并且每個主題有兩個分區(qū)(p0,p1),也就是說,整個消費組訂閱了8個分區(qū):tOpO 、 tOpl 、 tlpO 、 tlpl 、 t2p0 、t2pl 、t3p0 、 t3pl 誰來執(zhí)行Rebalance以及管理consumer的group呢?Kafka提供了一個角色:coordinator來執(zhí)行對于consumer group的管理,Kafka提供了一個角色:coordinator來執(zhí)行對于consumer group的管理,當consumer group的第一個consumer啟動的時候,它會去和kafka server確定誰是它們組的coordinator。之后該group內(nèi)的所有成員都會和該coordinator進行協(xié)調(diào)通信 如何確定coordinator consumer group如何確定自己的coordinator是誰呢, 消費者向kafka集群中的任意一個broker發(fā)送一個GroupCoordinatorRequest請求,服務端會返回一個負載最小的broker節(jié)點的id,并將該broker設置為coordinator JoinGroup的過程 在rebalance之前,需要保證coordinator是已經(jīng)確定好了的,整個rebalance的過程分為兩個步驟,Join和Sync join: 表示加入到consumer group中,在這一步中,所有的成員都會向coordinator發(fā)送joinGroup的請求。一旦所有成員都發(fā)送了joinGroup請求,那么coordinator會選擇一個consumer擔任leader角色,并把組成員信息和訂閱信息發(fā)送消費者 image.png protocol_metadata: 序列化后的消費者的訂閱信息 每個消費者都可以設置自己的分區(qū)分配策略,對于消費組而言,會從各個消費者上報過來的分區(qū)分配策略中選舉一個彼此都贊同的策略來實現(xiàn)整體的分區(qū)分配,這個'贊同'的規(guī)則是,消費組內(nèi)的各個消費者會通過投票來決定
Synchronizing Group State階段 完成分區(qū)分配之后,就進入了Synchronizing Group State階段,主要邏輯是向GroupCoordinator發(fā)送SyncGroupRequest請求,并且處理SyncGroupResponse響應,簡單來說,就是leader將消費者對應的partition分配方案同步給consumer group 中的所有consumer image.png 每個消費者都會向coordinator發(fā)送syncgroup請求,不過只有l(wèi)eader節(jié)點會發(fā)送分配方案,其他消費者只是打打醬油而已。當leader把方案發(fā)給coordinator以后,coordinator會把結果設置到SyncGroupResponse中。這樣所有成員都知道自己應該消費哪個分區(qū)。 consumer group的分區(qū)分配方案是在客戶端執(zhí)行的!Kafka將這個權利下放給客戶端主要是因為這樣做可以有更好的靈活性 總結 我們再來總結一下consumer group rebalance的過程 ? 發(fā)起join group請求,兩種情況
到這里為止,我們已經(jīng)知道了消息的發(fā)送分區(qū)策略,以及消費者的分區(qū)消費策略和rebalance。對于應用層面來說,還有一個最重要的東西沒有講解,就是offset,他類似一個游標,表示當前消費的消息的位置。 如何保存消費端的消費位置什么是offset 前面在講解partition的時候,提到過offset, 每個topic可以劃分多個分區(qū)(每個Topic至少有一個分區(qū)),同一topic下的不同分區(qū)包含的消息是不同的。每個消息在被添加到分區(qū)時,都會被分配一個offset(稱之為偏移量),它是消息在此分區(qū)中的唯一編號,kafka通過offset保證消息在分區(qū)內(nèi)的順序,offset的順序不跨分區(qū),即kafka只保證在同一個分區(qū)內(nèi)的消息是有序的; 對于應用層的消費來說,每次消費一個消息并且提交以后,會保存當前消費到的最近的一個offset。那么offset保存在哪里? image.png offset在哪里維護? 在kafka中,提供了一個consumer_offsets_* 的一個topic,把offset信息寫入到這個topic中。 從輸出結果中,我們就可以看到test這個topic的offset的位移日志 分區(qū)的副本機制我們已經(jīng)知道Kafka的每個topic都可以分為多個Partition,并且多個partition會均勻分布在集群的各個節(jié)點下。雖然這種方式能夠有效的對數(shù)據(jù)進行分片,但是對于每個partition來說,都是單點的,當其中一個partition不可用的時候,那么這部分消息就沒辦法消費。所以kafka為了提高partition的可靠性而提供了副本的概念(Replica),通過副本機制來實現(xiàn)冗余備份。 每個分區(qū)可以有多個副本,并且在副本集合中會存在一個leader的副本,所有的讀寫請求都是由leader副本來進行處理。剩余的其他副本都做為follower副本,follower副本會從leader副本同步消息日志。 一般情況下,同一個分區(qū)的多個副本會被均勻分配到集群中的不同broker上,當leader副本所在的broker出現(xiàn)故障后,可以重新選舉新的leader副本繼續(xù)對外提供服務。通過這樣的副本機制來提高kafka集群的可用性。 創(chuàng)建一個帶副本機制的topic 通過下面的命令去創(chuàng)建帶2個副本的topic 然后我們可以在/tmp/kafka-log路徑下看到對應topic的副本信息了。我們通過一個圖形的方式來表達。 image.png 如何知道那個各個分區(qū)中對應的leader是誰呢? 在zookeeper服務器上,通過如下命令去獲取對應分區(qū)的信息, 比如下面這個是獲取secondTopic第1個 {'controller_epoch':12,'leader':0,'version':1,'leader_epoch':0,'isr':[0,1]} leader表示當前分區(qū)的leader是那個broker-id。下圖中。綠色線條的表示該分區(qū)中的leader節(jié)點。其他節(jié)點就為follower image.png 需要注意的是,kafka集群中的一個broker中最多只能有一個副本,leader副本所在的broker節(jié)點的分區(qū)叫l(wèi)eader節(jié)點,follower副本所在的broker節(jié)點的分區(qū)叫follower節(jié)點 副本的leader選舉Kafka提供了數(shù)據(jù)復制算法保證,如果leader副本所在的broker節(jié)點宕機或者出現(xiàn)故障,或者分區(qū)的leader節(jié)點發(fā)生故障,這個時候怎么處理呢?
副本協(xié)同機制 剛剛提到了,消息的讀寫操作都只會由leader節(jié)點來接收和處理。follower副本只負責同步數(shù)據(jù)以及當leader副本所在的broker掛了以后,會從follower副本中選取新的leader。 寫請求首先由Leader副本處理,之后follower副本會從leader上拉取寫入的消息,這個過程會有一定的延遲,導致follower副本中保存的消息略少于leader副本,但是只要沒有超出閾值都可以容忍。但是如果一個follower副本出現(xiàn)異常,比如宕機、網(wǎng)絡斷開等原因長時間沒有同步到消息,那這個時候,leader就會把它踢出去。kafka通過ISR集合來維護一個分區(qū)副本信息 image.png 一個新leader被選舉并被接受客戶端的消息成功寫入。Kafka確保從同步副本列表中選舉一個副本為leader;leader負責維護和跟蹤ISR(in-Sync replicas , 副本同步隊列)中所有follower滯后的狀態(tài)。當producer發(fā)送一條消息到broker后,leader寫入消息并復制到所有follower。消息提交之后才被成功復制到所有的同步副本。 ISR ISR表示目前“可用且消息量與leader相差不多的副本集合,這是整個副本集合的一個子集”。怎么去理解可用和相差不多這兩個詞呢?具體來說,ISR集合中的副本必須滿足兩個條件:
follower副本把leader副本LEO之前的日志全部同步完成時,則認為follower副本已經(jīng)追趕上了leader副本,這個時候會更新這個副本的lastCaughtUpTimeMs標識,kafk副本管理器會啟動一個副本過期檢查的定時任務,這個任務會定期檢查當前時間與副本的lastCaughtUpTimeMs的差值是否大于參數(shù)replica.lag.time.max.ms 的值,如果大于,則會把這個副本踢出ISR集合 image.png 如何處理所有的Replica不工作的情況在ISR中至少有一個follower時,Kafka可以確保已經(jīng)commit的數(shù)據(jù)不丟失,但如果某個Partition的所有Replica都宕機了,就無法保證數(shù)據(jù)不丟失了
這就需要在可用性和一致性當中作出一個簡單的折衷。 副本數(shù)據(jù)同步原理了解了副本的協(xié)同過程以后,還有一個最重要的機制,就是數(shù)據(jù)的同步過程。它需要解決
下圖中,深紅色部分表示test_replica分區(qū)的leader副本,另外兩個節(jié)點上淺色部分表示follower副本 image.png Producer在發(fā)布消息到某個Partition時,
LEO:即日志末端位移(log end offset),記錄了該副本底層日志(log)中下一條消息的位移值。注意是下一條消息!也就是說,如果LEO=10,那么表示該副本保存了10條消息,位移值范圍是[0, 9]。另外,leader LEO和follower LEO的更新是有區(qū)別的。 HW:即上面提到的水位值(Hight Water)。對于同一個副本對象而言,其HW值不會大于LEO值。小于等于HW值的所有消息都被認為是“已備份”的(replicated)。同理,leader副本和follower副本的HW更新是有區(qū)別的 通過下面這幅圖來表達LEO、HW的含義,隨著follower副本不斷和leader副本進行數(shù)據(jù)同步,follower副本的LEO會主鍵后移并且追趕到leader副本,這個追趕上的判斷標準是當前副本的LEO是否大于或者等于leader副本的HW,這個追趕上也會使得被踢出的follower副本重新加入到ISR集合中。 image.png 數(shù)據(jù)丟失的問題 表達的含義是,至少需要多少個副本同步才能表示消息是提交的, 所以,當 min.insync.replicas=1的時候,一旦消息被寫入leader端log即被認為是“已提交”,而延遲一輪FETCH RPC更新HW值的設計使得follower HW值是異步延遲更新的,倘若在這個過程中l(wèi)eader發(fā)生變更,那么成為新leader的follower的HW值就有可能是過期的,使得clients端認為是成功提交的消息被刪除。 image.png acks配置表示producer發(fā)送消息到broker上以后的確認值。有三個可選項 數(shù)據(jù)丟失的解決方案 在kafka0.11.0.0版本之后,引入了一個leader epoch來解決這個問題,所謂的leader epoch實際上是一對值(epoch,offset),epoch代表leader的版本號,從0開始遞增,當leader發(fā)生過變更,epoch就+1,而offset則是對應這個epoch版本的leader寫入第一條消息的offset,比如 我們基于同樣的情況來分析,follower宕機并且恢復之后,有兩種情況,如果這個時候leader副本沒有掛,也就是意味著沒有發(fā)生leader選舉,那么follower恢復之后并不會去截斷自己的日志,而是先發(fā)送一個OffsetsForLeaderEpochRequest請求給到leader副本,leader副本收到請求之后返回當前的LEO。 Leader副本的選舉過程
消息的存儲消息發(fā)送端發(fā)送消息到broker上以后,消息是如何持久化的呢?那么接下來去分析下消息的存儲首先我們需要了解的是,kafka是使用日志文件的方式來保存生產(chǎn)者和發(fā)送者的消息,每條消息都有一個offset值來表示它在分區(qū)中的偏移量。Kafka中存儲的一般都是海量的消息數(shù)據(jù),為了避免日志文件過大,Log并不是直接對應在一個磁盤上的日志文件,而是對應磁盤上的一個目錄,這個目錄的命名規(guī)則是 消息的文件存儲機制 一個topic的多個partition在物理磁盤上的保存路徑,路徑保存在 /tmp/kafka-logs/topic_partition,包含日志文件、索引文件和時間索引文件 image.png kafka是通過分段的方式將Log分為多個LogSegment,LogSegment是一個邏輯上的概念,一個LogSegment對應磁盤上的一個日志文件和一個索引文件,其中日志文件是用來記錄消息的。索引文件是用來保存消息的索引。那么這個LogSegment是什么呢? LogSegment 假設kafka以partition為最小存儲單位,那么我們可以想象當kafka producer不斷發(fā)送消息,必然會引起partition文件的無線擴張,這樣對于消息文件的維護以及被消費的消息的清理帶來非常大的挑戰(zhàn),所以kafka 以segment為單位又把partition進行細分。每個partition相當于一個巨型文件被平均分配到多個大小相等的segment數(shù)據(jù)文件中(每個segment文件中的消息不一定相等),這種特性方便已經(jīng)被消費的消息的清理,提高磁盤的利用率。
查看segment文件命名規(guī)則 通過下面這條命令可以看到kafka消息日志的內(nèi)容 假如第一個log文件的最后一個offset為:5376,所以下一個segment的文件命名為: segment中index和log的對應關系 從所有分段中,找一個分段進行分析 image.png 如圖所示,index中存儲了索引以及物理偏移量。 log存儲了消息的內(nèi)容。索引文件的元數(shù)據(jù)執(zhí)行對應數(shù)據(jù)文件中message的物理偏移地址。舉個簡單的案例來說,以[4053,80899]為例,在log文件中,對應的是第4053條記錄,物理偏移量(position)為80899. position是ByteBuffer的指針位置 在partition中如何通過offset查找message 查找的算法是
比如說,我們要查找offset=2490這條消息,那么先找到00000000000000000000.index, 然后找到[2487,49111]這個索引,再到log文件中,根據(jù)49111這個position開始查找,比較每條消息的offset是否大于等于2490。最后查找到對應的消息以后返回 Log文件的消息內(nèi)容分析 前面我們通過kafka提供的命令,可以查看二進制的日志文件信息,一條消息,會包含很多的字段。 offset和position這兩個前面已經(jīng)講過了、 createTime表示創(chuàng)建時間、keysize和valuesize表示key和value的大小、 compresscodec表示壓縮編碼、payload:表示消息的具體內(nèi)容 日志的清除策略以及壓縮策略日志清除策略 前面提到過,日志的分段存儲,一方面能夠減少單個文件內(nèi)容的大小,另一方面,方便kafka進行日志清理。日志的清理策略有兩個:
通過log.retention.bytes和log.retention.hours這兩個參數(shù)來設置,當其中任意一個達到要求,都會執(zhí)行刪除。 日志壓縮策略 Kafka還提供了“日志壓縮(Log Compaction)”功能,通過這個功能可以有效的減少日志文件的大小,緩解磁盤緊張的情況,在很多實際場景中,消息的key和value的值之間的對應關系是不斷變化的,就像數(shù)據(jù)庫中的數(shù)據(jù)會不斷被修改一樣,消費者只關心key對應的最新的value。因此,我們可以開啟kafka的日志壓縮功能,服務端會在后臺啟動啟動Cleaner線程池,定期將相同的key進行合并,只保留最新的value值。日志的壓縮原理是 image.png 磁盤存儲的性能問題磁盤存儲的性能優(yōu)化 我們現(xiàn)在大部分企業(yè)仍然用的是機械結構的磁盤,如果把消息以隨機的方式寫入到磁盤,那么磁盤首先要做的就是尋址,也就是定位到數(shù)據(jù)所在的物理地址,在磁盤上就要找到對應的柱面、磁頭以及對應的扇區(qū);這個過程相對內(nèi)存來說會消耗大量時間,為了規(guī)避隨機讀寫帶來的時間消耗,kafka采用順序?qū)懙姆绞酱鎯?shù)據(jù)。即使是這樣,但是頻繁的I/O操作仍然會造成磁盤的性能瓶頸 零拷貝 消息從發(fā)送到落地保存,broker維護的消息日志本身就是文件目錄,每個文件都是二進制保存,生產(chǎn)者和消費者使用相同的格式來處理。在消費者獲取消息時,服務器先從硬盤讀取數(shù)據(jù)到內(nèi)存,然后把內(nèi)存中的數(shù)據(jù)原封不動的通過socket發(fā)送給消費者。雖然這個操作描述起來很簡單,但實際上經(jīng)歷了很多步驟。 操作系統(tǒng)將數(shù)據(jù)從磁盤讀入到內(nèi)核空間的頁緩存: image.png 通過“零拷貝”技術,可以去掉這些沒必要的數(shù)據(jù)復制操作,同時也會減少上下文切換次數(shù)?,F(xiàn)代的unix操作系統(tǒng)提供一個優(yōu)化的代碼路徑,用于將數(shù)據(jù)從頁緩存?zhèn)鬏數(shù)絪ocket;在Linux中,是通過sendfile系統(tǒng)調(diào)用來完成的。Java提供了訪問這個系統(tǒng)調(diào)用的方法:FileChannel.transferTo API image.png 頁緩存 頁緩存是操作系統(tǒng)實現(xiàn)的一種主要的磁盤緩存,但凡設計到緩存的,基本都是為了提升i/o性能,所以頁緩存是用來減少磁盤I/O操作的。 當 一 個進程準備讀取磁盤上的文件內(nèi)容時, 操作系統(tǒng)會先查看待讀取的數(shù)據(jù)所在的頁(page)是否在頁緩存(pagecache)中,如果存在(命中)則直接返回數(shù)據(jù), 從而避免了對物理磁盤的I/0操作;如果沒有命中, 則操作系統(tǒng)會向磁盤發(fā)起讀取請求并將讀取的數(shù)據(jù)頁存入頁緩存, 之后再將數(shù)據(jù)返回給進程。 Kafka消息的可靠性沒有一個中間件能夠做到百分之百的完全可靠,可靠性更多的還是基于幾個9的衡量指標,比如4個9、5個9. 軟件系統(tǒng)的可靠性只能夠無限去接近100%,但不可能達到100%。所以kafka如何是實現(xiàn)最大可能的可靠性呢?
|
|
來自: xxcc140 > 《大數(shù)據(jù)》