不同場景下,如何選擇資料庫?
- 2019 年 11 月 24 日
- 筆記
我們做資料庫選型的時候首先要問:需求是誰提出的,也就是說誰選型?是負責採購的同學、 DBA 還是業務研發?
- 如果選型的是採購的同學,他們更注重成本,包括存儲方式、網路需求等;
- 如果選型的是 DBA 同學,他們關心運維成本、穩定性、性能等方面,具體如下:
首先是運維成本,包括監控告警是否完善、是否有備份恢復機制、升級和遷移的成本是否高、社區是否穩定、是否方便調優、排障是否簡易等;
其次是穩定性,包括是否支援數據多副本、服務高可用、多寫多活等;
第三是性能,包括延遲、QPS 以及是否支援更高級的分級存儲功能等;
第四是擴展性,如果業務的需求不確定,是否容易橫向擴展和縱向擴容;
最後是安全,需要符合審計要求,不容易出現 SQL 注入或拖庫情況。
- 除了採購和 DBA 之外,後台應用研發同樣會關注穩定性、性能、擴展性等問題,同時也非常關注資料庫介面是否便於開發,以及是否便於修改資料庫 schema 等問題。

接下來我們來看一下愛奇藝使用的資料庫類型。
1.MySQL, 互聯網業務必備系統;
2.TiDB,愛奇藝的 TiDB 實踐會有另外的具體介紹;
3.Redis, KV 資料庫,互聯網公司標配;
4.Couchbase,這個在愛奇藝用的比較多,但中國互聯網公司用的比較少,接下來的部分會詳細說明;
5.其他,比如 MongoDB、圖資料庫、自研 KV 資料庫 HiKV 等;
6.大數據分析相關係統,比如 Hive、Impala 等等。
可以看到愛奇藝的資料庫種類是很多的,這可能會造成業務開發不太清楚在他的業務場景下,應該選用哪種資料庫系統。所以,我們先對這些資料庫按照介面(SQL,NoSQL)和面向的業務場景(OLTP, OLAP)這兩個維度進行一個簡單的分類。
OLTP是支援 SQL 的這樣一類系統,例如 MySQL,一般支援事務不同的隔離級別, QPS 要求比較高,延時比較低,主要用於交易資訊和關鍵數據的存儲,比如訂單、VIP 資訊等。
NoSQL 資料庫,是一類針對特殊場景做優化的系統,schema 一般比較簡單,吞吐量較高、延遲較低,一般用作快取或者 KV 資料庫。
OLAP 大數據分析系統,包括 Clickhouse、Impala 等,一般支援 SQL、不支援事務,擴展性比較好,可以通過加機器增加數據的存儲量,響應延遲較長。
還有一類資料庫是比較中立的,在數據量比較小的時候性能比較好,在數據量較大或複雜查詢的時候性能也不差,一般通過不同的存儲引擎和查詢引擎來滿足不同的業務需求,我們把它叫做 HTAP,TiDB 就是這樣一種資料庫。

那麼,愛奇藝是如何使用這些資料庫的?
▌MySQL在愛奇藝的使用
MySQL 基本使用方式是 master-slave + 半同步,支援每周全備 + 每日增量備份。我們做了一些基本功能的增強,增強了數據恢復工具 Xtrabackup 的性能。之前遇到一個情況,我們有一個庫是 300G 數據,增量庫每天 70G 數據,總數據量 700G 左右。當時是只需要恢復一個表的數據,但該工具不支援單表恢復,且整庫恢復需要 5 個小時。針對這個情況我們排查了原因,發現在數據恢復的過程中需要進行多次寫盤的 IO 操作並且有很多串列操作,所以我們做了優化,例如刪減過程中的一些寫盤操作,減少落盤並將數據處理並行化,優化後整庫恢復耗時減少到 100 分鐘,而且可以直接恢復單表數據。
- 適配 DDL 和 DML 工具到內部系統,gh-ostt 和 oak-online-alter-table 在數據量大的時候會造成 master-slave 延時,所以我們在使用工具的時候也增加了延時上的考慮,實時探測 Master-Slave 庫之間延時的情況,如果延時較大會暫停工具的使用,恢復到正常水平再繼續。
- MySQL 高可用。Master-slave 加上半同步這種高可用方式不太完善,所以參照了 MHA 並進行改動,採用 master + agent 的方式。Agent 在每一個物理機上部署,可以監控這個物理機上的所有實例的狀態,周期性地向 master 發送心跳,Master 會實時監測各個 Agent 的狀態。如果 MySQL 故障,會啟動 Binlog 補償機制,並切換訪問域名完成 failover。考慮到資料庫跨機房跨地區部署的情況,MHA 的 master 也做了高可用設計,眾多 master 會通過 raft 組成一個 raft group,類似 TiDB 的 PD 模組。目前 MySQL failover 策略支援三種方式:同機房、同地域跨機房以及跨地域。
- 提高 MySQL 擴展能力,以提供更大容量的數據存儲。擴展方式有 SDK,例如開源的 ShardingSphere,在愛奇藝的使用也比較廣泛。另外就是 Proxy,開源的就更多了。但是 SDK 和 Proxy 使用的問題是支援的 SQL 語句簡單,擴容難度大,依賴較多且運維複雜,所以部分業務已經遷移至 TiDB。
- 審計。我們在 MySQL 上做了一個插件獲取全量 SQL 操作,後端打到 Kafka,下游再接入包括 Clickhouse 等目標端進行 SQL 統計分析。除此之外還有安全策略,包括主動探索是否有 SQL 注入及是否存在拖庫情況等,並觸發對應的告警。MySQL 審計插件最大的問題是如何降低對 MySQL 性能的影響,對此我們進行了一些測試,發現使用 General Log 對性能損耗較大,有 10%~20% 的降低。於是我們通過介面來獲取 MySQL 插件里的監控項,再把監控項放到 buffer 裡邊,用兩級的 RingBuffer 來保證數據的寫入不會有鎖資源競爭。在這個插件里再啟動一個執行緒,從 RingBuffer 里讀取數據並把數據打包寫到 FIFO 管道里。我們在每台 MySQL 的物理機里再啟動一個 Agent,從管道里阻塞地讀取數據發至 Kafka。優化後我們再次進行壓測,在每台機器上有 15 萬的更新、刪除或插入操作下不會丟失數據,性能損耗一般情況下小於 2%。
- 分級存儲。MySQL 里會存一些過程性的數據,即只需要讀寫最近一段時間存入的數據,過段時間這些數據就不需要了,需要進行定時清理。分級存儲就是在 MySQL 之上又用了其他存儲方式,例如 TiDB 或其他 TokuDB,兩者之間可以進行數據自動搬遷和自動歸檔,同時前端通過 SDK + Proxy 來做統一的訪問入口。這樣一來,業務的開發同學只需要將數據存入 MySQL 里,讀取時可能從後端接入的任意資料庫讀出。這種方式目前只是過渡使用,之後會根據 TiDB 的特性進行逐步遷移。

▌Redis在愛奇藝的使用
Redis 也是使用 master – slave 這種方式,由於網路的複雜性我們對 Sentinel 的部署進行了一些特殊配置,在多機房的情況下每個機房配置一定數量 Sentinel 來避免腦裂。
備份恢復方面介紹一個特殊場景,雖然 Redis 是一個快取,發現不少的同學會把它當做 KVDB 來使用,在某些情況下會造成數據的丟失。所以我們做了一個 Redis 實時備份功能,啟動一個進程偽裝成 Redis 的 Slave 實時獲取數據,再放到後端的 KV 存儲里,例如 ScyllaDB,如果要恢復就可以從 ScyllaDB 里把數據拉出來。我們在用 Redis 時最大的痛點就是它對網路的延遲或抖動非常敏感。如有抖動造成 Redis Master 超時,會由 Sentinel 重新選出一個新的節點成為 Master,再把該節點上的數據同步到所有 Slave 上,此過程中數據會放在 Master 節點的 Buffer 里,如果寫入的 QPS 很高會造成 Buffer 滿溢。如果 Buffer 滿後 RDB 文件還沒有拷貝過去,重建過程就會失敗。
基於這種情況,我們對 Redis 告警做了自動化優化,如有大量 master – slave 重建失敗,我們會動態調整一些參數,例如把 Buffer 臨時調大等, 此外我們還做了 Redis 集群的自動擴縮容功能。
我們在做 Redis 開發時如果是 Java 語言都會用到 Jedis。用 Jedis 訪問客戶端分片的 Redis 集群,如果某個分片發生了故障或者 failover,Jedis 就會對所有後端的分片重建連接。如果某一分片發生問題,整個 Redis 的訪問性能和 QPS 會大幅降低。針對這個情況我們優化了 Jedis,如果某個分片發生故障,就只針對這個分片進行重建。
在業務訪問 Redis 時我們會對 Master 綁定一個讀寫域名,多個從庫綁定讀域名。但如果我們進行 Master failover,會將讀寫域名從某舊 Master 解綁,再綁定到新 Master 節點上。DNS 本身有一個超時時間,所以資料庫做完 failover 後業務程式里沒有立刻獲取到新的 Master 節點的 IP 的話,有可能還會連到原來的機器上,造成訪問失敗。我們的解決方法是把 DNS 的 TTL 縮短,但對 DNS 服務又會造成很大的壓力,所以我們在 SDK 上提供 Redis 的名字服務 RNS,RNS 從 Sentinel 里獲取集群的拓撲和拓撲的變化情況,如果集群 failover,Sentinel 會接到通知,客戶端就可以通過 RNS 來獲取新的 Master 節點的 IP 地址。我們去掉域名,通過 IP 地址來訪問整個集群,屏蔽了 DNS 的超時,縮短了故障的恢復時間。SDK 上還做了一些功能,例如 Load Balance 以及故障檢測,比如某個節點延時較高的話會被臨時熔斷等。
客戶端分片的方式會造成 Redis 的擴容非常痛苦,如果客戶端已經進行了一定量的分片,之後再增加就會非常艱難。Redis 在 3.0 版本後會提供 Redis Cluster,因為功能受限在愛奇藝應用的不是很多,例如不支援顯示跨 DC 部署和訪問,讀寫只在主庫上等。我們某些業務場景下會使用 Redis 集群,例如資料庫訪問只發生在本 DC,我們會在 DC 內部進行 Cluster 部署。但有些業務在使用的過程中還是想做 failover,如果集群故障可以切換到其他集群。根據這種情況我們做了一個 Proxy,讀寫都通過它來進行。寫入數據時 Proxy 會做一個旁路,把新增的數據寫在 Kafka 里,後台啟用同步程式再把 Kafka 里的數據同步到其他集群,但存在一些限制,比如我們沒有做衝突檢測,所以集群間數據需要業務的同學做單元化。線上環境的 Redis Cluster 集群間場景跨 DC 同步 需要 50 毫秒左右的時間。

▌Cuchbase在愛奇藝的使用
Redis 雖然提供 Cluster 這種部署方式,但存在一些問題。所以數據量較大的時候(經驗是 160G),就不推薦 Redis 了,而是採用另一種存儲方式 Couchbase。
Couchbase 在中國互聯網公司用的比較少,一開始我們是把他當做一個 Memcached 來使用的,即純粹的快取系統。但其實它性能還是比較強大的,是一個分散式高性能的 KV 系統,支援多種存儲引擎 (bucket)。第一種是 Memcached bucket,使用方式和 Memcached 一樣為 KV 存儲,不支援數據持久化也沒有數據副本,如果節點故障會丟失數據;第二種是 Couchbase bucket,支援數據持久化,使用 Json 寫入,有副本,我們一般會在線上配置兩個副本,如果新加節點會對數據進行 rebalance,愛奇藝使用的一般是 Couchbase bucket 這種配置。Couchbase 數據的分布如右圖,數據寫入時在客戶端上會先進行一次哈希運算,運算完後會定位 Key 在哪一個 vBucket (相當於資料庫里的某個分片)。之後客戶端會根據 Cluster Map 發送資訊至對應的服務端,客戶端的 Cluster Map 保存的是 vBucket 和伺服器的映射關係,在服務端數據遷移的過程中客戶端的 Cluster Map 映射關係會動態更新,因此客戶端對於 服務端的 failover 操作不需要做特殊處理,但可能在 rebalance 過程中會有短暫的超時,導致的告警對業務影響不大。
Couchbase 在愛奇藝應用比較早,在還沒有 Redis Cluster 的時候就開始使用了。集群管理使用 erlang 語言開發,最大功能是進行集群間的複製,提供多種複製方式:單向、雙向、星型、環式、鏈式等。愛奇藝從最初的 1.8 版本使用到如今的 5.0 版本,正在調研 6.0。中間也遇到了很多問題,例如 NTP 時間配置出錯會導致崩潰,如果每個集群對外 XDCR 並發過高導致不穩定,同步方向變更會導致數據丟失等,會通過運維和一些外部工具來進行規避。
Couchbase 的集群是獨立集群,集群間的數據同步通過 XDCR,我們一般配置為雙向同步。對於業務來說,如果 Cluster 1 寫入, Cluster 2 不寫入,正常情況下客戶端會寫 Cluster 1。如果 Cluster 1 有故障,我們提供了一個 Java SDK,可以在配置中心把寫入更改到 Cluster 2,把原來到 Cluster 1 的連接逐步斷掉再與 Cluster 2 新建連接。這種集群 failover 的過程對於客戶端來說是相對透明和無感的。

▌愛奇藝自研資料庫HiKV的使用
Couchbase 雖然性能非常高,並且數據的存儲可以超過記憶體。但如果數據量超過記憶體 75% 這個閾值時性能就會下降地特別快。我們會把數據量控制在可用記憶體的範圍之內,當做記憶體資料庫使用。但是它的成本非常高,所以我們後面又開發了一個新的資料庫—— HiKV。
開發 HiKV 的目的是為了把一些對性能要求沒那麼高的 Couchbase 應用遷移到 HiKV 上。HiKV 基於開源系統 ScyllaDB,主要使用了其分散式資料庫的管理功能,增加了單機存儲引擎 HiKV。ScyllaDB 比較吸引人的是它宣稱性能高於 Cassandra 十倍,又完全兼容 Cassandra 介面,設計基本一致,可以視為 C++ 版 Cassandra 系統。ScyllaDB 性能的提升主要是使用了一些新的技術框架,例如 C++ 非同步框架 seastar,主要原理是在每台物理機的核上會 attach 一個應用執行緒,每個核上有自己獨立的記憶體、網路、IO 資源,核與核之間沒有數據共享但可以通訊,其最大的好處是記憶體訪問無鎖,沒有衝突過程。當一個數據讀或寫到達 ScyllaDB 的 server 時,會按照哈希演算法來判斷請求的 Key 是否是該執行緒需要處理的,如果是則本執行緒處理,否則會轉發到對應執行緒上去。除此之外,它還支援多副本、多數據中心、多寫多活,功能比較強大。
我們基於 SSD 做了一個 KV 存儲引擎。Key 放在記憶體里,Value 放在盤上的文件里,在讀和寫文件時,只需要在記憶體索引里定位,再進行一次盤的 IO 開銷就可以把數據讀出來,相比 ScyllaDB 原本基於 LSM Tree 的存儲引擎方式對 IO 的開銷較少。索引數據全部放在記憶體中,如果索引長度較長會限制單機可存儲的數據量,於是可通過開發定長的記憶體分布器,對於比較長的 Key 做摘要縮短長度至 20 位元組,採用紅黑樹索引限制每條記錄在記憶體里的索引長度至為 64 位元組。記憶體數據要定期做 checkpoint,客戶端要做限流、熔斷等。
HiKV 目前在愛奇藝應用範圍比較大,截至目前已經替換了 30% 的 Couchbase,有效地降低了存儲成本。

▌愛奇藝的資料庫運維管理
愛奇藝資料庫種類較多,如何高效地運維和管理這些資料庫也是經歷了不同的階段。
最初我們通過 DBA 寫腳本的方式管理,如果腳本出問題就找 DBA,導致了 DBA 特別忙碌。
第二個階段我們考慮讓大家自己去查問題的答案,於是在內部構建了一個私有雲,通過 Web 的方式展示資料庫運行狀態,讓業務同學可以自己去申請集群,簡單的操作也可以通過自服務平台實現,解放了 DBA。
一些需要人工處理的大型運維操作經常會造成一些人為故障,敲錯參數造成數據丟失等。於是在第三個階段我們把運維操作 Web 化,通過網頁點擊可以進行 90% 的操作。
第四個階段讓經驗豐富的 DBA 把自身經驗變成一些工具,把操作串起來形成一套工具,出問題時業務的同學可以自己通過網頁上的一鍵診斷工具去排查,自助進行處理。除此之外還會定期做預警檢查,對業務集群里潛在的問題進行預警報告;開發智慧客服,回答問題;通過監控的數據對實例打標籤,進行削峰填谷的智慧調度,提高資源利用率。

▌實用資料庫選型樹:快速進行資料庫選型
最後來說一些具體資料庫選型建議。這是 DBA 和業務一起,通過經驗得出來的一些結論。對於關係型資料庫的選型來說,可以從數據量和擴展性兩個維度考慮,再根據資料庫有沒有冷備、要不要使用 Toku 存儲引擎,要不要使用 Proxy 等等進行抉擇。

NoSQL 也是什麼情況下使用 master-slave,什麼情況下使用客戶端分片、集群、Couchbase、HiKV 等,我們內部自服務平台上都有這個選型樹資訊。
▌一些思考
在選型時先思考需求,判斷需求是否真實。可以從數據量、QPS、延時等方面考慮需求,但這些都是真實需求嗎?是否可以通過其他方式把這個需求消耗掉,例如在數據量大的情況下可以先做數據編碼或者壓縮,數據量可能就降下來了。不要把所有需求都推到資料庫層面,它其實是一個兜底的系統。
第二,對於某個資料庫系統或是某個技術選型我們應該考慮什麼?是因為熱門嗎?還是因為技術上比較先進?但是不是能真正地解決問題?如果你數據量不是很大的話就不需要選擇可以存儲大數據量的系統。
第三,當你放棄一個系統時真的是因為不好用嗎?還是沒有用好?放棄一個東西很難,但在放棄時最好有一個充分的理由,包括實測的結果。
第四,在需要自己開發資料庫時可以參考和使用一些成熟的產品,但不要盲目自研。
最後是開源,要有擁抱開源的態度。
作者:郭磊濤
來源:https://url.cn/5vombOO