吳鏑:TiDB 在今日頭條的實踐

本文整理自今日頭條數據庫中間件/分佈式數據庫負責人吳鏑( 知乎 ID:吳鏑)在TiDB DevCon2018 上的分享內容。

TiDB 主要應用在今日頭條核心 OLTP 系統 - 對象存儲系統中,存儲其中一部分元數據,支持頭條圖片和視頻相關業務,好比抖音等。數據庫

現在(數據截至發文),TiDB 支撐着今日頭條 OLTP 系統裏 QPS 比較高的場景:集羣容量約幾十 T,平常 QPS 峯值會達到幾十萬。後端

爲何咱們須要用 TiDB

今日頭條內部有一些業務數據量很是大,以前用的 MySQL 的單機盤是大概 2.8T 的 SSD 盤。咱們作對象存儲。由於頭條不但作視頻,還作圖片,這些視頻和圖片當中基本上都是用咱們自研的 S3 存儲系統,這種存儲系統須要一個元數據,好比一個圖片存下來,它存在 S3 系統的哪一個機器、哪一個文件、哪一個偏移裏面的數據,還有好比一個大的視頻,S3 會把它切成不少小的視頻片斷,每個分片的位置,都會存在元數據裏面。app

用 TiDB 以前,元數據是存在 MySQL 裏的一個 2.8TB 的盤,由於增加的特別快,因此致使磁盤不夠用,只能用分庫分表的方案。咱們之前用的的分庫分表方案是 MyCAT。但用這個方案的過程當中咱們有遇到了一些問題,好比丟數據。某一個數據我 commit 了以後,最後發現這個數據丟了。負載均衡

再就是鏈接的問題,目前頭條作分片是大概固定分 100 個片。若是你的業務是須要分庫分表,那你這邊搞 101 個分片,這樣有些業務,他用了一個分片鍵,用分片鍵來作查詢,那可能中間件只用一個鏈接就能夠找到相關數據。但有些業務,確實有不帶分片鍵的請求。會致使 select 語句過來的時候,下面會建 101 個對後端的鏈接,也就是說,由於有鏈接的限制,有一個沒有帶分片鍵的這種請求過來以後, MyCAT 能夠啓 101 個鏈接到後面的每個 MySQL 庫。那這樣的話,有時候我給它 5 萬個鏈接,他一會兒就把一百萬用掉了。這樣會致使它在非分片鍵的 select 請求,它鏈接速度消耗很是快,常常在業務這邊會拋出說,鏈接數不夠。分佈式

頭條的數據庫主要用的是 MySQL 和 MongoDB,相對比較單一,所咱們也想多嘗試一些其餘的數據庫。工具

主要使用場景

目前,TiDB 主要在如下兩個場景下使用:性能

首先是 OLTP 的場景,也就是大數據量的場景,咱們不只僅是考慮到延時,而是考慮到數據量單機裝不下,須要擴展性;大數據

還有 OLAP 場景,有些用戶,他用的是 Hive 或者 Tableau,而後用的過程當中發現,由於後面都是接 MySQL,作一些 OLAP 的方式查詢就比較慢。後來公司在推廣 TiDB,因此就接了一些 OLAP 的業務場景。優化

頭條的自研對象存儲系統元數據量很是大,並且增加很是快。以其中最大的一個集羣舉例:該集羣有兩種方式,一是分片信息最先是用 MySQL 的。若是想用 TiDB 的話,可能先得把 TiDB 作了 MySQL 的備,用 TiDB 提供的 syncer 來同步數據,有些讀請求咱們能夠從 MySQL 上切到 TiDB 上來。ui

咱們用了一段時間,以爲 TiDB 其實挺穩定的。而後,公司會有這種需求,好比說忽然接了一個元旦的活動,這個時候上傳的圖片就比較多,數據增加的就太大了,這種活動中 S3 系統壓力比較大。咱們 MySQL 的單盤基本上穩定的在 2.0TB 以上(盤總記 2.8TB),對此咱們就只能刪數據(一些很老的數據),跟業務部門溝通說,這個數據不要了,從 MySQL 的單盤裏刪掉,經過這種方式來支撐。

但即便這麼作,單盤仍是扛不住如今數據增加的需求。而後當時就想幹脆激進點,把一些寫進來後當即就讀、而且之後都不會讀的一些流量切到 TiDB 裏。由於 S3 存儲分不少 bucket ,作活動的人就去新建一些 bucket, 這些 bucket 的元數據就直接存在 TiDB 裏面,就不存 MySQL 了。

這兩個 case,就是目前在頭條的 OLAP 和 OLTP 裏數據流量最大、QPS 最大的一個場景。

集羣部署狀態

關於部署,咱們把 TiDB 和 PD 部在一塊兒,都是 3 個。TiKV 咱們一共是用了幾十臺的機器。CPU 是 40 個虛擬的 CPU,256G 的內存。

目前平均值 QPS 在十幾萬,用了 3 個 TiDB,3 個 TiDB 總的鏈接數加起來大概 14K,而後 Latency 的 pct99 小於 60ms。這其實都屬於挺高峯時期的數據了,作活動的時候 QPS 會達到幾十萬。

與 MySQL 的延時對比

在使用 TiDB 過程當中,咱們也比較了一下 TiDB 和 MySQL 的延時:

第一條線就是 MySQL 的延時,pct99 的,下面的黑線是 TiDB 的延時。能夠看到,在 MySQL 的數據量很是大的狀況下,TiDB 是明顯 Latency 更優的,雖說它用的機器會稍微多點。

一些使用中的吐槽和經驗

使用的過程當中咱們也碰到了一些槽點,這些槽點 TiDB 如今的版本已經基本都解決了。

第一個就是直方圖。你們知道基於 CBO 的這種優化器,確定要用一些統計信息,TiDB 在以前的版本里對直方圖的統計信息的更新沒有作到很及時,致使我拿了一個 SQL 選執行計劃的時候我會選錯。好比說我能夠選一個索引,可是實際上,由於這個更新信息不實時,因此它可能會作全表掃描。

你們之後發現這種你能夠用 explain 這個命令看執行計劃,若是有這樣的問題就能夠用 analyze 這個命令,他能夠把一張表統計信息給更新,更新以後再一次執行 SQL 語句,你會發現他的執行計劃已經變了。

第二個就是 raft leader。由於你們都知道,每一個 region 是一個 raft ,TiDB 有一個監控指標,給出每一個機器上有多少個 raft leader。當咱們數據量跑到 10TB+,大概 20TB 的時候,會發現這個 raft leader 頻繁掉線。掉線的緣由主要是因爲作 region 遷移的時候,好比你後邊作遷移或者作負載均衡,會把 RocksDB 裏面一個 range 的數據發到你要遷移的目標機器上面去。發過去了以後,目標端至關於要把 SST 文件 load 到 RocksDB 裏,這個過程當中,因爲 RocksDB 實現的一個問題,致使把 SST 加到 RocksDB 的裏面去的這個過程花費了大概 30 到 40 秒,正常狀況下可能就毫秒級或者 1 秒。RocksDB 實現 ingest file 的時候,它打開了一些其實不須要打開的文件。由於 LevelDB、RocksDB 有不少層,把一個 file 給 ingest 進去的時候其實你要和一些 overlap 的數據作合併,由於它的實現問題,致使有一些沒有必要去 touch 的 SST 它都會去 touch,會產生大量 IO 。由於咱們數據量比較大, SST 就很是多,因此在數據量很是大的狀況下就會踩到這個坑。

而後,RocksDB ingest 一個文件時間過長,致使 Raft 的心跳就斷了。由於 Raft 協議要維持你的 lease,你要發心跳包,這個時候心跳包都給堵在後面,由於前面 ingest file 時間太長了。而後 Raft leader 就掉,掉了之後不少讀寫請求就會有問題。

第三個是大量的短連接。咱們的業務使用數據庫的時候,常常建了很是多短連接。由於大部分業務都是不大會使用數據庫的,它也不知道要設置鏈接池,idle connection 這種東西。因此常常用完一個鏈接後就關掉。這種大量的短連接最後打到 TiDB,TiDB 鏈接創建了以後要去查一個 System 的變量,這些變量在 TiDB 裏面是存在某幾個 TiKV 實例裏面的,那若是有大量短連接,這些短連接一上來,就會去查這些系統變量,恰好這些系統變量就聚在幾臺機器上面,致使說這幾臺機器就負載特別大。而後就會報警讀請求堆積。TiKV 使用的是線程模型,請求過來以後,丟到隊列裏面去。而後線程再拿出來處理。如今 PingCAP 也在作優化,把這些 Cache 在 TiDB 這個進程裏面。

第四點,嚴格來講這不算是 TiKV 的問題,算是 prometheus 的客戶端有問題。咱們當時遇到這麼一個狀況:部署 prometheus 的這個機器宕掉了,重啓以後,咱們會發現不少 TiKV 的監控信息都沒有上報。後來查的時候發現壓根 TiKV 這臺機器都沒有到 prometheus 這臺機器的鏈接。因此咱們就以爲 prometheus 這邊客戶端實現有問題。

第五個問題就是 Row id 的打散。這個問題正好是咱們這邊碰到的一個性能上的問題。由於 TiDB 存儲數據是這麼存的:我要插入一行數據,他會有兩行,第一行是索引,索引是 Key ,而後 value 是 row id;第二行是 row id 是 Key,value 是整行的數據,至關於第二行有點像彙集索引這種東西。可是這個彙集索引的 Key 是 row id。原來的版本實現上是說這個 row id 是個遞增了,因此這種就致使無論你插入什麼數據,這個 row id 都是遞增的,由於 row id 一遞增,這些數據都會打到一個 TiKV 的一個 region 上面。由於個人 TiKV 是一個有序的 Map,因此說 row id 若是遞增的話,確定你們插入的時候都是打到一個 TiKV 上面。咱們當時業務的壓力比較大,致使客戶發現他把這個業務的機器實例數給擴容上去以後,會發現這個 insert 的 TPS 大概也就在兩萬,一行大概就一百多個字節吧,你再怎麼加他上不去了,也就是說 insert 的這個 QPS 上不去了。

這一點 TiDB 新版本的方案就是,row id 不是單調遞增,而是把 row id 打的很散,這種方案性能會比較好,沒有熱點。

最後這個問題,由於 TiDB 這種事務模型,是須要拿一個事務版本,這個事務版本在 TiDB 裏面是一個時間戳,而且這個時間戳是由 PD 這個組件來管理的。至關於每個事務基本上連上來以後,它都要去訪問 PD 這個組件拿時間戳。其實作 rpc 的時候拿時間戳延遲不會太長,也就是個位數毫秒級。但由於 TiDB 是 Go 寫的,有調度開銷。從 PD 拿回來一堆時間戳的 goroutine 把這堆時間戳發放給執行事務的一堆 goroutine 很慢,在連接數和壓力都比較大的時候,大概有 30 毫秒左右的延時。可能調 rpc 的時候也就大概須要 1 毫秒,不到 2 毫秒。但因爲 Go 的開銷,能把這個延時翻幾倍。

以上這些講的都是 TiDB 在頭條作 OLTP 的場景下,碰到的一些主要的問題,這些問題大部分如今已經修復。

頭條在 OLAP 上的一些應用

在 OLAP 的場景下內容就比較少了。前面的一些業務喜歡用 tableau 這種客戶端後面鏈接 MySQL,這就太慢了。能夠用 syncer 把一些數據從 MySQL 同步到 TiDB。

這就可能碰到一個問題:咱們公司有一個組件,是會把 Hive 的數據批量的同步到 MySQL 的一個工具,不少作數據分析的同窗就會把 Hive 裏的數據同步到 TiDB。可是這個工具產生的事務很是大,而 TiDB 自己對事務的大小是有一個限制的。

此時,把下面這兩個配置項打開以後,TiDB 內部會把這種大的事務切成不少小的事務,就沒有這個問題:

  • set @@tidb_batch_insert =ON
  • set @@tidb_batch_delete = ON

有事務大小的限制主要在於 TiKV 的實現用了一致性協議。對於任何一個分佈式數據庫,若是你要用一致性協議去作這種複製,確定要避免很是大的事務。因此這個問題不是 TiDB 的問題。基本上,每一個想要作分佈式數據庫的確定都會碰到這麼一個問題。在 OLAP 場景下,你們對數據的事務性要求沒那麼高,因此把這個配置項打開沒什麼問題。

這就是頭條在 OLAP 上的一些應用:好比說 ugc 點擊量,app crash 的需求是客戶端請求掛掉以後,要打一個 log 在 TiDB 的集羣裏面。druid 這個 OLAP 這個引擎,他會有 MySQL 的數據作元數據,有些人就把這個元數據存在 TiDB 上了,還有一些問答業務,也是把一些相關的數據放到 TiDB 上。

相關文章
相關標籤/搜索