0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學(xué)習在線(xiàn)課程
  • 觀(guān)看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區
會(huì )員中心
創(chuàng )作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內不再提示

從Kafka中學(xué)習高性能系統如何設計

OSC開(kāi)源社區 ? 來(lái)源:OSCHINA 社區 ? 2023-07-17 11:25 ? 次閱讀

1 前言

相信各位小伙伴之前或多或少接觸過(guò)消息隊列,比較知名的包含 Rocket MQ 和 Kafka,在京東內部使用的是自研的消息中間件 JMQ,從 JMQ2 升級到 JMQ4 的也是帶來(lái)了性能上的明顯提升,并且 JMQ4 的底層也是參考 Kafka 去做的設計。在這里我會(huì )給大家展示 Kafka 它的高性能是如何設計的,大家也可以學(xué)習相關(guān)方法論將其利用在實(shí)際項目中,也許下一個(gè)頂級項目就在各位的代碼中產(chǎn)生了。

2 如何理解高性能設計

2.1 高性能設計的” 秘籍”

先拋開(kāi) kafka,咱們先來(lái)談?wù)撘幌赂咝阅茉O計的本質(zhì),在這里借用一下網(wǎng)上的一張總結高性能的思維導圖:

186b082e-2239-11ee-962d-dac502259ad0.png

從中可以看到,高性能設計的手段還是非常多,從” 微觀(guān)設計” 上的無(wú)鎖化、序列化,到” 宏觀(guān)設計” 上的緩存、存儲等,可以說(shuō)是五花八門(mén),令人眼花繚亂。但是在我看來(lái)本質(zhì)就兩點(diǎn):計算和 IO。下面將從這兩點(diǎn)來(lái)淺析一下我認為的高性能的” 道”。

2.2 高性能設計的” 道法”

2.2.1 計算上的” 道”

計算上的優(yōu)化手段無(wú)外乎兩種方式:1. 減少計算量 2. 加快單位時(shí)間的計算量

減少計算量:比如用索引來(lái)取代全局掃描、用同步代替異步、通過(guò)限流來(lái)減少請求處理量、采用更高效的數據結構和算法等。(舉例:mysql 的 BTree,redis 的跳表等)

加快單位時(shí)間的計算量:可以利用 CPU 多核的特性,比如用多線(xiàn)程代替單線(xiàn)程、用集群代替單機等。(舉例:多線(xiàn)程編程、分治計算等)

2.2.2 IO 上的” 道”

IO 上的優(yōu)化手段也可以從兩個(gè)方面來(lái)體現:1. 減少 IO 次數或者 IO 數據量 2. 加快 IO 速度

減少 IO 次數或者 IO 數據量:比如借助系統緩存或者外部緩存、通過(guò)零拷貝技術(shù)減少 IO 復制次數、批量讀寫(xiě)、數據壓縮等。

加快 IO 速度:比如用磁盤(pán)順序寫(xiě)代替隨機寫(xiě)、用 NIO 代替 BIO、用性能更好的 SSD 代替機械硬盤(pán)等。

3 kafka 高性能設計

理解了高性能設計的手段和本質(zhì)之后,我們再來(lái)看看 kafka 里面使用到的性能優(yōu)化方法。各類(lèi)消息中間件的本質(zhì)都是一個(gè)生產(chǎn)者 - 消費者模型,生產(chǎn)者發(fā)送消息給服務(wù)端進(jìn)行暫存,消費者從服務(wù)端獲取消息進(jìn)行消費。也就是說(shuō) kafka 分為三個(gè)部分:生產(chǎn)者 - 服務(wù)端 - 消費者,我們可以按照這三個(gè)來(lái)分別歸納一下其關(guān)于性能優(yōu)化的手段,這些手段也會(huì )涵蓋在我們之前梳理的腦圖里面。

3.1 生產(chǎn)者的高性能設計

3.1.1 批量發(fā)送消息

之前在上面說(shuō)過(guò),高性能的” 道” 在于計算和 IO 上,咱們先來(lái)看看在 IO 上 kafka 是如何做設計的。 IO 上的優(yōu)化
kafka 是一個(gè)消息中間件,數據的載體就是消息,如何將消息高效的進(jìn)行傳遞和持久化是 kafka 高性能設計的一個(gè)重點(diǎn)?;诖朔治?kafka 肯定是 IO 密集型應用,producer 需要通過(guò)網(wǎng)絡(luò ) IO 將消息傳遞給 broker,broker 需要通過(guò)磁盤(pán) IO 將消息持久化,consumer 需要通過(guò)網(wǎng)絡(luò ) IO 將消息從 broker 上拉取消費。

網(wǎng)絡(luò ) IO 上的優(yōu)化:producer->broker 發(fā)送消息不是一條一條發(fā)送的,kafka 模式會(huì )有個(gè)消息發(fā)送延遲機制,會(huì )將一批消息進(jìn)行聚合,一口氣打包發(fā)送給 broker,這樣就成功減少了 IO 的次數。除了傳輸消息本身以外,還要傳輸非常多的網(wǎng)絡(luò )協(xié)議本身的一些內容(稱(chēng)為 Overhead),所以將多條消息合并到一起傳輸,可有效減少網(wǎng)絡(luò )傳輸的 Overhead,進(jìn)而提高了傳輸效率。

磁盤(pán) IO 上的優(yōu)化:大家知道磁盤(pán)和內存的存儲速度是不同的,在磁盤(pán)上操作的速度是遠低于內存,但是在成本上內存是高于磁盤(pán)。kafka 是面向大數據量的消息中間件,也就是說(shuō)需要將大批量的數據持久化,這些數據放在內存上也是不現實(shí)。那 kafka 是怎么在磁盤(pán) IO 上進(jìn)行優(yōu)化的呢?在這里我先直接給出方法,具體細節在后文中解釋?zhuān)ㄋ墙柚谝环N磁盤(pán)順序寫(xiě)的機制來(lái)提升寫(xiě)入速度)。

3.1.2 負載均衡

1.kafka 負載均衡設計

188d6fe0-2239-11ee-962d-dac502259ad0.png

Kafka 有主題(Topic)概念,他是承載真實(shí)數據的邏輯容器,主題之下還分為若干個(gè)分區,Kafka 消息組織方式實(shí)際上是三級結構:主題 - 分區 - 消息。主題下的每條消息只會(huì )在某一個(gè)分區中,而不會(huì )在多個(gè)分區中被保存多份。
Kafka 這樣設計,使用分區的作用就是提供負載均衡的能力,對數據進(jìn)行分區的主要目的就是為了實(shí)現系統的高伸縮性(Scalability)。

不同的分區能夠放在不同的節點(diǎn)的機器上,而數據的讀寫(xiě)操作也都是針對分區這個(gè)粒度進(jìn)行的,每個(gè)節點(diǎn)的機器都能獨立地執行各自分區讀寫(xiě)請求。我們還可以通過(guò)增加節點(diǎn)來(lái)提升整體系統的吞吐量。Kafka 的分區設計,還可以實(shí)現業(yè)務(wù)級別的消息順序的問(wèn)題。

2. 具體分區策略

所謂的分區策略是指決定生產(chǎn)者將消息發(fā)送到那個(gè)分區的算法。Kafka 提供了默認的分區策略是輪詢(xún),同時(shí) kafka 也支持用戶(hù)自己制定。

輪詢(xún)策略:也稱(chēng)為 Round-robin 策略,即順序分配。輪詢(xún)的優(yōu)點(diǎn)是有著(zhù)優(yōu)秀的負載均衡的表現。

隨機策略:雖然也是追求負載均衡,但總體表現差于輪詢(xún)。

消息鍵劃分策略:還要一種是為每條消息配置一個(gè) key,按消息的 key 來(lái)存。Kafka 允許為每條消息指定一個(gè) key。一旦指定了 key ,那么會(huì )對 key 進(jìn)行 hash 計算,將相同的 key 存入相同的分區中,而且每個(gè)分區下的消息都是有序的。key 的作用很大,可以是一個(gè)有著(zhù)明確業(yè)務(wù)含義的字符串,也可以是用來(lái)表征消息的元數據。

其他的分區策略:基于地理位置的分區??梢詮乃蟹謪^中找出那些 Leader 副本在某個(gè)地理位置所有分區,然后隨機挑選一個(gè)進(jìn)行消息發(fā)送。

3.1.3 異步發(fā)送

1. 線(xiàn)程模型

18a682f0-2239-11ee-962d-dac502259ad0.png

之前已經(jīng)說(shuō)了 kafka 是選擇批量發(fā)送消息來(lái)提升整體的 IO 性能,具體流程是 kafka 生產(chǎn)者使用批處理試圖在內存中積累數據,主線(xiàn)程將多條消息通過(guò)一個(gè) ProduceRequest 請求批量發(fā)送出去,發(fā)送的消息暫存在一個(gè)隊列 (RecordAccumulator) 中,再由 sender 線(xiàn)程去獲取一批數據或者不超過(guò)某個(gè)延遲時(shí)間內的數據發(fā)送給 broker 進(jìn)行持久化。

優(yōu)點(diǎn):

可以提升 kafka 整體的吞吐量,減少網(wǎng)絡(luò ) IO 的次數;

提高數據壓縮效率 (一般壓縮算法都是數據量越大越能接近預期的壓縮效果);

缺點(diǎn):

數據發(fā)送有一定延遲,但是這個(gè)延遲可以由業(yè)務(wù)因素來(lái)自行設置。

3.1.4 高效序列化

1. 序列化的優(yōu)勢

Kafka 消息中的 Key 和 Value,都支持自定義類(lèi)型,只需要提供相應的序列化和反序列化器即可。因此,用戶(hù)可以根據實(shí)際情況選用快速且緊湊的序列化方式(比如 ProtoBuf、Avro)來(lái)減少實(shí)際的網(wǎng)絡(luò )傳輸量以及磁盤(pán)存儲量,進(jìn)一步提高吞吐量。

2. 內置的序列化器

org.apache.kafka.common.serialization.StringSerializer;

org.apache.kafka.common.serialization.LongSerializer;

org.apache.kafka.common.serialization.IntegerSerializer;

org.apache.kafka.common.serialization.ShortSerializer;

org.apache.kafka.common.serialization.FloatSerializer;

org.apache.kafka.common.serialization.DoubleSerializer;

org.apache.kafka.common.serialization.BytesSerializer;

org.apache.kafka.common.serialization.ByteBufferSerializer;

org.apache.kafka.common.serialization.ByteArraySerializer;

3.1.5 消息壓縮

1. 壓縮的目的

壓縮秉承了用時(shí)間換空間的經(jīng)典 trade-off 思想,即用 CPU 的時(shí)間去換取磁盤(pán)空間或網(wǎng)絡(luò ) I/O 傳輸量,Kafka 的壓縮算法也是出于這種目的。并且通常是:數據量越大,壓縮效果才會(huì )越好。

因為有了批量發(fā)送這個(gè)前期,從而使得 Kafka 的消息壓縮機制能真正發(fā)揮出它的威力(壓縮的本質(zhì)取決于多消息的重復性)。對比壓縮單條消息,同時(shí)對多條消息進(jìn)行壓縮,能大幅減少數據量,從而更大程度提高網(wǎng)絡(luò )傳輸率。 2. 壓縮的
方法

想了解 kafka 消息壓縮的設計,就需要先了解 kafka 消息的格式:

Kafka 的消息層次分為:消息集合(message set)和消息(message);一個(gè)消息集合中包含若干條日志項(record item),而日志項才是真正封裝消息的地方。

Kafka 底層的消息日志由一系列消息集合 - 日志項組成。Kafka 通常不會(huì )直接操作具體的一條條消息,他總是在消息集合這個(gè)層面上進(jìn)行寫(xiě)入操作。

每條消息都含有自己的元數據信息,kafka 會(huì )將一批消息相同的元數據信息給提升到外層的消息集合里面,然后再對整個(gè)消息集合來(lái)進(jìn)行壓縮。批量消息在持久化到 Broker 中的磁盤(pán)時(shí),仍然保持的是壓縮狀態(tài),最終是在 Consumer 端做了解壓縮操作。

壓縮算法效率對比

Kafka 共支持四種主要的壓縮類(lèi)型:Gzip、Snappy、Lz4 和 Zstd,具體效率對比如下:

18da1a7a-2239-11ee-962d-dac502259ad0.png

3.2 服務(wù)端的高性能設計

3.2.1 Reactor 網(wǎng)絡(luò )通信模型

kafka 相比其他消息中間件最出彩的地方在于他的高吞吐量,那么對于服務(wù)端來(lái)說(shuō)每秒的請求壓力將會(huì )巨大,需要有一個(gè)優(yōu)秀的網(wǎng)絡(luò )通信機制來(lái)處理海量的請求。如果 IO 有所研究的同學(xué),應該清楚:Reactor 模式正是采用了很經(jīng)典的 IO 多路復用技術(shù),它可以復用一個(gè)線(xiàn)程去處理大量的 Socket 連接,從而保證高性能。Netty 和 Redis 為什么能做到十萬(wàn)甚至百萬(wàn)并發(fā)?它們其實(shí)都采用了 Reactor 網(wǎng)絡(luò )通信模型。

1.kafka 網(wǎng)絡(luò )通信層架構

18fc9cda-2239-11ee-962d-dac502259ad0.png

從圖中可以看出,SocketServer 和 KafkaRequestHandlerPool 是其中最重要的兩個(gè)組件:

SocketServer:主要實(shí)現了 Reactor 模式,用于處理外部多個(gè) Clients(這里的 Clients 指的是廣義的 Clients,可能包含 Producer、Consumer 或其他 Broker)的并發(fā)請求,并負責將處理結果封裝進(jìn) Response 中,返還給 Clients

KafkaRequestHandlerPool:Reactor 模式中的 Worker 線(xiàn)程池,里面定義了多個(gè)工作線(xiàn)程,用于處理實(shí)際的 I/O 請求邏輯。

2. 請求流程

Clients 或其他 Broker 通過(guò) Selector 機制發(fā)起創(chuàng )建連接請求。(NIO 的機制,使用 epoll)

Processor 線(xiàn)程接收請求,并將其轉換成可處理的 Request 對象。

Processor 線(xiàn)程將 Request 對象放入共享的 RequestChannel 的 Request 隊列。

KafkaRequestHandler 線(xiàn)程從 Request 隊列中取出待處理請求,并進(jìn)行處理。

KafkaRequestHandler 線(xiàn)程將 Response 放回到對應 Processor 線(xiàn)程的 Response 隊列。

Processor 線(xiàn)程發(fā)送 Response 給 Request 發(fā)送方。

3.2.2 Kafka 的底層日志結構

基本結構的展示

191d5b96-2239-11ee-962d-dac502259ad0.jpg

Kafka 是一個(gè) Pub-Sub 的消息系統,無(wú)論是發(fā)布還是訂閱,都須指定 Topic。Topic 只是一個(gè)邏輯的概念。每個(gè) Topic 都包含一個(gè)或多個(gè) Partition,不同 Partition 可位于不同節點(diǎn)。同時(shí) Partition 在物理上對應一個(gè)本地文件夾 (也就是個(gè)日志對象 Log),每個(gè) Partition 包含一個(gè)或多個(gè) Segment,每個(gè) Segment 包含一個(gè)數據文件和多個(gè)與之對應的索引文件。在邏輯上,可以把一個(gè) Partition 當作一個(gè)非常長(cháng)的數組,可通過(guò)這個(gè) “數組” 的索引(offset)去訪(fǎng)問(wèn)其數據。 2.Partition 的并行處理能力

一方面,topic 是由多個(gè) partion 組成,Producer 發(fā)送消息到 topic 是有個(gè)負載均衡機制,基本上會(huì )將消息平均分配到每個(gè) partion 里面,同時(shí) consumer 里面會(huì )有個(gè) consumer group 的概念,也就是說(shuō)它會(huì )以組為單位來(lái)消費一個(gè) topic 內的消息,一個(gè) consumer group 內包含多個(gè) consumer,每個(gè) consumer 消費 topic 內不同的 partion,這樣通過(guò)多 partion 提高了消息的接收和處理能力

另一方面,由于不同 Partition 可位于不同機器,因此可以充分利用集群優(yōu)勢,實(shí)現機器間的并行處理。并且 Partition 在物理上對應一個(gè)文件夾,即使多個(gè) Partition 位于同一個(gè)節點(diǎn),也可通過(guò)配置讓同一節點(diǎn)上的不同 Partition 置于不同的 disk drive 上,從而實(shí)現磁盤(pán)間的并行處理,充分發(fā)揮多磁盤(pán)的優(yōu)勢。

3. 過(guò)期消息的清除

Kafka 的整個(gè)設計中,Partition 相當于一個(gè)非常長(cháng)的數組,而 Broker 接收到的所有消息順序寫(xiě)入這個(gè)大數組中。同時(shí) Consumer 通過(guò) Offset 順序消費這些數據,并且不刪除已經(jīng)消費的數據,從而避免了隨機寫(xiě)磁盤(pán)的過(guò)程。

由于磁盤(pán)有限,不可能保存所有數據,實(shí)際上作為消息系統 Kafka 也沒(méi)必要保存所有數據,需要刪除舊的數據。而這個(gè)刪除過(guò)程,并非通過(guò)使用 “讀 - 寫(xiě)” 模式去修改文件,而是將 Partition 分為多個(gè) Segment,每個(gè) Segment 對應一個(gè)物理文件,通過(guò)刪除整個(gè)文件的方式去刪除 Partition 內的數據。這種方式清除舊數據的方式,也避免了對文件的隨機寫(xiě)操作。

3.2.3 樸實(shí)高效的索引

1. 稀疏索引

194bb9d2-2239-11ee-962d-dac502259ad0.png

可以從上面看到,一個(gè) segment 包含一個(gè).log 后綴的文件和多個(gè) index 后綴的文件。那么這些文件具體作用是干啥的呢?并且這些文件除了后綴不同文件名都是相同,為什么這么設計?

.log 文件:具體存儲消息的日志文件

.index 文件:位移索引文件,可根據消息的位移值快速地從查詢(xún)到消息的物理文件位置

.timeindex 文件:時(shí)間戳索引文件,可根據時(shí)間戳查找到對應的位移信息

.txnindex 文件:已中止事物索引文件

除了.log 是實(shí)際存儲消息的文件以外,其他的幾個(gè)文件都是索引文件。索引本身設計的原來(lái)是一種空間換時(shí)間的概念,在這里 kafka 是為了加速查詢(xún)所使用。kafka 索引不會(huì )為每一條消息建立索引關(guān)系,這個(gè)也很好理解,畢竟對一條消息建立索引的成本還是比較大的,所以它是一種稀疏索引的概念,就好比我們常見(jiàn)的跳表,都是一種稀疏索引。

kafka 日志的文件名一般都是該 segment 寫(xiě)入的第一條消息的起始位移值 baseOffset,比如 000000000123.log,這里面的 123 就是 baseOffset,具體索引文件里面紀錄的數據是相對于起始位移的相對位移值 relativeOffset,baseOffset 與 relativeOffse 的加和即為實(shí)際消息的索引值。假設一個(gè)索引文件為:00000000000000000100.index,那么起始位移值即 100,當存儲位移為 150 的消息索引時(shí),在索引文件中的相對位移則為 150 - 100 = 50,這么做的好處是使用 4 字節保存位移即可,可以節省非常多的磁盤(pán)空間。(ps:kafka 真的是極致的壓縮了數據存儲的空間)

2. 優(yōu)化的二分查找算法

kafka 沒(méi)有使用我們熟知的跳表或者 B+Tree 結構來(lái)設計索引,而是使用了一種更為簡(jiǎn)單且高效的查找算法:二分查找。但是相對于傳統的二分查找,kafka 將其進(jìn)行了部分優(yōu)化,個(gè)人覺(jué)得設計的非常巧妙,在這里我會(huì )進(jìn)行詳述。

在這之前,我先補充一下 kafka 索引文件的構成:每個(gè)索引文件包含若干條索引項。不同索引文件的索引項的大小不同,比如 offsetIndex 索引項大小是 8B,timeIndex 索引項的大小是 12B。

1981b384-2239-11ee-962d-dac502259ad0.jpg

這里以 offsetIndex 為例子來(lái)詳述 kafka 的二分查找算法:

1)普通二分查找

offsetIndex 每個(gè)索引項大小是 8B,但操作系統訪(fǎng)問(wèn)內存時(shí)的最小單元是頁(yè),一般是 4KB,即 4096B,會(huì )包含了 512 個(gè)索引項。而找出在索引中的指定偏移量,對于操作系統訪(fǎng)問(wèn)內存時(shí)則變成了找出指定偏移量所在的頁(yè)。假設索引的大小有 13 個(gè)頁(yè),如下圖所示:

19965834-2239-11ee-962d-dac502259ad0.png

由于 Kafka 讀取消息,一般都是讀取最新的偏移量,所以要查詢(xún)的頁(yè)就集中在尾部,即第 12 號頁(yè)上。根據二分查找,將依次訪(fǎng)問(wèn) 6、9、11、12 號頁(yè)。

19cb1736-2239-11ee-962d-dac502259ad0.png

當隨著(zhù) Kafka 接收消息的增加,索引文件也會(huì )增加至第 13 號頁(yè),這時(shí)根據二分查找,將依次訪(fǎng)問(wèn) 7、10、12、13 號頁(yè)。

19f244d2-2239-11ee-962d-dac502259ad0.png

可以看出訪(fǎng)問(wèn)的頁(yè)和上一次的頁(yè)完全不同。之前在只有 12 號頁(yè)的時(shí)候,Kafak 讀取索引時(shí)會(huì )頻繁訪(fǎng)問(wèn) 6、9、11、12 號頁(yè),而由于 Kafka 使用了mmap來(lái)提高速度,即讀寫(xiě)操作都將通過(guò)操作系統的 page cache,所以 6、9、11、12 號頁(yè)會(huì )被緩存到 page cache 中,避免磁盤(pán)加載。但是當增至 13 號頁(yè)時(shí),則需要訪(fǎng)問(wèn) 7、10、12、13 號頁(yè),而由于 7、10 號頁(yè)長(cháng)時(shí)間沒(méi)有被訪(fǎng)問(wèn)(現代操作系統都是使用 LRU 或其變體來(lái)管理 page cache),很可能已經(jīng)不在 page cache 中了,那么就會(huì )造成缺頁(yè)中斷(線(xiàn)程被阻塞等待從磁盤(pán)加載沒(méi)有被緩存到 page cache 的數據)。在 Kafka 的官方測試中,這種情況會(huì )造成幾毫秒至 1 秒的延遲。 2)kafka 優(yōu)化的二分查找
Kafka 對二分查找進(jìn)行了改進(jìn)。既然一般讀取數據集中在索引的尾部。那么將索引中最后的 8192B(8KB)劃分為 “熱區”(剛好緩存兩頁(yè)數據),其余部分劃分為 “冷區”,分別進(jìn)行二分查找。這樣做的好處是,在頻繁查詢(xún)尾部的情況下,尾部的頁(yè)基本都能在 page cahce 中,從而避免缺頁(yè)中斷。

下面我們還是用之前的例子來(lái)看下。由于每個(gè)頁(yè)最多包含 512 個(gè)索引項,而最后的 1024 個(gè)索引項所在頁(yè)會(huì )被認為是熱區。那么當 12 號頁(yè)未滿(mǎn)時(shí),則 10、11、12 會(huì )被判定是熱區;而當 12 號頁(yè)剛好滿(mǎn)了的時(shí)候,則 11、12 被判定為熱區;當增至 13 號頁(yè)且未滿(mǎn)時(shí),11、12、13 被判定為熱區。假設我們讀取的是最新的消息,則在熱區中進(jìn)行二分查找的情況如下:

1a0147de-2239-11ee-962d-dac502259ad0.png

當 12 號頁(yè)未滿(mǎn)時(shí),依次訪(fǎng)問(wèn) 11、12 號頁(yè),當 12 號頁(yè)滿(mǎn)時(shí),訪(fǎng)問(wèn)頁(yè)的情況相同。當 13 號頁(yè)出現的時(shí)候,依次訪(fǎng)問(wèn) 12、13 號頁(yè),不會(huì )出現訪(fǎng)問(wèn)長(cháng)時(shí)間未訪(fǎng)問(wèn)的頁(yè),則能有效避免缺頁(yè)中斷。

3.mmap 的使用

利用稀疏索引,已經(jīng)基本解決了高效查詢(xún)的問(wèn)題,但是這個(gè)過(guò)程中仍然有進(jìn)一步的優(yōu)化空間,那便是通過(guò) mmap(memory mapped files) 讀寫(xiě)上面提到的稀疏索引文件,進(jìn)一步提高查詢(xún)消息的速度。

究竟如何理解 mmap?前面提到,常規的文件操作為了提高讀寫(xiě)性能,使用了 Page Cache 機制,但是由于頁(yè)緩存處在內核空間中,不能被用戶(hù)進(jìn)程直接尋址,所以讀文件時(shí)還需要通過(guò)系統調用,將頁(yè)緩存中的數據再次拷貝到用戶(hù)空間中。

1)常規文件讀寫(xiě)

1a420eae-2239-11ee-962d-dac502259ad0.png

app 拿著(zhù) inode 查找讀取文件

address_space 中存儲了 inode 和該文件對應頁(yè)面緩存的映射關(guān)系

頁(yè)面緩存缺失,引發(fā)缺頁(yè)異常

通過(guò) inode 找到磁盤(pán)地址,將文件信息讀取并填充到頁(yè)面緩存

頁(yè)面緩存處于內核態(tài),無(wú)法直接被 app 讀取到,因此要先拷貝到用戶(hù)空間緩沖區,此處發(fā)生內核態(tài)和用戶(hù)態(tài)的切換

tips:這一過(guò)程實(shí)際上發(fā)生了四次數據拷貝。首先通過(guò)系統調用將文件數據讀入到內核態(tài) Buffer(DMA 拷貝),然后應用程序將內存態(tài) Buffer 數據讀入到用戶(hù)態(tài) Buffer(CPU 拷貝),接著(zhù)用戶(hù)程序通過(guò) Socket 發(fā)送數據時(shí)將用戶(hù)態(tài) Buffer 數據拷貝到內核態(tài) Buffer(CPU 拷貝),最后通過(guò) DMA 拷貝將數據拷貝到 NIC Buffer。同時(shí),還伴隨著(zhù)四次上下文切換。

2)mmap 讀寫(xiě)模式

1a721e78-2239-11ee-962d-dac502259ad0.png

調用內核函數 mmap (),在頁(yè)表 (類(lèi)比虛擬內存 PTE) 中建立了文件地址和虛擬地址空間中用戶(hù)空間的映射關(guān)系

讀操作引發(fā)缺頁(yè)異常,通過(guò) inode 找到磁盤(pán)地址,將文件內容拷貝到用戶(hù)空間,此處不涉及內核態(tài)和用戶(hù)態(tài)的切換

tips:采用 mmap 后,它將磁盤(pán)文件與進(jìn)程虛擬地址做了映射,并不會(huì )招致系統調用,以及額外的內存 copy 開(kāi)銷(xiāo),從而提高了文件讀取效率。具體到 Kafka 的源碼層面,就是基于 JDK nio 包下的 MappedByteBuffer 的 map 函數,將磁盤(pán)文件映射到內存中。只有索引文件的讀寫(xiě)才用到了 mmap。

3.2.4 消息存儲 - 磁盤(pán)順序寫(xiě)

對于我們常用的機械硬盤(pán),其讀取數據分 3 步:

尋道;

尋找扇區;

讀取數據;

前兩個(gè),即尋找數據位置的過(guò)程為機械運動(dòng)。我們常說(shuō)硬盤(pán)比內存慢,主要原因是這兩個(gè)過(guò)程在拖后腿。不過(guò),硬盤(pán)比內存慢是絕對的嗎?其實(shí)不然,如果我們能通過(guò)順序讀寫(xiě)減少尋找數據位置時(shí)讀寫(xiě)磁頭的移動(dòng)距離,硬盤(pán)的速度還是相當可觀(guān)的。一般來(lái)講,IO 速度層面,內存順序 IO > 磁盤(pán)順序 IO > 內存隨機 IO > 磁盤(pán)隨機 IO。這里用一張網(wǎng)上的圖來(lái)對比一下相關(guān) IO 性能:

1a86faa0-2239-11ee-962d-dac502259ad0.png

Kafka 在順序 IO 上的設計分兩方面看:

LogSegment 創(chuàng )建時(shí),一口氣申請 LogSegment 最大 size 的磁盤(pán)空間,這樣一個(gè)文件內部盡可能分布在一個(gè)連續的磁盤(pán)空間內;

.log 文件也好,.index 和.timeindex 也罷,在設計上都是只追加寫(xiě)入,不做更新操作,這樣避免了隨機 IO 的場(chǎng)景;

3.2.5 Page Cache 的使用

1ab61b00-2239-11ee-962d-dac502259ad0.jpg

為了優(yōu)化讀寫(xiě)性能,Kafka 利用了操作系統本身的 Page Cache,就是利用操作系統自身的內存而不是 JVM 空間內存。這樣做的好處有:

避免 Object 消耗:如果是使用 Java 堆,Java 對象的內存消耗比較大,通常是所存儲數據的兩倍甚至更多。

避免 GC 問(wèn)題:隨著(zhù) JVM 中數據不斷增多,垃圾回收將會(huì )變得復雜與緩慢,使用系統緩存就不會(huì )存在 GC 問(wèn)題

相比于使用 JVM 或 in-memory cache 等數據結構,利用操作系統的 Page Cache 更加簡(jiǎn)單可靠。

首先,操作系統層面的緩存利用率會(huì )更高,因為存儲的都是緊湊的字節結構而不是獨立的對象。

其次,操作系統本身也對于 Page Cache 做了大量?jì)?yōu)化,提供了 write-behind、read-ahead 以及 flush 等多種機制。

再者,即使服務(wù)進(jìn)程重啟,JVM 內的 Cache 會(huì )失效,Page Cache 依然可用,避免了 in-process cache 重建緩存的過(guò)程。

通過(guò)操作系統的 Page Cache,Kafka 的讀寫(xiě)操作基本上是基于內存的,讀寫(xiě)速度得到了極大的提升。

3.3 消費端的高性能設計

3.3.1 批量消費

生產(chǎn)者是批量發(fā)送消息,消息者也是批量拉取消息的,每次拉取一個(gè)消息 batch,從而大大減少了網(wǎng)絡(luò )傳輸的 overhead。在這里 kafka 是通過(guò) fetch.min.bytes 參數來(lái)控制每次拉取的數據大小。默認是 1 字節,表示只要 Kafka Broker 端積攢了 1 字節的數據,就可以返回給 Consumer 端,這實(shí)在是太小了。我們還是讓 Broker 端一次性多返回點(diǎn)數據吧。

并且,在生產(chǎn)者高性能設計目錄里面也說(shuō)過(guò),生產(chǎn)者其實(shí)在 Client 端對批量消息進(jìn)行了壓縮,這批消息持久化到 Broker 時(shí),仍然保持的是壓縮狀態(tài),最終在 Consumer 端再做解壓縮操作。

3.3.2 零拷貝 - 磁盤(pán)消息文件的讀取

1.zero-copy 定義

零拷貝并不是不需要拷貝,而是減少不必要的拷貝次數。通常是說(shuō)在 IO 讀寫(xiě)過(guò)程中。

零拷貝字面上的意思包括兩個(gè),“零” 和 “拷貝”:

“拷貝”:就是指數據從一個(gè)存儲區域轉移到另一個(gè)存儲區域。

“零” :表示次數為 0,它表示拷貝數據的次數為 0。

實(shí)際上,零拷貝是有廣義和狹義之分,目前我們通常聽(tīng)到的零拷貝,包括上面這個(gè)定義減少不必要的拷貝次數都是廣義上的零拷貝。其實(shí)了解到這點(diǎn)就足夠了。

我們知道,減少不必要的拷貝次數,就是為了提高效率。那零拷貝之前,是怎樣的呢?

2. 傳統 IO 的流程

做服務(wù)端開(kāi)發(fā)的小伙伴,文件下載功能應該實(shí)現過(guò)不少了吧。如果你實(shí)現的是一個(gè) web 程序 ,前端請求過(guò)來(lái),服務(wù)端的任務(wù)就是:將服務(wù)端主機磁盤(pán)中的文件從已連接的 socket 發(fā)出去。關(guān)鍵實(shí)現代碼如下:


while((n = read(diskfd, buf, BUF_SIZE)) > 0) write(sockfd, buf , n); 傳統的 IO 流程,包括 read 和 write 的過(guò)程。

read:把數據從磁盤(pán)讀取到內核緩沖區,再拷貝到用戶(hù)緩沖區

write:先把數據寫(xiě)入到 socket 緩沖區,最后寫(xiě)入網(wǎng)卡設備 流程圖如下:

1ae0a64a-2239-11ee-962d-dac502259ad0.png

用戶(hù)應用進(jìn)程調用 read 函數,向操作系統發(fā)起 IO 調用,上下文從用戶(hù)態(tài)轉為內核態(tài)(切換 1)

DMA 控制器把數據從磁盤(pán)中,讀取到內核緩沖區。

CPU 把內核緩沖區數據,拷貝到用戶(hù)應用緩沖區,上下文從內核態(tài)轉為用戶(hù)態(tài)(切換 2) ,read 函數返回

用戶(hù)應用進(jìn)程通過(guò) write 函數,發(fā)起 IO 調用,上下文從用戶(hù)態(tài)轉為內核態(tài)(切換 3)

CPU 將用戶(hù)緩沖區中的數據,拷貝到 socket 緩沖區

DMA 控制器把數據從 socket 緩沖區,拷貝到網(wǎng)卡設備,上下文從內核態(tài)切換回用戶(hù)態(tài)(切換 4) ,write 函數返回

從流程圖可以看出,傳統 IO 的讀寫(xiě)流程 ,包括了 4 次上下文切換(4 次用戶(hù)態(tài)和內核態(tài)的切換),4 次數據拷貝(兩次 CPU 拷貝以及兩次的 DMA 拷貝 ),什么是 DMA 拷貝呢?我們一起來(lái)回顧下,零拷貝涉及的操作系統知識點(diǎn)。

3. 零拷貝相關(guān)知識點(diǎn)

1)內核空間和用戶(hù)空間

操作系統為每個(gè)進(jìn)程都分配了內存空間,一部分是用戶(hù)空間,一部分是內核空間。內核空間是操作系統內核訪(fǎng)問(wèn)的區域,是受保護的內存空間,而用戶(hù)空間是用戶(hù)應用程序訪(fǎng)問(wèn)的內存區域。以 32 位操作系統為例,它會(huì )為每一個(gè)進(jìn)程都分配了 4G (2 的 32 次方) 的內存空間。

內核空間:主要提供進(jìn)程調度、內存分配、連接硬件資源等功能

用戶(hù)空間:提供給各個(gè)程序進(jìn)程的空間,它不具有訪(fǎng)問(wèn)內核空間資源的權限,如果應用程序需要使用到內核空間的資源,則需要通過(guò)系統調用來(lái)完成。進(jìn)程從用戶(hù)空間切換到內核空間,完成相關(guān)操作后,再從內核空間切換回用戶(hù)空間。

2)用戶(hù)態(tài) & 內核態(tài)

如果進(jìn)程運行于內核空間,被稱(chēng)為進(jìn)程的內核態(tài)

如果進(jìn)程運行于用戶(hù)空間,被稱(chēng)為進(jìn)程的用戶(hù)態(tài)。

3)上下文切換 cpu 上下文

CPU 寄存器,是 CPU 內置的容量小、但速度極快的內存。而程序計數器,則是用來(lái)存儲 CPU 正在執行的指令位置、或者即將執行的下一條指令位置。它們都是 CPU 在運行任何任務(wù)前,必須的依賴(lài)環(huán)境,因此叫做 CPU 上下文。

cpu 上下文切換

它是指,先把前一個(gè)任務(wù)的 CPU 上下文(也就是 CPU 寄存器和程序計數器)保存起來(lái),然后加載新任務(wù)的上下文到這些寄存器和程序計數器,最后再跳轉到程序計數器所指的新位置,運行新任務(wù)。

一般我們說(shuō)的上下文切換 ,就是指內核(操作系統的核心)在 CPU 上對進(jìn)程或者線(xiàn)程進(jìn)行切換。進(jìn)程從用戶(hù)態(tài)到內核態(tài)的轉變,需要通過(guò)系統調用 來(lái)完成。系統調用的過(guò)程,會(huì )發(fā)生 CPU 上下文的切換 。 4)DMA 技術(shù)

DMA,英文全稱(chēng)是 Direct Memory Access ,即直接內存訪(fǎng)問(wèn)。DMA 本質(zhì)上是一塊主板上獨立的芯片,允許外設設備和內存存儲器之間直接進(jìn)行 IO 數據傳輸,其過(guò)程不需要 CPU 的參與 。

我們一起來(lái)看下 IO 流程,DMA 幫忙做了什么事情。

1af40a64-2239-11ee-962d-dac502259ad0.png

可以發(fā)現,DMA 做的事情很清晰啦,它主要就是幫忙 CPU 轉發(fā)一下 IO 請求,以及拷貝數據 。 之所以需要 DMA,主要就是效率,它幫忙 CPU 做事情,這時(shí)候,CPU 就可以閑下來(lái)去做別的事情,提高了 CPU 的利用效率。 4.kafka 消費的 zero-copy

1)實(shí)現原理

零拷貝并不是沒(méi)有拷貝數據,而是減少用戶(hù)態(tài) / 內核態(tài)的切換次數以及 CPU 拷貝的次數。零拷貝實(shí)現有多種方式,分別是

mmap+write

sendfile

在服務(wù)端那里,我們已經(jīng)知道了 kafka 索引文件使用的 mmap 來(lái)進(jìn)行零拷貝優(yōu)化的,現在告訴你 kafka 消費者在讀取消息的時(shí)候使用的是 sendfile 來(lái)進(jìn)行零拷貝優(yōu)化。

linux 2.4 版本之后,對 sendfile 做了優(yōu)化升級,引入 SG-DMA 技術(shù),其實(shí)就是對 DMA 拷貝加入了 scatter/gather 操作,它可以直接從內核空間緩沖區中將數據讀取到網(wǎng)卡。使用這個(gè)特點(diǎn)搞零拷貝,即還可以多省去一次 CPU 拷貝 。

sendfile+DMA scatter/gather 實(shí)現的零拷貝流程如下:

1b269a88-2239-11ee-962d-dac502259ad0.png

用戶(hù)進(jìn)程發(fā)起 sendfile 系統調用,上下文(切換 1)從用戶(hù)態(tài)轉向內核態(tài)。

DMA 控制器,把數據從硬盤(pán)中拷貝到內核緩沖區。

CPU 把內核緩沖區中的文件描述符信息 (包括內核緩沖區的內存地址和偏移量)發(fā)送到 socket 緩沖區

DMA 控制器根據文件描述符信息,直接把數據從內核緩沖區拷貝到網(wǎng)卡

上下文(切換 2)從內核態(tài)切換回用戶(hù)態(tài) ,sendfile 調用返回。

可以發(fā)現,sendfile+DMA scatter/gather 實(shí)現的零拷貝,I/O 發(fā)生了 2 次用戶(hù)空間與內核空間的上下文切換,以及 2 次數據拷貝。其中 2 次數據拷貝都是包 DMA 拷貝 。這就是真正的 零拷貝(Zero-copy) 技術(shù),全程都沒(méi)有通過(guò) CPU 來(lái)搬運數據,所有的數據都是通過(guò) DMA 來(lái)進(jìn)行傳輸的。

2)底層實(shí)現 Kafka 數據傳輸通過(guò) TransportLayer 來(lái)完成,其子類(lèi) PlaintextTransportLayer 通過(guò) Java NIO 的 FileChannel 的 transferTo 和 transferFrom 方法實(shí)現零拷貝。底層就是 sendfile。消費者從 broker 讀取數據,就是由此實(shí)現。


@Override public long transferFrom(FileChannel fileChannel, long position, long count) throws IOException { return fileChannel.transferTo(position, count, socketChannel); } tips:transferTo 和 transferFrom 并不保證一定能使用零拷貝。實(shí)際上是否能使用零拷貝與操作系統相關(guān),如果操作系統提供 sendfile 這樣的零拷貝系統調用,則這兩個(gè)方法會(huì )通過(guò)這樣的系統調用充分利用零拷貝的優(yōu)勢,否則并不能通過(guò)這兩個(gè)方法本身實(shí)現零拷貝。

4 總結

文章第一部分為大家講解了高性能常見(jiàn)的優(yōu)化手段,從” 秘籍” 和” 道法” 兩個(gè)方面來(lái)詮釋高性能設計之路該如何走,并引申出計算和 IO 兩個(gè)優(yōu)化方向。 文章第二部分是 kafka 內部高性能的具體設計 —— 分別從生產(chǎn)者、服務(wù)端、消費者來(lái)進(jìn)行全方位講解,包括其設計、使用及相關(guān)原理。 希望通過(guò)這篇文章,能夠使大家不僅學(xué)習到相關(guān)方法論,也能明白其方法論具體的落地方案,一起學(xué)習,一起成長(cháng)。






審核編輯:劉清

聲明:本文內容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權轉載。文章觀(guān)點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習之用,如有內容侵權或者其他違規問(wèn)題,請聯(lián)系本站處理。 舉報投訴
  • 多路復用器
    +關(guān)注

    關(guān)注

    9

    文章

    846

    瀏覽量

    65138
  • 網(wǎng)絡(luò )通信

    關(guān)注

    4

    文章

    737

    瀏覽量

    29597
  • Hash算法
    +關(guān)注

    關(guān)注

    0

    文章

    43

    瀏覽量

    7368
  • 負載均衡器
    +關(guān)注

    關(guān)注

    0

    文章

    18

    瀏覽量

    2557

原文標題:從Kafka中學(xué)習高性能系統如何設計

文章出處:【微信號:OSC開(kāi)源社區,微信公眾號:OSC開(kāi)源社區】歡迎添加關(guān)注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    基于閃存存儲的Apache Kafka性能提升方法

    據生態(tài)系統中最常用的分布式消息傳遞系統之一的Apache Kafka進(jìn)行評估,測試如何以最佳方式將美光固態(tài)存儲應用于 Apache Kafka,以及將產(chǎn)生怎樣的收益。A
    發(fā)表于 07-24 06:58

    如何設計超高性能微波天線(xiàn)饋源系統?

    ,新一代的同步數字系列SDH微波通信系統替代了傳統意義上的PDH微波通信。為什么要研制超高性能的微波天線(xiàn)?為適應正在興起的SDH微波通信中頻率復用的發(fā)展。
    發(fā)表于 08-12 08:08

    Kafka讀取數據操作指南

    Kafka消費者—— Kafka讀取數據
    發(fā)表于 09-16 06:42

    詳解Kafka學(xué)習

    Kafka學(xué)習筆記
    發(fā)表于 10-12 15:11

    基于發(fā)布與訂閱的消息系統Kafka

    Kafka權威指南》——初識 Kafka
    發(fā)表于 03-05 13:46

    Kafka基礎入門(mén)文檔

    kafka系統入門(mén)教程(原理、配置、集群搭建、Java應用、Kafka-manager)
    發(fā)表于 03-12 07:22

    Kafka存儲數據學(xué)習

    Kafka學(xué)習筆記(一)
    發(fā)表于 04-03 11:34

    Kafka的四個(gè)基礎概念學(xué)習

    Kafka 是一個(gè)消息系統,原本開(kāi)發(fā)自 LinkedIn,用作 LinkedIn 的活動(dòng)流(Activity Stream)和運營(yíng)數據處理管道(Pipeline)的基礎?,F在它已被多家不同類(lèi)型的公司 作為多種類(lèi)型的數據管道和消息系統
    的頭像 發(fā)表于 05-03 18:20 ?2707次閱讀
    <b class='flag-5'>Kafka</b>的四個(gè)基礎概念<b class='flag-5'>學(xué)習</b>

    基于臭氧的Kafka自適應調優(yōu)方法ENLHS

    Kafka應用在生產(chǎn)環(huán)境中時(shí),除機器的硬件環(huán)境和系統平臺影響其性能外,Kaka自身的配置項決定著(zhù)其能否在硬件資源有限的情況下達到理想的性能,但人為修改和調優(yōu)配置項的效率極差。海量數據發(fā)
    發(fā)表于 05-13 11:39 ?7次下載

    為什么Kafka會(huì )怎么快 Kafka 的應用場(chǎng)景

    Kafka 是由 LinkedIn 公司推出的一個(gè)高吞吐的分布式消息系統,通俗地說(shuō)就是一個(gè)基于發(fā)布和訂閱的消息隊列,溫故而知新,反復學(xué)習優(yōu)秀的框架,定有所獲。 應用場(chǎng)景 Kafka
    的頭像 發(fā)表于 06-04 16:12 ?1884次閱讀
    為什么<b class='flag-5'>Kafka</b>會(huì )怎么快 <b class='flag-5'>Kafka</b> 的應用場(chǎng)景

    Kafka的概念及Kafka的宕機

    很好奇Kafka的高可用實(shí)現和保障。從 Kafka 部署后,系統內部使用的 Kafka 一直運行穩定,沒(méi)有出現不可用的情況。 但最近系統測試
    的頭像 發(fā)表于 08-27 11:21 ?1709次閱讀
    <b class='flag-5'>Kafka</b>的概念及<b class='flag-5'>Kafka</b>的宕機

    Kafka如何做到那么高的性能

    有人說(shuō):他曾在一臺配置較好的機子上對 Kafka 進(jìn)行性能壓測,壓測結果是 Kafka 單個(gè)節點(diǎn)的極限處理能力接近每秒 2000萬(wàn) 條消息,吞吐量達到每秒 600MB。
    的頭像 發(fā)表于 09-14 17:03 ?850次閱讀

    Kafka 的簡(jiǎn)介

    ? 1 kafka簡(jiǎn)介 2 為什么要用消息系統 3 kafka基礎知識 4 kafka集群架構 5 總結 ? 1 kafka簡(jiǎn)介 其主要設計
    的頭像 發(fā)表于 07-03 11:10 ?393次閱讀
    <b class='flag-5'>Kafka</b> 的簡(jiǎn)介

    物通博聯(lián)5G-kafka工業(yè)網(wǎng)關(guān)實(shí)現kafka協(xié)議對接到云平臺

    Kafka協(xié)議是一種基于TCP層的網(wǎng)絡(luò )協(xié)議,用于在分布式消息傳遞系統Apache Kafka中發(fā)送和接收消息。Kafka協(xié)議定義了客戶(hù)端和服務(wù)器之間的通信方式和數據格式,允許客戶(hù)端發(fā)送
    的頭像 發(fā)表于 07-11 10:44 ?343次閱讀

    golang中使用kafka的綜合指南

    kafka是一個(gè)比較流行的分布式、可拓展、高性能、可靠的流處理平臺。在處理kafka的數據時(shí),這里有確保處理效率和可靠性的多種最佳實(shí)踐。本文將介紹這幾種實(shí)踐方式,并通過(guò)sarama實(shí)現他們。
    的頭像 發(fā)表于 11-30 11:18 ?365次閱讀
    亚洲欧美日韩精品久久_久久精品AⅤ无码中文_日本中文字幕有码在线播放_亚洲视频高清不卡在线观看