資料庫儲存選型經驗總結

ITPUB社群發表於2022-11-29

工作中總是遇到資料儲存相關的 Bug 工單,新需求開發設計中也多多少少會有資料模型設計和儲存相關的問題。經過幾次儲存方案設計選型和討論後發現需要有更全面的思考框架。

  • 日常開發中常用的儲存方案選型很多都是 “拿來主義” 的,憑藉著經驗、習慣選用,但對它們的細節特性或約束少有研究。

  • 除了手邊會用的儲存方案,也應該關注市面上更合適的儲存方案。

  • 一定的技術預研和儲備能夠幫助未來更好的技術方案設計。

故寫了這篇文章,丟擲我的觀察和思考,希望日後可以將一些更先進 (合適) 的技術引入公司業務,助力業務發展。

儲存選型的考慮要素

儲存選型的目的還是為了我們的使用場景和使用者服務,因此在選型前需要回答一些業務指標 & 技術指標方面的問題,以便於我們清楚儲存選型的應用環境。

  • 使用者量:使用者量預估多少?幾百幾萬還是幾億?

  • 資料量:資料量預估多少?日均增量能有多少?

  • 讀寫偏好:資料是讀多一些還是寫多一些?

  • 資料場景:強事務型還是分析型需求?

  • 執行效能要求:併發量是多少?高峰、平均、低谷分別預估是多少?

儲存引擎分類及特性

資料庫的分類方式非常多樣,因參考維度不同而存在較大差異,下面是常見的一些分類。

資料庫型別常見資料庫
關係型MySQL、Oracle、DB2、SQLServer 等。
非關係型Hbase、Redis、MongodDB 等。
行式儲存MySQL、Oracle、DB2、SQLServer 等。
列式儲存Hbase、ClickHouse 等。
分散式儲存Cassandra、Hbase、MongodDB 等。
鍵值儲存Memcached、Redis、MemcacheDB 等。
圖形儲存Neo4J、TigerGraph 等。
文件儲存MongoDB、CouchDB 等。

先拿我們最熟悉的關聯式資料庫來說,它的優點非常多,我們選用關聯式資料庫的理由可簡單概括為以下幾點:

1 容易理解

可由二維表結構來邏輯表達,相對網狀、層次等其他模型更加容易被理解。嚴格遵循資料格式與長度規範,資料以行為單位,一行資料表示一個實體資訊,每一行資料的屬性都是相同的。

2 事務特性

支援 ACID 特性,可以維護資料之間的一致性,這是使用關聯式資料庫非常重要的一個理由。

3 操作方便

通用的 SQL 語言使得操作關係型資料庫非常方便,支援 join 等複雜查詢,Sql + 二維關係是關係型資料庫最無可比擬的優點,這種易用性非常貼近開發者。

4 資料穩定

資料持久化到磁碟,沒有丟失資料風險。

5 服務穩定

最常用的關係型資料庫產品 MySql、Oracle 伺服器效能卓越,服務穩定,通常很少出現當機異常。

然而,在享受關聯式資料庫帶來的便利的同時,我們也不得不面臨很多麻煩的問題:

1 高併發下資料庫瓶頸明顯

資料按行儲存,即使只針對某一列進行運算,也會將整行資料從儲存裝置中讀入記憶體,導致 IO 較高。寫入更新頻繁的情況下,資料庫往往會出現 CPU 飆高、Sql 執行慢、客戶端報資料庫連線池不夠等異常情況,且效能瓶頸透過加 CPU、換固態硬碟、繼續買伺服器加資料庫做分庫等方式處理 ROI 不高,受限於其本身的特點,可能花了很多錢都未必能達到想要的效果。因此例如萬人秒殺這種場景,我們絕對不可能透過資料庫直接去扣減庫存,需要做好流量漏斗。

2 為維護資料一致性付出的代價大

資料一致性是關係型資料庫的核心,但是同樣為了維護資料一致性的代價也非常大。SQL 標準為事務定義了不同的隔離級別,從低到高依次是讀未提交、讀已提交、可重複度、序列化,事務隔離級別越低,可能導致的併發異常越多,但是能提供的併發能力越強。那麼為了保證事務一致性,資料庫就需要提供併發控制與故障恢復兩種技術,前者用於減少併發異常,後者可以在系統異常的時候保證事務與資料庫狀態不會被破壞。對於併發控制,其核心思想就是加鎖,無論是樂觀鎖還是悲觀鎖,只要提供的隔離級別越高,那麼讀寫效能必然會受影響。

3 為維護索引付出的代價大

為了提供豐富的查詢能力,通常熱點表都會有多個二級索引,一旦有了二級索引,資料的新增必然伴隨著所有二級索引的新增,資料的更新也必然伴隨著所有二級索引的更新,這不可避免地降低了關係型資料庫的讀寫能力,且索引越多讀寫能力越差。除了資料檔案不可避免地佔空間外,索引佔的空間其實也並不少。

4 水平擴充套件後帶來的種種問題難處理

隨著業務規模擴大,一種方式是對資料庫做分庫,做了分庫之後,資料遷移(1 個庫的資料按照一定規則打到 2 個庫中)、跨庫 join、分散式事務處理都是需要考慮的問題,尤其是分散式事務處理,業界當前都沒有特別好的解決方案。

5 全文搜尋功能弱

例如 like “% 新年快樂 %”,只能搜尋到 “新年快樂,愛大家”,無法搜尋到 “新年真是太快樂了,愛大家” 這樣的文字,即不具備分詞能力,且 like 查詢在 “% 新年快樂” 這樣的搜尋條件下,無法命中索引,將會導致查詢效率大大降低。

6 表結構擴充套件不方便

由於資料庫儲存的是結構化資料,因此表結構 schema 是固定的,擴充套件不方便,如果需要修改表結構,需要執行 DDL(data definition language)語句修改,修改期間會導致鎖表,部分服務不可用。


如上文所分析的,關係型資料庫優點明顯,缺點同樣不能忽視,因此通常在企業規模不斷擴大的情況下,不會一味指望透過增強資料庫的能力來解決資料儲存問題,而是會引入其他儲存,也就是我們說的 NoSql。

NoSql 的全稱為 Not Only SQL,泛指非關係型資料庫,是對關係型資料庫的一種補充,特別注意補充這兩個字,這意味著 NoSql 與關係型資料庫並不是對立關係,二者各有優劣,取長補短,在合適的場景下選擇合適的儲存引擎才是正確的做法。

下面看一下常用的 NoSql 及他們的代表產品,並對每種 NoSql 的優缺點和適用場景做一下分析,便於熟悉每種 NoSql 的特點,方便技術選型。

KV 型 NoSql(代表 —-Redis)

KV 型 NoSql 顧名思義就是以鍵值對形式儲存的非關係型資料庫,是最常見的一種 NoSql。Redis、MemCache 是其中的代表,Redis 又是 KV 型 NoSql 中應用最廣泛的 NoSql,KV 型資料庫以 Redis 為例,最大的優點總結下來主要有兩點:

  • 資料基於記憶體,讀寫效率高

  • KV 型資料,時間複雜度為 O(1),查詢速度快

所以說,KV 型 NoSql 最大的優點就是高效能,利用 Redis 自帶的 BenchMark 做基準測試,TPS 可達到 10 萬的級別,效能非常強勁。同樣的 Redis 也有所有 KV 型 NoSql 都有的比較明顯的缺點:

  • 記憶體是有限的,無法支援海量資料儲存

  • 只能根據 K 查 V,無法根據 V 查 K

  • 查詢方式單一,只有 KV 的方式,不支援條件查詢,多條件查詢唯一的做法就是資料冗餘,但這會浪費很多儲存空間

  • 由於 KV 型 NoSql 的儲存是基於記憶體的,會有丟失資料的風險(有持久化儲存方案)

    綜上所述,KV 型 NoSql 最合適的場景就是快取的場景:

    • 讀遠多於寫

    • 沒有持久化的需求,可以容忍資料丟失

    針對那些讀遠多於寫的資料,引入一層快取,每次讀從快取中讀取,快取中讀取不到,再去資料庫中取,取完之後再寫入到快取,對資料做好失效機制通常就沒有大問題了。通常來說,快取是效能最佳化的第一選擇也是見效最明顯的方案。

    搜尋型 NoSql(代表 —-ElasticSearch)

    傳統關係型資料庫主要透過索引來達到快速查詢的目的,但是在全文搜尋的場景下,索引是無能為力的,like 查詢無法滿足所有模糊匹配需求,使用限制太大且使用不當容易引起慢查詢問題,搜尋型 NoSql 的誕生正是為了解決關係型資料庫全文搜尋能力較弱的問題,ElasticSearch 是搜尋型 NoSql 的代表產品。

    全文搜尋的原理是倒排索引,我們看一下什麼是倒排索引,它是關鍵字 –> 文件的對映,舉例來說,現在這裡有四個短句:

    • "Tom is Tom"

    • "Tom is my friend"

    • "Thank you, Betty"

    • "Tom is Betty's husband"

    搜尋引擎會根據一定的分詞規則將一句話切成多個關鍵字,並以關鍵字的維度維護關鍵字在每個文字中的出現次數。這樣下次搜尋“Tom”關鍵字的時候,由於 Tom 這個詞語在“Tom is Tom”、“Tom is my friend”、“Tom is Betty’s husband” 三句話中都出現過,因此這三條記錄都會被檢索出來,而且由於”Tom is Tom” 這句話中”Tom” 出現了 2 次,因此這條記錄對”Tom” 這個單詞的匹配度最高,最先展示。這就是搜尋引擎倒排索引的基本原理,假設某個關鍵字在某個文件中出現,那麼倒排索引中有兩部分內容:

    • 文件 ID

    • 該關鍵字在該文件中出現的位置情況

    相對應的,我們搜尋”Betty Tom” 這兩個詞語也是一樣,搜尋引擎將”Betty Tom” 切分為”Tom”、”Betty” 兩個單詞,根據開發者指定的滿足率,比如滿足率 = 50%,那麼只要記錄中出現了兩個單詞之一的記錄都會被檢索出來,再按照匹配度進行展示。

    搜尋型 NoSql 以 ElasticSearch 為例,它的優點為:

    • 支援分詞場景、全文搜尋,這是區別於關係型資料庫最大特點

    • 資料寫檔案無丟失風險,在叢集環境下可以方便橫向擴充套件,可承載 PB 級別的資料

    • 支援條件查詢,支援聚合操作,類似關係型資料庫的 Group By,但是功能更加強大,適合做資料分析

    • 高可用,自動發現新的或者失敗的節點,重組和重新平衡資料,確保資料是安全和可訪問的

    同樣,ElasticSearch 也有比較明顯的缺點:

    • 效能全靠記憶體來頂,也是使用的時候最需要注意的點,非常吃記憶體,大資料量下 64G + SSD 基本就是標配,相同的配置多一倍記憶體,一個月差不多就要多花好多錢。至於 ElasticSearch 記憶體主要用在以下幾個地方:

      • Indexing Buffer----ElasticSearch 基於 Luence,Lucene 的倒排索引是先在記憶體裡生成,然後定期以 Segment File 的方式刷磁碟的,每個 Segment File 實際就是一個完整的倒排索引

      • 各類快取 ----Filter Cache、Field Cache、Indexing Cache 等,用於提升查詢分析效能,例如 Filter Cache 用於快取使用過的 Filter 的結果集

      • Segment Memory---- 倒排索引前面說過是基於關鍵字的,Lucene 在 4.0 後會將所有關鍵字以 FST 這種資料結構的方式將所有關鍵字在啟動的時候全量載入到記憶體,加快查詢速度,官方建議至少留系統一半記憶體給 Lucene

      • Cluter State Buffer----ElasticSearch 被設計為每個 Node 都可以響應使用者請求,因此每個 Node 的記憶體中都包含有一份叢集狀態的複製,一個規模很大的叢集這個狀態資訊可能會非常大

    • 資料結構靈活性不高,欄位一旦建立就沒法修改型別了,假如建立的資料表某個欄位沒有加全文索引,想加上,那麼只能把整個表刪了再重建。

    • 讀寫之間有延遲,寫入的資料差不多 1s 樣子會被讀取到(資料寫入時需要維護很多索引)

    因此,搜尋型 NoSql 最適用的場景就是有條件搜尋尤其是全文搜尋的場景,作為關係型資料庫的一種替代方案,通常搜尋型 NoSql 也會作為一層前置快取,來對關係型資料庫進行保護。

    此外,搜尋型資料庫還有一種非常重要的應用場景。我們可以想,一旦對資料庫做了分庫分表後,原來可以在單表中做的聚合操作、統計操作是否統統失效?例如我把訂單表分 16 個庫,1024 張表,那麼訂單資料就散落在 1024 張表中,我想要統計昨天浙江省單筆成交金額最高的訂單是哪筆如何做?這就是搜尋型 NoSql 的另一大作用了,我們可以把分表之後的資料統一打在搜尋型 NoSql 中,利用搜尋型 NoSql 的搜尋與聚合能力完成對全量資料的查詢。

    列式 NoSql(代表 —-HBase)

    列式 NoSql 和關係型資料庫一樣都有主鍵的概念,區別在於關係型資料庫是按照行組織的資料,資料欄位即使沒有值同樣佔空間,列式儲存完全是另一種方式,它是按列進行資料組織的,好處在於:

    • 查詢時只有指定的列會被讀取,不會讀取所有列

    • 儲存上節約空間,空值不會被儲存,一列中有時候會有很多重複資料(尤其是列舉資料,性別、狀態等欄位),這類資料可壓縮

    • 列資料被組織到一起,一次磁碟 IO 可以將一列資料一次性讀取到記憶體中

    大資料時代最具代表性的技術之一 HBase 就是列式 NoSQL 的產品實現,其優點主要是:

    • 海量資料儲存,PB 級別資料隨便存,底層基於 HDFS(Hadoop 檔案系統),資料持久化

    • 讀寫效能好,只要沒有濫用造成資料熱點,讀寫基本沒任何問題

    • 橫向擴充套件在關係型資料庫及非關係型資料庫中都是最方便的之一,只需要新增新機器就可以實現資料容量的線性增長,且可用在廉價伺服器上,節省成本

    • 可儲存結構化或者半結構化的資料

    • 本身沒有單點故障,可用性高

    • 列數理論上無限制,HBase 本身只對列族數量有要求,建議 1~3 個

    缺點主要表現在:

    • HBase 是 Hadoop 生態的一部分,因此它本身是一款比較重的產品,依賴很多 Hadoop 元件,資料規模不大沒必要用,運維還是有點複雜的。

    • 不支援分頁查詢,因為統計不了資料總數。

    • KV 式儲存,條件查詢很弱,HBase 在 Scan 掃描一批資料的情況下還是提供了字首匹配這種 API 的,條件查詢除非定義多個 RowKey 做資料冗餘。

    因此 HBase 比較適用於 KV 型儲存且未來無法預估資料增長量的場景,另外 HBase 使用還是需要一定的經驗,主要體現在 RowKey 的設計上。

    文件型 NoSql(代表 —-MongoDB)

    文件型 NoSql 指的是將半結構化資料儲存為文件的一種 NoSql,文件型 NoSql 通常以 JSON 或者 XML 格式儲存資料,因此文件型 NoSql 是沒有 Schema 的,由於沒有 Schema 的特性,我們可以隨意地儲存與讀取資料,因此文件型 NoSql 的出現是解決關係型資料庫表結構擴充套件不方便的問題的。

    MongoDB 是文件型 NoSql 的代表產品,同時也是所有 NoSql 產品中的明星產品之一,它的很多概念與關聯式資料庫類似,因此,對於 MongDB,我們只需要理解成一個 Free-Schema 的關係型資料庫就好了,其優點主要是:

    • 沒有預定義的欄位,擴充套件欄位容易

    • 相較於關係型資料庫,讀寫效能優越,命中二級索引的查詢不會比關係型資料庫慢,對於非索引欄位的查詢則是全面勝出

    缺點在於:

    • 不支援事務操作,雖然 Mongodb4.0 之後宣稱支援事務,但是效果待觀測

    • 多表之間的關聯查詢不支援(雖然有嵌入文件的方式),join 查詢還是需要多次操作

    • 空間佔用較大,這個是 MongDB 的設計問題,空間預分配機制 + 刪除資料後空間不釋放,只有用 db.repairDatabase () 去修復才能釋放

    • 目前沒發現 MongoDB 有關係型資料庫例如 MySql 的 Navicat 這種成熟的運維工具

    總而言之,MongDB 的使用場景很大程度上可以對標關係型資料庫,但是比較適合處理那些沒有 join、沒有強一致性要求且表 Schema 會常變化的資料。


    透過以上討論分析我們心中已經有了一個基本的選型框架指導,實際上在資料庫選型時回答自己兩個核心問題就好了:

    • 什麼時候選用關係型資料庫,什麼時候選用非關係型資料庫

    • 選用非關係型資料庫的話,使用哪種非關係型資料庫

    NoSQL 資料庫都是透過犧牲了 ACID 特性來獲取更高效能的,假設表資料有很強的事務特性需求,那麼這類資料是不適合放在非關係型資料庫。此外,選用 NoSQL 資料庫時也要根據公司技術棧框架、業務特性、運維成本等多方面考慮是否採納。

    資料庫儲存選型經驗總結

    總結

    關係型資料庫和 NoSQL 資料庫的選型,往往需要考慮幾個指標:

    • 資料量

    • 併發量

    • 實時性

    • 一致性要求

    • 讀寫分佈和型別

    • 安全性

    • 運維成本

    常見軟體系統資料庫選型參考如下:

    • 中後臺管理型系統  - 如運營系統,資料量少,併發量小,首選關係型資料庫。

    • 大流量系統  - 如電商單品頁,後臺考慮選關係型資料庫,前臺考慮選記憶體型資料庫。

    • 日誌型系統  - 原始資料考慮選列式資料庫,日誌搜尋考慮選搜尋引擎。

    • 搜尋型系統  - 例如站內搜尋,非通用搜尋,如商品搜尋,後臺考慮選關係型資料庫,前臺考慮選搜尋引擎。

    • 事務型系統  - 如庫存,交易,記賬,考慮選關係型資料庫 + K-V 資料庫(作為快取)+ 分散式事務。

    • 離線計算 - 如大量資料分析,考慮選列式資料庫或關係型資料庫。

    • 實時計算  - 如實時監控,可以考慮選記憶體型資料庫或者列式資料庫。

    設計實踐中,要基於需求、業務驅動架構,無論選用 RDB/NoSQL, 一定是以需求為導向,最終資料儲存方案必然是各種權衡的綜合性設計。


    來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70024420/viewspace-2925691/,如需轉載,請註明出處,否則將追究法律責任。

    相關文章