新聞中心
近年來,我們專注于提供全系列企業級性能管理方案和相關的IT服務,在幫助用戶提高業務效率和整體生產力的同時,降低運營和運維成本。
返回列表
首頁 / 新聞資訊 / 公司動態
TiDB 高并發寫入常見熱點問題及規避方法
來源:   日期:2019-09-12

本文通過闡述一個高并發批量寫入數據到 TiDB 的典型場景中,TiDB 中常見的問題,給出一個業務的最佳實踐,避免業務在開發的時候陷入 TiDB 使用的 “反模式”。


面向的對象


本文主要面向對 TiDB 有一定了解的讀者,讀者在閱讀本文之前,推薦先閱讀講解 TiDB 原理的三篇文章(講存儲說計算談調度),以及 TiDB Best Practice


場景


高并發批量插入場景,通常存在于業務系統中的批量任務中,例如清算以及結算等業務。它存在以下顯著的特點:

  • 數據量大
  • 需要短時間內將歷史數據入庫
  • 需要短時間內讀取大量數據

這就對 TiDB 提出了一些挑戰:

  • 寫入/讀取能力是否可以線性水平擴展
  • 數據在持續大并發寫入,性能是否穩定不衰減


對于分布式數據庫來說,除了本身的基礎性能之外,最重要的就是充分利用所有節點能力,避免出現單個節點成為瓶頸。


TiDB 數據分布原理


如果要解決以上挑戰,需要從 TiDB 數據切分以及調度的原理開始講起。這里只是作簡單的說明,詳細請大家參見:談調度

TiDB 對于數據的切分,按 Region 為單位,一個 Region 有大小限制(默認 96M)。Region 的切分方式是范圍切分。每個 Region 會有多副本,每一組副本,稱為一個 Raft-Group。由 Leader 負責執行這塊數據的讀 & 寫(當然 TiDB 即將支持 Follower-Read)。Leader 會自動地被 PD 組件均勻調度在不同的物理節點上,用以均分讀寫壓力。

1.webp.jpg

圖 1 TiDB 數據概覽

只要業務的寫入沒有 AUTO_INCREMENT 的主鍵或者單調遞增的索引(也即沒有業務上的寫入熱點,更多細節參見 TiDB 正確使用方式)。從原理上來說,TiDB 依靠這個架構,是可以線性擴展讀寫能力,并且可以充分利用分布式的資源的。這一點上 TiDB 尤其適合高并發批量寫入場景的業務。


但是軟件世界里,沒有銀彈。具體的事情還需要具體分析。我們接下來就通過一些簡單的負載來探討 TiDB 在這種場景下,需要如何被正確的使用,才能達到此場景理論上的最佳性能。


簡單的例子


有一張簡單的表:

CREATE TABLE IF NOT EXISTS TEST_HOTSPOT(
     id                   BIGINT PRIMARY KEY,
     age                INT,
     user_name  VARCHAR(32),
     email   VARCHAR(128)
)


這個表結構非常簡單,除了 id 為主鍵以外,沒有額外的二級索引。寫入的語句如下,id 通過隨機數離散生成:

INSERT INTO TEST_HOTSPOT(id, age, user_name, email) values(%v, %v, '%v', '%v');

負載是短時間內密集地執行以上寫入語句。


到目前為止,似乎已經符合了我們上述提到的 TiDB 最佳實踐了,業務上沒有熱點產生,只要我們有足夠的機器,就可以充分利用 TiDB 的分布式能力了。要驗證這一點,我們可以在實驗環境中試一試(實驗環境部署拓撲是 2 個 TiDB 節點,3 個 PD 節點,6 個 TiKV 節點,請大家忽略 QPS,這里的測試只是為了闡述原理,并非 benchmark):

2.webp.jpg

圖 2 監控截圖


客戶端在短時間內發起了 “密集” 的寫入,TiDB 收到的請求是 3K QPS。如果沒有意外的話,壓力應該均攤給 6 個 TiKV 節點。但是從 TiKV 節點的 CPU 使用情況上看,存在明顯的寫入傾斜(tikv - 3 節點是寫入熱點):

3.webp.jpg

圖 3 監控截圖

4.webp.jpg

圖 4 監控截圖

Raft store CPU 代表 raftstore 線程的 CPU 使用率,通常代表著寫入的負載,在這個場景下 tikv-3 是 raft 的 leader,tikv-0 跟 tikv-1 是 raft 的 follower,其他的 tikv 節點的負載幾乎為空。


從 PD 的監控中也可以印證這一點:

5.webp.jpg

圖 5 監控截圖


反直覺的原因


上面這個現象是有一些違反直覺的,造成這個現象的原因是:剛創建表的時候,這個表在 TiKV 只會對應為一個 Region,范圍是:

[CommonPrefix + TableID, CommonPrefix + TableID + 1)

對于在短時間內的大量寫入,它會持續寫入到同一個 Region。


6.png

圖 6 TiKV Region 分裂流程

上圖簡單描述了這個過程,持續寫入,TiKV 會將 Region 切分。但是由于是由原 Leader 所在的 Store 首先發起選舉,所以大概率下舊的 Store 會成為新切分好的兩個 Region 的 Leader。對于新切分好的 Region 2,3。也會重復之前發生在 Region 1 上的事情。也就是壓力會密集地集中在 TiKV-Node 1 中。


在持續寫入的過程中, PD 能發現 Node 1 中產生了熱點,它就會將 Leader 均分到其他的 Node 上。如果 TiKV 的節點數能多于副本數的話,還會發生 Region 的遷移,盡量往空閑的 Node 上遷移,這兩個操作在插入過程,在 PD 監控中也可以印證:

7.webp.jpg

圖 7 監控截圖
在持續寫入一段時間以后,整個集群會被 PD 自動地調度成一個壓力均勻的狀態,到那個時候才會真正利用整個集群的能力。對于大多數情況來說,這個是沒有問題的,這個階段屬于表 Region 的預熱階段。

但是對于高并發批量密集寫入場景來說,這個卻是應該避免的。

那么我們能否跳過這個預熱的過程,直接將 Region 切分為預期的數量,提前調度到集群的各個節點中呢?


解決方法


TiDB 在 v3.0.x 版本以及 v2.1.13 以后的版本支持了一個新特性叫做 Split Region。這個特性提供了新的語法:

SPLIT TABLE table_name [INDEX index_name] BETWEEN (lower_value) AND (upper_value) REGIONS region_num

SPLIT TABLE table_name [INDEX index_name] BY (value_list) [, (value_list)]


讀者可能會有疑問,為何 TiDB 不自動將這個切分動作提前完成?大家先看一下下圖:

8.webp.jpg

圖 8 Table Region Range


從圖 8 可以知道,Table 行數據 key 的編碼之中,行數據唯一可變的是行 ID (rowID)。在 TiDB 中 rowID 是一個 Int64 整形。那么是否我們將 Int64 整形范圍均勻切分成我們要的份數,然后均勻分布在不同的節點就可以解決問題呢?


答案是不一定,需要看情況,如果行 id 的寫入是完全離散的,那么上述方式是可行的。但是如果行 id 或者索引是有固定的范圍或者前綴的。例如,我只在 [2000w, 5000w) 的范圍內離散插入,這種寫入依然是在業務上沒有熱點的,但是如果按上面的方式切分,那么就有可能在開始也還是只寫入到某個 Region。


作為通用的數據庫,TiDB 并不對數據的分布作假設,所以開始只用一個 Region 來表達一個表,等到真實數據插入進來以后,TiDB 自動地根據這個數據的分布來作切分。這種方式是較通用的。


所以 TiDB 提供了 Split Region 語法,來專門針對短時批量寫入場景作優化,下面我們嘗試在上面的例子中用以下語句提前切散 Region,再看看負載情況。


由于測試的寫入是在正數范圍內完全離散,所以我們可以用以下語句,在 Int64 空間內提前將表切散為 128 個 Region:

SPLIT TABLE TEST_HOTSPOT BETWEEN (0) AND (9223372036854775807) REGIONS 128;


切分完成以后,可以通過 SHOW TABLE test_hotspot REGIONS; 語句查看打散的情況,如果 SCATTERING 列值全部為 0,代表調度成功。


也可以通過 table-regions.py 腳本,查看 Region 的分布,已經比較均勻了:

[[email protected] scripts]# python table-regions.py --host 172.16.4.3 --port 31453 test test_hotspot
[RECORD - test.test_hotspot] - Leaders Distribution:
 total leader count: 127
 store: 1, num_leaders: 21, percentage: 16.54%
 store: 4, num_leaders: 20, percentage: 15.75%
 store: 6, num_leaders: 21, percentage: 16.54%
 store: 46, num_leaders: 21, percentage: 16.54%
 store: 82, num_leaders: 23, percentage: 18.11%
 store: 62, num_leaders: 21, percentage: 16.54%


我們再重新運行插入負載:

9.webp.jpg

圖 9 監控截圖

10.webp.jpg

圖 10 監控截圖

11.webp.jpg

圖 11 監控截圖


可以看到已經消除了明顯的熱點問題了。
當然,這里只是舉例了一個簡單的表,還有索引熱點的問題。如何預先切散索引相關的 Region?
這個問題可以留給讀者,通過 Split Region 文檔 可以獲得更多的信息。



 

更復雜一些的情況

如果表沒有主鍵或者主鍵不是 int 類型,用戶也不想自己生成一個隨機分布的主鍵 ID,TiDB 內部會有一個隱式的 _tidb_rowid 列作為行 id。在不使用 SHARD_ROW_ID_BITS 的情況下,_tidb_rowid 列的值基本上也是單調遞增的,此時也會有寫熱點存在。(查看什么是 SHARD_ROW_ID_BITS

要避免由 _tidb_rowid 帶來的寫入熱點問題,可以在建表時,使用 SHARD_ROW_ID_BITS  和 PRE_SPLIT_REGIONS 這兩個建表 option(查看什么是 PRE_SPLIT_REGIONS)。

SHARD_ROW_ID_BITS 用來把 _tidb_rowid 列生成的行 ID 隨機打散,pre_split_regions 用來在建完表后就預先 split region。注意:pre_split_regions 必須小于等于 shard_row_id_bits。

示例:

create table t (a int, b int) shard_row_id_bits = 4 pre_split_regions=·3;
  • SHARD_ROW_ID_BITS = 4 表示 tidb_rowid 的值會隨機分布成 16 (16=2^4) 個范圍區間。
  • pre_split_regions=3 表示建完表后提前 split 出 8 (2^3) 個 region。

在表 t 開始寫入后,數據寫入到提前 split 好的 8 個 region 中,這樣也避免了剛開始建表完后因為只有一個 region 而存在的寫熱點問題。


參數配置


 

關閉 TiDB 的 Latch 機制


TiDB 2.1 版本中在 SQL 層引入了 latch 機制,用于在寫入沖突比較頻繁的場景中提前發現事務沖突,減少 TiDB 跟 TiKV 事務提交時寫寫沖突導致的重試。對于跑批場景,通常是存量數據,所以并不存在事務的寫入沖突。可以把 TiDB 的 latch 關閉,以減少細小內存對象的分配:

[txn-local-latches]
enabled = false







12.webp.jpg


TiDB | 國產數據庫

趨勢所驅  擁抱開源

簡單易用  面向未來


識別二維

查看更多課程詳情


電話咨詢:18501287439(同微信號)


TiDB相關資料


送驚喜 | TiDB DBA 官方指定培訓·線下班限時特惠

東方龍馬與PingCAP大學正式達成合作,共同助力開源數據庫生態建設

初級/高級 TiDB DBA線下培訓報名開始了!

TiDB熱火朝天,你愿跟隨時代的腳步一起前進嗎?

精華 | 5分鐘了解TiDB (炙手可熱的寵兒)




|  北京    |    上海    |   廣州    |   成都    |


4008-906-960


4008-906-960

全國免費咨詢電話
  • 官方微博
  • 官方微信
Copyright 1998-2016 版權所有 北京東方龍馬軟件發展有限公司 京ICP備14000200號-1
上海时时彩加盟