kafka網絡請求模塊

最近一直研究kafka源碼,想着有必要記錄一下。不管研究是否到位,也算是一個里程碑吧。

當我們說到 Kafka 服務器端,也就是 Broker 的時候,往往會說它承擔着消息持久化的功能,但本質上,它其實就是一個不斷接收外部請求、處理請求,然後發送處理結果的 Java 進程 (因爲scala 代碼被編譯之後生成.class 文件,它和 Java 代碼被編譯後的效果是一樣的)。

高效地保存排隊中的請求,是確保 Broker 高處理性能的關鍵。既然這樣,Broker 上的請求隊列是怎麼實現的呢?接下來,就看下 Broker 底層請求對象的建模和請求隊列的實現原理,以及 Broker請求處理方面的核心監控指標。目前,Broker 與 Clients 進行交互主要是基於Request / Response 機制,所以,很有必要學習一下源碼是如何定義 Request 和 Response 的。

一. 請求(Request)

先看一下 RequestChannel 源碼中的 Request 定義代碼。源碼位於 core/src/main/scala/kafka/network ,RequestChannel.scala 文件,是主要實現類。

```java

sealed trait BaseRequest
case object ShutdownRequest extends BaseRequest
class Request(val processor: Int,
              val context: RequestContext,
              val startTimeNanos: Long,
              memoryPool: MemoryPool,
              @volatile private var buffer: ByteBuffer,
              metrics: RequestChannel.Metrics) extends BaseRequest {

                        .....

}

```

BaseRequest 是一個 trait 接口,定義了基礎的請求類型。它有兩個實現類:ShutdownRequest 類和 Request 類。

ShutdownRequest 僅僅起到一個標誌位的作用。當 Broker 進程關閉時,請求處理器會發送 ShutdownRequest 到專屬的請求處理線程。該線程接收到此請求後,會主動觸發一系列的 Broker 關閉邏輯。Request 則是真正定義各類 Clients 端或 Broker 端請求的實現類。它定義的屬性包括 processor、context、startTimeNanos、memoryPool、buffer 和 metrics等等。

1.  processor

processor 是 Processor 線程的序號,即這個請求是由哪個 Processor 線程接收處理的。Broker 端參數 num.network.threads 控制了 Broker 每個監聽器上創建的 Processor 線程數。在默認情況下,Broker 啓動時會創建 3 個 Processor 線程,爲一組,分別給 listeners 參數中設置的監聽器使用,每組的序號分別是 0、1、2。

那爲什麼要保存 Processor 線程的序號呢?這是因爲,當 Request 被後面的 I/O 線程處理完成後,還要依靠 Processor 線程發送 Response 給請求發送方,因此,Request 中必須記錄它之前是被哪個 Processor 線程接收的。另外,這裏明確一點:Processor 線程僅僅是網絡接收線程,不會執行真正的 Request 請求處理邏輯,那是 I/O 線程負責的事情。

2.  context

context 是用來標識請求上下文信息的。Kafka 源碼中定義了 RequestContext 類,顧名思義,它保存了有關 Request 的所有上下文信息。RequestContext 類定義在 clients 工程中,下面是它主要的邏輯代碼。我用註釋的方式解釋下主體代碼的含義。

```java

public class RequestContext implements AuthorizableRequestContext {
    // Request頭部數據,主要是一些對用戶不可見的元數據信息,如Request類型、Request API版本、clientId等
    public final RequestHeader header;
    // Request發送方的TCP連接串標識,由Kafka根據一定規則定義,主要用於表示TCP連接
    public final String connectionId;
    // Request發送方IP地址
    public final InetAddress clientAddress;
    // Kafka用戶認證類,用於認證授權
    public final KafkaPrincipal principal;
    // 監聽器名稱,可以是預定義的監聽器(如PLAINTEXT),也可自行定義
    public final ListenerName listenerName;
    // 安全協議類型,目前支持4種:PLAINTEXT、SSL、SASL_PLAINTEXT、SASL_SSL
    public final SecurityProtocol securityProtocol;
    // 用戶自定義的一些連接方信息
    public final ClientInformation clientInformation;
    // RequestContext的封裝
    public RequestContext(RequestHeader header,
                          String connectionId,
                          InetAddress clientAddress,
                          KafkaPrincipal principal,
                          ListenerName listenerName,
                          SecurityProtocol securityProtocol,
                          ClientInformation clientInformation) {
        this.header = header;
        this.connectionId = connectionId;
        this.clientAddress = clientAddress;
        this.principal = principal;
        this.listenerName = listenerName;
        this.securityProtocol = securityProtocol;
        this.clientInformation = clientInformation;
    }
    // 從給定的ByteBuffer中提取出Request和對應的Size值
    public RequestAndSize parseRequest(ByteBuffer buffer) {
        if (isUnsupportedApiVersionsRequest()) {
            // Unsupported ApiVersion requests are treated as v0 requests and are not parsed
            // 不支持的ApiVersions請求類型被視爲是V0版本的請求,並且不做解析操作,直接返回
            ApiVersionsRequest apiVersionsRequest = new ApiVersionsRequest(new ApiVersionsRequestData(), (short) 0, header.apiVersion());
            return new RequestAndSize(apiVersionsRequest, 0);
        } else {
            // 從請求頭部數據中獲取ApiKeys信息
            ApiKeys apiKey = header.apiKey();
            try {
                // 從請求頭部數據中獲取版本信息
                short apiVersion = header.apiVersion();
                // 解析請求
                Struct struct = apiKey.parseRequest(apiVersion, buffer);
                AbstractRequest body = AbstractRequest.parseRequest(apiKey, apiVersion, struct);
                // 封裝解析後的請求對象以及請求大小返回
                return new RequestAndSize(body, struct.sizeOf());
            } catch (Throwable ex) {
                // 解析過程中出現任何問題都視爲無效請求,拋出異常
                throw new InvalidRequestException("Error getting request for apiKey: " + apiKey +
                        ", apiVersion: " + header.apiVersion() +
                        ", connectionId: " + connectionId +
                        ", listenerName: " + listenerName +
                        ", principal: " + principal, ex);
            }
        }
    }

```

3. startTimeNanos

startTimeNanos 記錄了 Request 對象被創建的時間,主要用於各種時間統計指標的計算。

請求對象中的很多 JMX 指標,特別是時間類的統計指標,都需要使用 startTimeNanos 字段。它是以納秒爲單位的時間戳信息,可以實現非常細粒度的時間統計精度。

4. memoryPool

memoryPool 表示源碼定義的一個非阻塞式的內存緩衝區,主要作用是避免 Request 對象無限使用內存。當前,該內存緩衝區的接口類和實現類,分別是 MemoryPool 和 SimpleMemoryPool。

5. buffer

buffer 是真正保存 Request 對象內容的字節緩衝區。Request 發送方必須按照 Kafka RPC 協議規定的格式向該緩衝區寫入字節,否則將拋出 InvalidRequestException 異常。這個邏輯主要是由 RequestContext 的 parseRequest 方法實現的,看上圖。

6. metrics

metrics 是 Request 相關的各種監控指標的一個管理類。它裏面構建了一個 Map,封裝了所有的請求 JMX 指標。除了上面這些重要的字段屬性之外,Request 類中的大部分代碼都是與監控指標相關的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章