作者介紹:呂磊,摩拜單車高級 DBA
摩拜單車 2017 年開始將 TiDB 嘗試應用到實際業務當中,根據業務的不斷發展,TiDB 版本快速迭代,我們將 TiDB 在摩拜單車的使用場景逐漸分為了三個等級:
本文會選擇三個場景,給大家簡單介紹一下 TiDB 在摩拜單車的使用姿勢、遇到的問題以及解決方案。
訂單業務是公司的 P0 級核心業務,以前的 Sharding 方案已經無法繼續支撐摩拜快速增長的訂單量,單庫容量上限、數據分布不均等問題愈發明顯,尤其是訂單合庫,單表已經是百億級別,TiDB 作為 Sharding 方案的一個替代方案,不僅完美解決了上面的問題,還能為業務提供多維度的查詢。
2.1 訂單 TiDB 集群的兩地三中心部署架構
整個集群部署在三個機房,同城 A、同城 B、異地 C。由於異地機房的網路延遲較高,設計原則是盡量使 PD Leader 和 TiKV Region Leader 選在同城機房(Raft 協議只有 Leader 節點對外提供服務),我們的解決方案如下:
2.2 訂單集群的遷移過程以及業務接入拓撲
為了方便描述,圖中 Sharding-JDBC 部分稱為老 Sharding 集群,DBProxy 部分稱為新 Sharding 集群。
2.3 使用 TiDB 遇到的一些問題
2.3.1 上線初期新集群流量灰度到 20% 的時候,發現 TiDB coprocessor 非常高,日誌出現大量 server is busy 錯誤。
問題分析:
Next/NextChunk()
NextChunk
tidb_max_chunk_size
解決方案:
2.3.2 數據全量導入 TiDB 時,由於 TiDB 會默認使用一個隱式的自增 rowid,大量 INSERT 時把數據集中寫入單個 Region,造成寫入熱點。
SHARD_ROW_ID_BITS
2.3.3 異地機房由於網路延遲相對比較高,設計中賦予它的主要職責是災備,並不提供服務。曾經出現過一次大約持續 10s 的網路抖動,TiDB 端發現大量的 no Leader 日誌,Region follower 節點出現網路隔離情況,隔離節點 term 自增,重新接入集群時候會導致 Region 重新選主,較長時間的網路波動,會讓上面的選主發生多次,而選主過程中無法提供正常服務,最後可能導致雪崩。
問題分析:Raft 演算法中一個 Follower 出現網路隔離的場景,如下圖所示。
在線業務集群,承載了用戶餘額變更、我的消息、用戶生命周期、信用分等 P1 級業務,數據規模和訪問量都在可控範圍內。產出的 TiDB Binlog 可以通過 Gravity 以增量形式同步給大數據團隊,通過分析模型計算出用戶新的信用分定期寫回 TiDB 集群。
數據沙盒,屬於離線業務集群,是摩拜單車的一個數據聚合集群。目前運行著近百個 TiKV 實例,承載了 60 多 TB 數據,由公司自研的 Gravity 數據複製中心將線上資料庫實時匯總到 TiDB 供離線查詢使用,同時集群也承載了一些內部的離線業務、數據報表等應用。目前集群的總寫入 TPS 平均在 1-2w/s,QPS 峰值 9w/s+,集群性能比較穩定。該集群的設計優勢有如下幾點:
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-A、tidb-01-B 部署在同一台物理機,tidb-01-B 內存超過閾值會被系統自動重啟,不影響 tidb-01-A。
tidb-01-A
tidb-01-B
TiDB 在 2.1 版本後引入新的參數 tidb_mem_quota_query,可以設置查詢語句的內存使用閾值,目前 TiDB 已經可以部分解決上述問題。
tidb_mem_quota_query
4.1.2 TiDB-Binlog 組件的效率問題
大家平時關注比較多的是如何從 MySQL 遷移到 TiDB,但當業務真正遷移到 TiDB 上以後,TiDB 的 Binlog 就開始變得重要起來。TiDB-Binlog 模塊,包含 Pump&Drainer 兩個組件。TiDB 開啟 Binlog 後,將產生的 Binlog 通過 Pump 組件實時寫入本地磁碟,再非同步發送到 Kafka,Drainer 將 Kafka 中的 Binlog 進行歸併排序,再轉換成固定格式輸出到下游。
使用過程中我們碰到了幾個問題:
其實前兩個問題都是讀寫 Kafka 時產生的,Pump&Drainer 按照順序、單 partition 分別進行讀&寫,速度瓶頸非常明顯,後期增大了 Pump 發送的 batch size,加快了寫 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 監控就會變成下圖的斷點樣子:
目前臨時處理方案是部署多套 Pushgateway,將 TiKV 的監控信息指向不同的 Pushgateway 節點來分擔流量。這個問題的最終還是要用 TiDB 的新版本(2.1.3 以上的版本已經支持),Prometheus 能夠直接拉取 TiKV 的監控信息,取消對 Pushgateway 的依賴。
4.2 數據複製中心 Gravity (DRC)
下面簡單介紹一下摩拜單車自研的數據複製組件 Gravity(DRC)。
Gravity 是摩拜單車資料庫團隊自研的一套數據複製組件,目前已經穩定支撐了公司數百條同步通道,TPS 50000/s,80 線延遲小於 50ms,具有如下特點:
使用場景:
Gravity 的設計初衷是要將多種數據源聯合到一起,互相打通,讓業務設計上更靈活,數據複製、數據轉換變的更容易,能夠幫助大家更容易的將業務平滑遷移到 TiDB 上面。該項目已經在 GitHub 開源,歡迎大家交流使用:https://github.com/moiot/gravity。
TiDB 的出現,不僅彌補了 MySQL 單機容量上限、傳統 Sharding 方案查詢維度單一等缺點,而且其計算存儲分離的架構設計讓集群水平擴展變得更容易。業務可以更專註於研發而不必擔心複雜的維護成本。未來,摩拜單車還會繼續嘗試將更多的核心業務遷移到 TiDB 上,讓 TiDB 發揮更大價值,也祝願 TiDB 發展的越來越好。
更多案例閱讀: