愛奇藝的 "數據庫" 選型到底有多牛逼?

點擊上方 "程序員小樂"關注, 星標或置頂一起成長

每天凌晨00點00分, 第一時間與你相約


每日英文

Say not all that you know, believe not all that you hear.

你所知道的不要全說,你所聽到的不要全信。


每日掏心話

做人不要太玻璃心,不要別人一條信息沒回,就覺得自己做錯了什麼,不要被人一句”呵呵“,就覺得對方是討厭自己。玻璃心,想太多,什麼事都對號入座,何必那麼累。




來源:asktug.com/t/topic/1396?utm_source=wechat&utm_medium=dbapluspostaiqiyi

愛奇藝的

程序員小樂(ID:study_tech)第 766 次推文 圖片來自 Pexels


往日回顧:鍾南山再談疫情:確診數增加會持續但不會太久!目前病死率2.3%-2.4%比非典低


正文


我們進行數據庫選型的時候要考慮哪些問題?有哪些需求?待選用的數據庫是否和需求對得上?是不是直接可以拿來用?需不需要一些額外的開發?這些都會在本文的分享中提及。


一、數據庫技術選型的思考維度


我們做選型的時候首先要問:誰選型?是負責採購的同學、 DBA 還是業務研發?如果選型的是採購的同學,他們更注重成本,包括存儲方式、網絡需求等。如果選型的是 DBA 同學,他們關心的:
① 運維成本


首先是運維成本,包括監控告警是否完善、是否有備份恢復機制、升級和遷移的成本是否高、社區是否穩定、是否方便調優、排障是否簡易等;
② 穩定性
其次,DBA會關注穩定性,包括是否支持數據多副本、服務高可用、多寫多活等;
③ 性能
第三是性能,包括延遲、QPS 以及是否支持更高級的分級存儲功能等;
④ 拓展性
第四是擴展性,如果業務的需求不確定,是否容易橫向擴展和縱向擴容;
⑤ 安全
最後是安全,需要符合審計要求,不容易出現 SQL 注入或拖庫情況。
⑥ 其他
除了採購和 DBA之外,後臺應用研發的同學同樣會關注穩定性、性能、擴展性等問題,同時也非常關注數據庫接口是否便於開發,是否便於修改數據庫 schema 等問題。

愛奇藝的

接下來我們來看一下愛奇藝使用的數據庫類型:
  • MySQL,互聯網業務必備系統;

  • TiDB,愛奇藝的 TiDB 實踐會有另外的具體介紹;

  • Redis,KV 數據庫,互聯網公司標配;

  • Couchbase,這個在愛奇藝用得比較多,但國內互聯網公司用得比較少,接下來的部分會詳細說明;

  • 其他,比如 MongoDB、圖數據庫、自研 KV 數據庫 HiKV 等;

  • 大數據分析相關係統,比如 Hive、Impala 等等。


  • 可以看到愛奇藝的數據庫種類還是很多的,這會造成業務開發的同學可能不太清楚在他的業務場景下應該選用哪種數據庫系統。


  • 那麼,我們先對這些數據庫按照接口(SQL、NoSQL)和麵向的業務場景(OLTP、OLAP)這兩位維度進行一個簡單非嚴謹的分類。


  • 下圖中,左上角是面向 OLTP、支持 SQL 的這樣一類系統,例如 MySQL,一般支持事務不同的隔離級別, QPS 要求比較高,延時比較低,主要用於交易信息和關鍵數據的存儲,比如訂單、VIP 信息等。


  • 愛奇藝的

  • 左下角是 NoSQL 數據庫,是一類針對特殊場景做優化的系統,schema 一般比較簡單,吞吐量較高、延遲較低,一般用作緩存或者 KV 數據庫。


  • 整個右側都是 OLAP 的大數據分析系統,包括 Clickhouse、Impala等,一般支持SQL、不支持事務,擴展性比較好,可以通過加機器增加數據的存儲量,響應延遲較長。


  • 還有一類數據庫是比較中立的,在數據量比較小的時候性能比較好,在數據量較大或複雜查詢的時候性能也不差,一般通過不同的存儲引擎和查詢引擎來滿足不同的業務需求,我們把它叫做 HTAP,TiDB 就是這樣一種數據庫。


  • 二、iQIYI對數據庫的優化與完善


前面我們提到了很多種的數據庫,那麼接下來就和大家介紹一下在愛奇藝我們是怎麼使用這些數據庫的。


1、MySQL在愛奇藝的使用
① MySQL
首先是 MySQL。MySQL 基本使用方式是 master-slave + 半同步,支持每週全備+每日增量備份。我們做了一些基本功能的增強,首先是增強了數據恢復工具 Xtrabackup 的性能。

之前遇到一個情況,我們有一個全量庫是 300G 數據,增量庫每天 70G 數據,總數據量 700G 左右。我們當時只需要恢復一個表的數據,但該工具不支持單表恢復,且整庫恢復需要 5 個小時。


針對這個情況我們具體排查了原因,發現在數據恢復的過程中需要進行多次寫盤的 IO 操作並且有很多串行操作,所以我們做了一些優化。例如刪減過程中的一些寫盤操作,減少落盤並將數據處理並行化,優化後整庫恢復耗時減少到 100 分鐘,而且可以直接恢復單表數據。


然後是適配 DDL 和 DML 工具到內部系統,gh-ostt 和 oak-online-alter-table 在數據量大的時候會造成 master-slave 延時,所以我們在使用工具的時候也增加了延時上的考慮,實時探測Master-Slave 庫之間延時的情況,如果延時較大會暫停工具的使用,恢復到正常水平再繼續。


② MySQL高可用


第二是 MySQL 高可用。Master-slave 加上半同步這種高可用方式不太完善,所以我們參照了 MHA 並進行了改動,採用 master + agent 的方式。Agent 在每一個物理機上部署,可以監控這個物理機上的所有實例的狀態,週期性地向 master 發送心跳,Master 會實時監測各個Agent的狀態。


如果 MySQL故障,會啟動 Binlog 補償機制,並切換訪問域名完成 failover。考慮到數據庫跨機房跨地區部署的情況,MHA 的 master 我們也做了高可用設計,眾多 master 會通過 raft 組成一個 raft group,類似 TiDB 的 PD 模塊。目前 MySQL failover 策略支持三種方式:同機房、同地域跨機房以及跨地域。


③ MySQL拓展能力


第三是提高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 的特性進行逐步遷移。


愛奇藝的

2、Redis在愛奇藝的使用

接下來是 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 毫秒左右的時間。


愛奇藝的

3、Couchbase在愛奇藝的使用

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 在愛奇藝應用比較早,2012 年還沒有 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 的過程對於客戶端來說是相對透明和無感的。


愛奇藝的

4、愛奇藝自研數據庫HiKV的使用

Couchbase 雖然性能非常高,並且數據的存儲可以超過內存。但是,如果數據量超過內存 75% 這個閾值,性能就會下降地特別快。在愛奇藝,我們會把數據量控制在可用內存的範圍之內,當做內存數據庫使用。但是它的成本非常高,所以我們後面又開發了一個新的數據庫—— HiKV。


開發 HiKV 的目的是為了把一些對性能要求沒那麼高的 Couchbase 應用遷移到 HiKV 上。HiKV 基於開源系統 ScyllaDB,主要使用了其分佈式數據庫的管理功能,增加了單機存儲引擎 HiKV。


ScyllaDB 比較吸引人的是它宣稱性能高於 Cassandra 十倍,又完全兼容 Cassandra 接口,設計基本一致,可以視為 C++ 版 Cassandra 系統。


ScyllaDB 性能的提升主要是使用了一些新的技術框架,例如 C++ 異步框架 seastar,主要原理是在j每臺物理機的核上會 attach 一個應用線程,每個核上有自己獨立的內存、網絡、IO 資源,核與核之間沒有數據共享但可以通信,其最大的好處是內存訪問無鎖,沒有衝突過程。


當一個數據讀或寫到達 ScyllaDB 的 server 時,會按照哈希算法來判斷請求的 Key 是否是該線程需要處理的,如果是則本線程處理,否則會轉發到對應線程上去。


除此之外,它還支持多副本、多數據中心、多寫多活,功能比較強大。


在愛奇藝,我們基於 SSD 做了一個 KV 存儲引擎。Key 放在內存裡,Value 放在盤上的文件裡,我們在讀和寫文件時,只需要在內存索引裡定位,再進行一次盤的 IO 開銷就可以把數據讀出來,相比 ScyllaDB 原本基於 LSM Tree 的存儲引擎方式對 IO 的開銷較少。


索引數據全部放在內存中,如果索引長度較長會限制單機可存儲的數據量,於是我們通過開發定長的內存分佈器,對於比較長的 Key 做摘要縮短長度至 20 字節,採用紅黑樹索引,限制每條記錄在內存裡的索引長度至為 64 字節。內存數據要定期做 checkpoint,客戶端要做限流、熔斷等。


HiKV 目前在愛奇藝應用範圍比較大,截至目前已經替換了 30% 的 Couchbase,有效地降低了存儲成本。


愛奇藝的


5、愛奇藝的數據庫運維管理

愛奇藝數據庫種類較多,如何高效地運維和管理這些數據庫也是經歷了不同的階段。


最初我們通過 DBA 寫腳本的方式管理,如果腳本出問題就找 DBA,導致了 DBA 特別忙碌。


第二個階段我們考慮讓大家自己去查問題的答案,於是在內部構建了一個私有云,通過 Web 的方式展示數據庫運行狀態,讓業務的同學可以自己去申請集群,一些簡單的操作也可以通過自服務平臺實現,解放了 DBA。一些需要人工處理的大型運維操作經常會造成一些人為故障,敲錯參數造成數據丟失等。


於是在第三個階段我們把運維操作 Web 化,通過網頁點擊可以進行 90% 的操作。


第四個階段讓經驗豐富的 DBA 把自身經驗變成一些工具,比如有業務同學說 MySQL master-slave 延時了,DBA 會通過一系列操作排查問題。現在我們把這些操作串起來形成一套工具,出問題時業務的同學可以自己通過網頁上的一鍵診斷工具去排查,自助進行處理。


除此之外我們還會定期做預警檢查,對業務集群裡潛在的問題進行預警報告;開發智能客服,回答問題;通過監控的數據對實例打標籤,進行削峰填谷地智能調度,提高資源利用率。


愛奇藝的

三、不同場景下數據庫選型建議
1、實用數據庫選型樹

最後來說一些具體數據庫選型建議。這是 DBA 和業務一起,通過經驗得出來的一些結論。


對於關係型數據庫的選型來說,可以從數據量和擴展性兩個維度考慮,再根據數據庫有沒有冷備、要不要使用 Toku 存儲引擎,要不要使用 Proxy 等等進行抉擇。


愛奇藝的


NoSQL 也是什麼情況下使用 master-slave,什麼情況下使用客戶端分片、集群、Couchbase、HiKV 等,我們內部自服務平臺上都有這個選型樹信息。


愛奇藝的


2、一些思考

① 需求


我們在選型時先思考需求,判斷需求是否真實。


你可以從數據量、QPS、延時等方面考慮需求,但這些都是真實需求嗎?是否可以通過其他方式把這個需求消耗掉,例如在數據量大的情況下可以先做數據編碼或者壓縮,數據量可能就降下來了。


不要把所有需求都推到數據庫層面,它其實是一個兜底的系統。


② 選擇


第二個思考的點是對於某個數據庫系統或是某個技術選型我們應該考慮什麼?是因為熱門嗎?還是因為技術上比較先進?但是不是能真正地解決你的問題?如果你數據量不是很大的話就不需要選擇可以存儲大數據量的系統。


③ 放棄


第三是放棄,當你放棄一個系統時真的是因為不好用嗎?還是沒有用好?放棄一個東西很難,但在放棄時最好有一個充分的理由,包括實測的結果。


④ 自研


第四是自研,在需要自己開發數據庫時可以參考和使用一些成熟的產品,但不要盲目自研。


⑤ 開源


最後是開源,要有擁抱開源的態度。


愛奇藝的


大家在數據庫選型中會考慮哪些因素呢?歡迎評論留言告訴大家你的想法~


歡迎在留言區留下你的觀點,一起討論提高。如果今天的文章讓你有新的啟發,學習能力的提升上有新的認識,歡迎轉發分享給更多人。


猜你還想看


阿里、騰訊、百度、華為、京東最新面試題彙集

怎樣提供一個好的移動 API 接口服務

高併發下的抽獎優化

這些方法,能夠讓你的Python程序快如閃電


關注訂閱號「程序員小樂」,收看更多精彩內容
嘿,你在看嗎?


分享到:


相關文章: