Kafka 請求是怎麼被處理的

2021-10-13 12:36:00 字數 3398 閱讀 8945

kafka broker 端處理請求的全流程:

1.順序處理請求。

while

(true

)

這個方法吞吐量太差,只適用於請求傳送非常不頻繁的系統。

每個請求使用單獨執行緒處理。為每個入站請求都建立乙個新的執行緒來非同步處理。

while

(true))

; thread.

start()

;}

該方法為每個請求都建立執行緒的做法開銷極大,在某些場景下甚至會壓垮整個服務。也是只適用於請求傳送頻率很低的業務場景。

kafka 使用的是reactor 模式處理請求:

reactor 模式是事件驅動架構的一種實現方式,特別適合應用於處理多個客戶端併發向伺服器端傳送請求的場景。

多個客戶端會傳送請求給到 reactor。reactor 有個請求分發執行緒 dispatcher,也就是圖中的 acceptor,它會將不同的請求下發到多個工作執行緒中處理。

在這個架構中,acceptor 執行緒只是用於請求分發,不涉及具體的邏輯處理,非常得輕量級,因此有很高的吞吐量表現。而這些工作執行緒可以根據實際業務處理需要任意增減,從而動態調節系統負載能力。

kafka的reactor模式:

在 kafka 中,工作的執行緒池叫網路執行緒池

kafka 提供了 broker 端引數 num.network.threads,用於調整該網路執行緒池的執行緒數。其預設值是 3,表示每台 broker 啟動時會建立 3 個網路執行緒,專門處理客戶端傳送的請求。

acceptor 執行緒採用輪詢的方式將入站請求公平地發到所有網路執行緒中,這種輪詢策略編寫簡單,同時也避免了請求處理的傾斜,有利於實現較為公平的請求處理排程。

當網路執行緒接收到請求後,又做了一層非同步執行緒池的處理:

當網路執行緒拿到請求後,它不是自己處理,而是將請求放入到乙個共享請求佇列中。broker 端還有個 io 執行緒池,負責從該佇列中取出請求,執行真正的處理。如果是 produce 生產請求,則將訊息寫入到底層的磁碟日誌中;如果是 fetch 請求,則從磁碟或頁快取中讀取訊息。

io 執行緒池處中的執行緒才是執行請求邏輯的執行緒。broker 端引數num.io.threads控制了這個執行緒池中的執行緒數。目前該引數預設值是 8,表示每台 broker 啟動後自動建立 8 個 io 執行緒處理請求。你可以根據實際硬體條件設定此執行緒池的個數。

當 io 執行緒處理完請求後,會將生成的響應傳送到網路執行緒池的響應佇列中,然後由對應的網路執行緒負責將 response 返還給客戶端。

請求佇列是所有網路執行緒共享的,而響應佇列則是每個網路執行緒專屬的。

圖中有乙個叫 purgatory 的元件,這是 kafka 中著名的「煉獄」元件。用來快取延時請求(delayed request)的。所謂延時請求,就是那些一時未滿足條件不能立刻處理的請求。比如設定了 acks=all 的 produce 請求,一旦設定了 acks=all,那麼該請求就必須等待 isr 中所有副本都接收了訊息後才能返回,此時處理該請求的 io 執行緒就必須等待其他 broker 的寫入結果。當請求不能立刻處理時,它就會暫存在 purgatory 中。稍後一旦滿足了完成條件,io 執行緒會繼續處理該請求,並將 response 放入對應網路執行緒的響應佇列中。

到目前為止,請求處理流程對於所有請求都是適用的,也就是說,kafka broker 對所有請求是一視同仁的。但是,在 kafka 內部,除了客戶端傳送的 produce 請求和 fetch 請求之外,還有很多執行其他操作的請求型別,比如負責更新 leader 副本、follower 副本以及 isr 集合的 leaderandisr 請求,負責勒令副本下線的 stopreplica 請求等。與 produce 和 fetch 請求相比,這些請求有個明顯的不同:它們不是資料類的請求,而是控制類的請求。也就是說,它們並不是操作訊息資料的,而是用來執行特定的 kafka 內部動作的。

kafka 社群把 produce 和 fetch 這類請求稱為資料類請求,把 leaderandisr、stopreplica 這類請求稱為控制類請求。細究起來,當前這種一視同仁的處理方式對控制類請求是不合理的。為什麼呢?因為控制類請求有這樣一種能力:它可以直接令資料類請求失效!

我來舉個例子說明一下。假設我們有個主題只有 1 個分割槽,該分割槽配置了兩個副本,其中 leader 副本儲存在 broker 0 上,follower 副本儲存在 broker 1 上。假設 broker 0 這台機器積壓了很多的 produce 請求,此時你如果使用 kafka 命令強制將該主題分割槽的 leader、follower 角色互換,那麼 kafka 內部的控制器元件(controller)會傳送 leaderandisr 請求給 broker 0,顯式地告訴它,當前它不再是 leader,而是 follower 了,而 broker 1 上的 follower 副本因為被選為新的 leader,因此停止向 broker 0 拉取訊息。

這時,乙個尷尬的場面就出現了:如果剛才積壓的 produce 請求都設定了 acks=all,那麼這些在 leaderandisr 傳送之前的請求就都無法正常完成了。就像前面說的,它們會被暫存在 purgatory 中不斷重試,直到最終請求超時返回給客戶端。

設想一下,如果 kafka 能夠優先處理 leaderandisr 請求,broker 0 就會立刻丟擲not_leader_for_partition 異常,快速地標識這些積壓 produce 請求已失敗,這樣客戶端不用等到 purgatory 中的請求超時就能立刻感知,從而降低了請求的處理時間。即使 acks 不是 all,積壓的 produce 請求能夠成功寫入 leader 副本的日誌,但處理 leaderandisr 之後,broker 0 上的 leader 變為了 follower 副本,也要執行顯式的日誌截斷(log truncation,即原 leader 副本成為 follower 後,會將之前寫入但未提交的訊息全部刪除),依然做了很多無用功。

基於這些問題,社群於 2.3 版本正式實現了資料類請求和控制類請求的分離。即複製一套資料類請求處理流程作為控制類請求處理流程。kafka broker 啟動後,會在後台分別建立網路執行緒池和 io 執行緒池,它們分別處理資料類請求和控制類請求。至於所用的 socket 埠,自然是使用不同的埠了,你需要提供不同的listeners 配置,顯式地指定哪套埠用於處理哪類請求。

備選:在 broker 中實現乙個優先順序佇列,並賦予控制類請求更高的優先順序。這個方案最大的問題在於,它無法處理請求佇列已滿的情形。當請求佇列已經無法容納任何新的請求時,縱然有優先順序之分,它也無法處理新的控制類請求了。

Django是怎麼處理請求的

當你通過在瀏覽器裡敲來訪問hello world訊息得時候,django在後台有些什麼動作呢?所有均開始於setting檔案。當你執行python manage.py runserver,指令碼將在於manage.py同乙個目錄下查詢名為setting.py的檔案。這個檔案包含了所有有關這個djan...

Django是怎麼處理請求的

當你執行python manage.py runserver,指令碼將在於manage.py同乙個目錄下查詢名為setting.py的檔案。這個檔案包含了所有有關這個django專案的配置資訊,總結一下 進來的請求轉入 hello django通過在root urlconf配置來決定根urlconf...

深入kafka 一 處理請求

介紹集群成員關係前,我們要清楚集群包含哪些成員,所有關係都是圍繞這幾個成員展開的 1.成員 包含broker zookeeper和kafka元件。2.關係 broker啟動的時候,它通過建立臨時節點把自己的id註冊到zookeeper kafka元件訂閱zookeeper的 brokers ids路...