TiDB 在摩拜單車的深度實踐及應用

做者介紹:呂磊,摩拜單車高級 DBA。git

1、業務場景

摩拜單車 2017 年開始將 TiDB 嘗試應用到實際業務當中,根據業務的不斷髮展,TiDB 版本快速迭代,咱們將 TiDB 在摩拜單車的使用場景逐漸分爲了三個等級:github

  • P0 級核心業務:線上核心業務,必須單業務單集羣,不容許多個業務共享集羣性能,跨 AZ 部署,具備異地災備能力。
  • P1 級在線業務:線上業務,在不影響主流程的前提下,能夠容許多個業務共享一套 TiDB 集羣。
  • 離線業務集羣:非線上業務,對實時性要求不高,能夠忍受分鐘級別的數據延遲。

本文會選擇三個場景,給你們簡單介紹一下 TiDB 在摩拜單車的使用姿式、遇到的問題以及解決方案。算法

2、訂單集羣(P0 級業務)

訂單業務是公司的 P0 級核心業務,之前的 Sharding 方案已經沒法繼續支撐摩拜快速增加的訂單量,單庫容量上限、數據分佈不均等問題愈發明顯,尤爲是訂單合庫,單表已是百億級別,TiDB 做爲 Sharding 方案的一個替代方案,不只完美解決了上面的問題,還能爲業務提供多維度的查詢。sql

2.1 訂單 TiDB 集羣的兩地三中心部署架構

圖 1  兩地三中心部署架構圖

<center>圖 1 兩地三中心部署架構圖</center>數據庫

整個集羣部署在三個機房,同城 A、同城 B、異地 C。因爲異地機房的網絡延遲較高,設計原則是儘可能使 PD Leader 和 TiKV Region Leader 選在同城機房(Raft 協議只有 Leader 節點對外提供服務),咱們的解決方案以下:安全

  • PD 經過 Leader priority 將三個 PD server 優先級分別設置爲 5 5 3。
  • 將跨機房的 TiKV 實例經過 label 劃分 AZ,保證 Region 的三副本不會落在同一個 AZ 內。
  • 經過 label-property reject-leader 限制異地機房的 Region Leader,保證絕大部分狀況下 Region 的 Leader 節點會選在同城機房 A、B。

2.2 訂單集羣的遷移過程以及業務接入拓撲

圖 2 訂單集羣的遷移過程以及業務接入拓撲圖

<center>圖 2 訂單集羣的遷移過程以及業務接入拓撲圖</center>網絡

爲了方便描述,圖中 Sharding-JDBC 部分稱爲老 Sharding 集羣,DBProxy 部分稱爲新 Sharding 集羣。session

  • 新 Sharding 集羣按照 order_id 取模經過 DBproxy 寫入各分表,解決數據分佈不均、熱點等問題。
  • 將老 Sharding 集羣的數據經過使用 DRC(摩拜自研的開源異構數據同步工具 Gravity)全量+增量同步到新 Sharding 集羣,並將增量數據進行打標,反向同步鏈路忽略帶標記的流量,避免循環複製。
  • 爲支持上線過程當中業務回滾至老 Sharding 集羣,須要將新 Sharding 集羣上的增量數據同步回老 Sharding 集羣,因爲寫回老 Sharding 集羣須要耦合業務邏輯,所以 DRC(Gravity)負責訂閱 DBProxy-Sharding 集羣的增量數放入 Kafka,由業務方開發一個消費 Kafka 的服務將數據寫入到老 Sharding 集羣。
  • 新的 TiDB 集羣做爲訂單合庫,使用 DRC(Gravity)重新 Sharding 集羣同步數據到 TiDB中。
  • 新方案中 DBProxy 集羣負責 order_id 的讀寫流量,TiDB 合庫做爲 readonly 負責其餘多維度的查詢。

2.3 使用 TiDB 遇到的一些問題

2.3.1 上線初期新集羣流量灰度到 20% 的時候,發現 TiDB coprocessor 很是高,日誌出現大量 server is busy 錯誤。數據結構

問題分析:架構

  • 訂單數據單表超過 100 億行,每次查詢涉及的數據分散在 1000+ 個 Region 上,根據 index 構造的 handle 去讀表數據的時候須要往這些 Region 上發送不少 distsql 請求,進而致使 coprocessor 上 gRPC 的 QPS 上升。
  • TiDB 的執行引擎是以 Volcano 模型運行,全部的物理 Executor 構成一個樹狀結構,每一層經過調用下一層的 Next/NextChunk() 方法獲取結果。Chunk 是內存中存儲內部數據的一種數據結構,用於減少內存分配開銷、下降內存佔用以及實現內存使用量統計/控制,TiDB 2.0 中使用的執行框架會不斷調用 Child 的 NextChunk 函數,獲取一個 Chunk 的數據。每次函數調用返回一批數據,數據量由一個叫 tidb_max_chunk_size 的 session 變量來控制,默認是 1024 行。訂單表的特性,因爲數據分散,實際上單個 Region 上須要訪問的數據並很少。因此這個場景 Chunk size 直接按照默認配置(1024)顯然是不合適的。

解決方案:

  • 升級到 2.1 GA 版本之後,這個參數變成了一個全局可調的參數,而且默認值改爲了 32,這樣內存使用更加高效、合理,該問題獲得解決。

2.3.2 數據全量導入 TiDB 時,因爲 TiDB 會默認使用一個隱式的自增 rowid,大量 INSERT 時把數據集中寫入單個 Region,形成寫入熱點。

解決方案:

  • 經過設置 SHARD_ROW_ID_BITS,能夠把 rowid 打散寫入多個不一樣的 Region,緩解寫入熱點問題:ALTER TABLE table_name SHARD_ROW_ID_BITS = 8;

2.3.3 異地機房因爲網絡延遲相對比較高,設計中賦予它的主要職責是災備,並不提供服務。曾經出現過一次大約持續 10s 的網絡抖動,TiDB 端發現大量的 no Leader 日誌,Region follower 節點出現網絡隔離狀況,隔離節點 term 自增,從新接入集羣時候會致使 Region 從新選主,較長時間的網絡波動,會讓上面的選主發生屢次,而選主過程當中沒法提供正常服務,最後可能致使雪崩。

問題分析:

  • Raft 算法中一個 Follower 出現網絡隔離的場景,以下圖所示。

圖 3  Raft 算法中,Follower 出現網絡隔離的場景圖

<center>圖 3 Raft 算法中,Follower 出現網絡隔離的場景圖</center>

  • Follower C 在 election timeout 沒收到心跳以後,會發起選舉,並轉換爲 Candidate 角色。
  • 每次發起選舉時都會把 term 加 1,因爲網絡隔離,選舉失敗的 C 節點 term 會不斷增大。
  • 在網絡恢復後,這個節點的 term 會傳播到集羣的其餘節點,致使從新選主,因爲 C 節點的日誌數據實際上不是最新的,並不會成爲 Leader,整個集羣的秩序被這個網絡隔離過的 C 節點擾亂,這顯然是不合理的。

解決方案:

  • TiDB 2.1 GA 版本引入了 Raft PreVote 機制,該問題獲得解決。
  • 在 PreVote 算法中,Candidate 首先要確認本身能贏得集羣中大多數節點的投票,纔會把本身的 term 增長,而後發起真正的投票,其餘節點贊成發起從新選舉的條件更嚴格,必須同時知足 :
    • 沒有收到 Leader 的心跳,至少有一次選舉超時。
    • Candidate 日誌足夠新。PreVote 算法的引入,網絡隔離節點因爲沒法得到大部分節點的許可,所以沒法增長 term,從新加入集羣時不會致使從新選主。

3、在線業務集羣(P1 級業務)

在線業務集羣,承載了用戶餘額變動、個人消息、用戶生命週期、信用分等 P1 級業務,數據規模和訪問量都在可控範圍內。產出的 TiDB Binlog 能夠經過 Gravity 以增量形式同步給大數據團隊,經過分析模型計算出用戶新的信用分按期寫回 TiDB 集羣。

圖 4  在線業務集羣拓撲圖

<center>圖 4 在線業務集羣拓撲圖</center>

4、數據沙盒集羣(離線業務)

數據沙盒,屬於離線業務集羣,是摩拜單車的一個數據聚合集羣。目前運行着近百個 TiKV 實例,承載了 60 多 TB 數據,由公司自研的 Gravity 數據複製中心將線上數據庫實時彙總到 TiDB 供離線查詢使用,同時集羣也承載了一些內部的離線業務、數據報表等應用。目前集羣的總寫入 TPS 平均在 1-2w/s,QPS 峯值 9w/s+,集羣性能比較穩定。該集羣的設計優點有以下幾點:

  • 可供開發人員安全的查詢線上數據。

  • 特殊場景下的跨庫聯表 SQL。

  • 大數據團隊的數據抽取、離線分析、BI 報表。

  • 能夠隨時按需增長索引,知足多維度的複雜查詢。

  • 離線業務能夠直接將流量指向沙盒集羣,不會對線上數據庫形成額外負擔。

  • 分庫分表的數據聚合。

  • 數據歸檔、災備。

圖 5  數據沙盒集羣拓撲圖

<center>圖 5 數據沙盒集羣拓撲圖</center>

4.1 遇到過的一些問題和解決方案

4.1.1 TiDB server oom 重啓

不少使用過 TiDB 的朋友可能都遇到過這一問題,當 TiDB 在遇到超大請求時會一直申請內存致使 oom, 偶爾由於一條簡單的查詢語句致使整個內存被撐爆,影響集羣的整體穩定性。雖然 TiDB 自己有 oom action 這個參數,可是咱們實際配置過並無效果。

因而咱們選擇了一個折中的方案,也是目前 TiDB 比較推薦的方案:單臺物理機部署多個 TiDB 實例,經過端口進行區分,給不穩定查詢的端口設置內存限制(如圖 5 中間部分的 TiDBcluster1 和 TiDBcluster2)。例:

[tidb_servers]
tidb-01-A ansible_host=$ip_address deploy_dir=/$deploydir1 tidb_port=$tidb_port1 tidb_status_port=$status_port1
tidb-01-B ansible_host=$ip_address deploy_dir=/$deploydir2 tidb_port=$tidb_port2 tidb_status_port=$status_port2  MemoryLimit=20G

實際上 tidb-01-Atidb-01-B 部署在同一臺物理機,tidb-01-B 內存超過閾值會被系統自動重啓,不影響 tidb-01-A

TiDB 在 2.1 版本後引入新的參數 tidb_mem_quota_query,能夠設置查詢語句的內存使用閾值,目前 TiDB 已經能夠部分解決上述問題。

4.1.2 TiDB-Binlog 組件的效率問題

你們平時關注比較多的是如何從 MySQL 遷移到 TiDB,但當業務真正遷移到 TiDB 上之後,TiDB 的 Binlog 就開始變得重要起來。TiDB-Binlog 模塊,包含 Pump&Drainer 兩個組件。TiDB 開啓 Binlog 後,將產生的 Binlog 經過 Pump 組件實時寫入本地磁盤,再異步發送到 Kafka,Drainer 將 Kafka 中的 Binlog 進行歸併排序,再轉換成固定格式輸出到下游。

使用過程當中咱們碰到了幾個問題:

  • Pump 發送到 Kafka 的速度跟不上 Binlog 產生的速度。

  • Drainer 處理 Kafka 數據的速度太慢,致使延時太高。

  • 單機部署多 TiDB 實例,不支持多 Pump。

其實前兩個問題都是讀寫 Kafka 時產生的,Pump&Drainer 按照順序、單 partition 分別進行讀&寫,速度瓶頸很是明顯,後期增大了 Pump 發送的 batch size,加快了寫 Kafka 的速度。但同時又遇到一些新的問題:

  • 當源端 Binlog 消息積壓太多,一次往 Kafka 發送過大消息,致使 Kafka oom。

  • 當 Pump 高速大批寫入 Kafka 的時候,發現 Drainer 不工做,沒法讀取 Kafka 數據。

和 PingCAP 工程師一塊兒排查,最終發現這是屬於 sarama 自己的一個 bug,sarama 對數據寫入沒有閾值限制,可是讀取卻設置了閾值:https://github.com/Shopify/sarama/blob/master/real_decoder.go#L88

最後的解決方案是給 Pump 和 Drainer 增長參數 Kafka-max-message 來限制消息大小。單機部署多 TiDB 實例,不支持多 Pump,也經過更新 ansible 腳本獲得瞭解決,將 Pump.service 以及和 TiDB 的對應關係改爲 Pump-8250.service,以端口區分。

針對以上問題,PingCAP 公司對 TiDB-Binlog 進行了重構,新版本的 TiDB-Binlog 再也不使用 Kafka 存儲 binlog。Pump 以及 Drainer 的功能也有所調整,Pump 造成一個集羣,能夠水平擴容來均勻承擔業務壓力。另外,原 Drainer 的 binlog 排序邏輯移到 Pump 來作,以此來提升總體的同步性能。

4.1.3 監控問題

當前的 TiDB 監控架構中,TiKV 依賴 Pushgateway 拉取監控數據到 Prometheus,當 TiKV 實例數量愈來愈多,達到 Pushgateway 的內存限制 2GB 進程會進入假死狀態,Grafana 監控就會變成圖 7 的斷點樣子:

圖 6 監控拓撲圖

<center>圖 6 監控拓撲圖</center>

圖 7 監控展現圖

<center>圖 7 監控展現圖</center>

目前臨時處理方案是部署多套 Pushgateway,將 TiKV 的監控信息指向不一樣的 Pushgateway 節點來分擔流量。這個問題的最終仍是要用 TiDB 的新版本(2.1.3 以上的版本已經支持),Prometheus 可以直接拉取 TiKV 的監控信息,取消對 Pushgateway 的依賴。

4.2 數據複製中心 Gravity (DRC)

下面簡單介紹一下摩拜單車自研的數據複製組件 Gravity(DRC)。

Gravity 是摩拜單車數據庫團隊自研的一套數據複製組件,目前已經穩定支撐了公司數百條同步通道,TPS 50000/s,80 線延遲小於 50ms,具備以下特色:

  • 多數據源(MySQL, MongoDB, TiDB, PostgreSQL)。
  • 支持異構(不一樣的庫、表、字段之間同步),支持分庫分表到合表的同步。
  • 支持雙活&多活,複製過程將流量打標,避免循環複製。
  • 管理節點高可用,故障恢復不會丟失數據。
  • 支持 filter plugin(語句過濾,類型過濾,column 過濾等多維度的過濾)。
  • 支持傳輸過程進行數據轉換。
  • 一鍵全量 + 增量遷移數據。
  • 輕量級,穩定高效,容易部署。
  • 支持基於 Kubernetes 的 PaaS 平臺,簡化運維任務。

使用場景:

  • 大數據總線:發送 MySQL Binlog,Mongo Oplog,TiDB Binlog 的增量數據到 Kafka 供下游消費。
  • 單向數據同步:MySQL → MySQL&TiDB 的全量、增量同步。
  • 雙向數據同步:MySQL ↔ MySQL 的雙向增量同步,同步過程當中能夠防止循環複製。
  • 分庫分表到合庫的同步:MySQL 分庫分表 → 合庫的同步,能夠指定源表和目標表的對應關係。
  • 數據清洗:同步過程當中,可經過 filter plugin 將數據自定義轉換。
  • 數據歸檔:MySQL→ 歸檔庫,同步鏈路中過濾掉 delete 語句。

Gravity 的設計初衷是要將多種數據源聯合到一塊兒,互相打通,讓業務設計上更靈活,數據複製、數據轉換變的更容易,可以幫助你們更容易的將業務平滑遷移到 TiDB 上面。該項目 已經在 GitHub 開源,歡迎你們交流使用。

5、總結

TiDB 的出現,不只彌補了 MySQL 單機容量上限、傳統 Sharding 方案查詢維度單一等缺點,並且其計算存儲分離的架構設計讓集羣水平擴展變得更容易。業務能夠更專一於研發而沒必要擔憂複雜的維護成本。將來,摩拜單車還會繼續嘗試將更多的核心業務遷移到 TiDB 上,讓 TiDB 發揮更大價值,也祝願 TiDB 發展的愈來愈好。

相關文章
相關標籤/搜索