TiDB Binlog 源碼閱讀系列文章(三)Pump client 介紹

作者:黃佳豪

上篇文章 中,我們介紹了 Pump 的作用是存儲 TiDB 產生的 binlog。本篇將介紹 Pump client,希望大家瞭解 TiDB 把 binlog 寫到 Pump,以及輸出數據的過程。

gRPC API

Pump client 的代碼在 tidb-tools 下這個 路徑,TiDB 會直接 import 這個路徑使用 Pump client package。TiDB 跟 Pump 之間使用 gRPC 通信,相關的 proto 文件定義在 這裏。Pump server 提供以下兩個接口:

// Interfaces exported by Pump.
service Pump {
    // Writes a binlog to the local file on the pump machine.
    // A response with an empty errmsg is returned if the binlog is written successfully.
    rpc WriteBinlog(WriteBinlogReq) returns (WriteBinlogResp) {}

    // Sends binlog stream from a given location.
    rpc PullBinlogs(PullBinlogReq) returns (stream PullBinlogResp) {}
}

本文我們主要介紹 RPC WriteBinlog 這個接口,Pump client 會通過這個接口寫 binlog 到 Pump。

WriteBinlogReq 裏面包含的 binlog event

// Binlog contains all the changes in a transaction, which can be used to reconstruct SQL statement, then export to
// other systems.
message Binlog {
    optional BinlogType    tp             = 1 [(gogoproto.nullable) = false];

    // start_ts is used in Prewrite, Commit and Rollback binlog Type.
    // It is used for pairing prewrite log to commit log or rollback log.
    optional int64         start_ts       = 2 [(gogoproto.nullable) = false];

    // commit_ts is used only in binlog type Commit.
    optional int64         commit_ts      = 3 [(gogoproto.nullable) = false];

    // prewrite key is used only in Prewrite binlog type.
    // It is the primary key of the transaction, is used to check that the transaction is
    // commited or not if it failed to pair to commit log or rollback log within a time window.
    optional bytes         prewrite_key   = 4;

    // prewrite_data is marshalled from PrewriteData type,
    // we do not need to unmarshal prewrite data before the binlog have been successfully paired.
    optional bytes         prewrite_value = 5;

    // ddl_query is the original ddl statement query.
    optional bytes         ddl_query      = 6;

    // ddl_job_id is used for DDL Binlog.
    // If ddl_job_id is setted, this is a DDL Binlog and ddl_query contains the DDL query.
    optional int64         ddl_job_id     = 7 [(gogoproto.nullable) = false];
}

TiDB 如何寫 binlog

TiDB 的事務採用 2-phase-commit 算法,一次事務提交會分爲 Prewrite 和 Commit 階段,有興趣的可以看下相關文章《TiKV 事務模型概覽,Google Spanner 開源實現》

大家可以先猜想一下 TiDB 是如何寫 binlog 的?

如果只寫一條 binlog 的話可行嗎?可以很容易想到,如果只寫一條 binlog 的話必須確保寫 binlog 操作和事務提交操作是一個原子操作,那麼就要基於事務模型再構建一個複雜的 2PC 模型,從複雜度方面考慮這個方案幾乎是不可行的。

實際上,在 TiDB 的實現中,TiDB 會每個階段分別寫一條 binlog, 即:Prewrite binlog 和 Commit binlog,下面會簡稱 P-binlog 和 C-binlog ,具體寫入流程如下:

這裏我們說的 P-binlog 和 C-binlog 都是通過 RPC WriteBinlog 接口寫入,對應着參數 WriteBinlogReq 裏面包含的 binlog event,只是字段有些區別:

  • P-binlog 對應的 tpPrewrite,C-binlog 的 tpCommit 或者 Rollback
  • 同個事務的 P-binlog 和 C-binlog 包含相同 start_ts
  • 只有 P-binlog 包含對應事務修改數據 prewrite_value
  • 只有 C-binlog 包含事務的 commit_ts

在 Prepare 的階段,TiDB 會把 Prewrite 的數據發到 TiKV,同時併發寫一條 P-binlog 到其中一個 Pump。 兩個操作全部成功後纔會進行 Commit 階段,所以我們提交事務時就可以確定 P-binlog 已經成功保存。寫 C-binlog 是在 TiKV 提交事務後異步發送的,告訴 Pump 這個事務提交了還是回滾了。

寫 binlog 對事務延遲的影響

  • Prepare 階段:併發寫 P-binlog 到 Pump 和 Prewrite data 到 TiKV,如果請求 Pump 寫 P-binlog 的速度快於寫 TiKV 的速度,那麼對延遲沒有影響。一般而言寫入 Pump 會比寫入 TiKV 更快。
  • Commit 階段:異步的去寫 C-binlog,對延遲也沒有影響。

寫 binlog 失敗

  1. 寫 P-binlog 失敗,那麼 transaction 不會 commit,不會對系統有任何影響。
  2. 寫 C-binlog 失敗,Pump 會等待最多 max transaction timeout 的時間(這是一個 TiDB/Pump 的配置,默認爲 10 分鐘),然後向 TiKV 去查詢 transaction 的提交狀態來補全 C-binlog,但是此時同步延遲也等於 max transaction timeout 。這種情況經常發生於 TiDB 進程重啓或者掛掉的場景。
  3. 寫 P-binlog 成功,但是 Prewrite 失敗,那麼也會和 2 類似。

Pump client 源碼

Pump client 的代碼維護在 pump_client,提供了 NewPumpsClient 方法來創建一個 Pump client 實例。Pump client 的主要功能就是維護所有 Pump 狀態(將 Pump 分爲 avaliable 和 unavailable 兩種狀態),以此爲依據將 TiDB 生成的 binlog 發送到合適的 Pump。爲此 Pump client 主要實現了以下幾個機制:

  1. watch etcd

    Pump 在運行時會將自己的狀態信息上報到 PD(etcd)中,並且定時更新自己的狀態。在創建 Pump client 的時候,會 首先從 PD(etcd)中獲取所有的 Pump 狀態信息,根據 Pump 狀態是否爲 Online 初步判斷 Pump 爲 avaliable 或者 unavailable。然後 Pump client 會 watch etcd 中的 Pump 狀態變更,及時更新內存中維護的 Pump 狀態。

  2. binlog 重試機制

    對於每個 Pump,在 Pump client 中都維護了一個變量 ErrNum 來記錄該 Pump 寫 binlog 的失敗次數,當 ErrNum 超過一定的閾值,則判斷 該 Pump 不可用,如果寫 binlog 成功,則 重置 ErrNum

  3. 發送探活請求

    在某些情況下,比如網絡抖動,可能會導致 Pump 寫 binlog 失敗,因此該 Pump 被 Pump client 判斷狀態爲 unavailable,但是當網絡恢復後,該 Pump 仍然可以提供寫 binlog 服務。Pump client 實現了 detect 機制,會定期向 unavailable 狀態的 Pump 發送探活請求,如果探活請求成功,則更新 Pump 狀態爲 avaliable。

爲了將 binlog 均勻地分發到所有 Pump,Pump client 使用 PumpSelector 爲每一個 binlog 選擇一個合適的 Pump,PumpSelector 是一個接口,提供 SetPumps 方法來設置可選的 Pump 列表,提供 Select 來爲 binlog 選擇 Pump。目前主要實現了 HashRound-Robin 兩種策略。

爲了提高 Pump client 的健壯性,binlog 寫失敗後會提供一定的重試,每個 Pump 可以重試寫多次,同時也會盡量嘗試所有的 Pump,這樣就可以保證部分 Pump 有故障或者臨時的網絡抖動也不影響 TiDB 寫 binlog,可以查看 WriteBinlog 瞭解具體實現方式。

小結

本文給大家介紹了 TiDB 如何通過 Pump client 寫 binlog 到 Pump,以及 binlog 的主要內容,後續我們將繼續介紹 Pump server 是對應如何處理相應請求的。

原文閱讀https://pingcap.com/blog-cn/tidb-binlog-source-code-reading-3/

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