你是否在為系統的數據庫來一波大流量就幾乎打滿CPU,日常CPU居高不下煩惱?你是否在各種NoSql間糾結不定,到底該選用那種最好?今天的你就是昨天的我,這也是寫這篇文章的初衷。
這篇文章是我好幾個月來一直想寫的一篇文章,也是一直想學習的一個內容,作為互聯網從業人員,我們要知道關系型數據庫(MySql、Oracle)無法滿足我們對存儲的所有要求,因此對底層存儲的選型,對每種存儲引擎的理解非常重要。同時也由于過去一段時間的工作經歷,對這塊有了一些更多的思考,想通過自己的總結把這塊寫出來分享給大家。
結構化、非結構化和半結構化數據
文章的開始,聊一下結構化數據、非結構化數據與半結構化數據,因為數據特點的不同,將在技術上直接影響存儲引擎的選型。
首先是結構化數據,根據定義結構化數據指的是由二維表結構來邏輯表達和實現的數據,嚴格遵循數據格式與長度規范,也稱作為行數據,特點為:數據以行為單位,一行數據表示一個實體的信息,每一行數據的屬性是相同的。例如:
因此關系型數據庫完美契合結構化數據的特點,關系型數據庫也是關系型數據最主要的存儲與管理引擎。
非結構化數據,指的是數據結構不規則或不完整,沒有任何預定義的數據模型,不方便用二維邏輯表來表現的數據,例如辦公文檔(Word)、文本、圖片、HTML、各類報表、視頻音頻等。
介于結構化與非結構化數據之間的數據就是半結構化數據了,它是結構化數據的一種形式,雖然不符合二維邏輯這種數據模型結構,但是包含相關標記,用來分割語義元素以及對記錄和字段進行分層。常見的半結構化數據有XML和JSON,例如:
<person> <name>張三</name> <age>18</age> <phone>12345</phone></person>復制代碼
這種結構也被成為自描述的結構。
以關系型數據庫的方式做存儲的架構演進
首先,我們看一下使用關系型數據庫的方式,企業一個系統發展的幾個階段的架構演進(由于本文寫的是Sql與NoSql,因此只以存儲方式作為切入點,不會涉及類似MQ、ZK這些中間件內容):
階段一:企業剛發展的階段,最簡單,一個應用服務器配一個關系型數據庫,每次讀寫數據庫。
階段二:無論是使用MySQL還是Oracle還是別的關系型數據庫,數據庫通常不會先成為性能瓶頸,通常隨著企業規模的擴大,一臺應用服務器扛不住上游過來的流量且一臺應用服務器會產生單點故障的問題,因此加應用服務器并且在流量入口使用Nginx做一層負載均衡,保證把流量均勻打到應用服務器上。
階段三:隨著企業規模的繼續擴大,此時由于讀寫都在同一個數據庫上,數據庫性能出現一定的瓶頸,此時簡單地做一層讀寫分離,每次寫主庫,讀備庫,主備庫之間通過binlog同步數據,就能很大程度上解決這個階段的數據庫性能問題
階段四:企業發展越來越好了,業務越來越大了,做了讀寫分離數據庫壓力還是越來越大,這時候怎么辦呢,一臺數據庫扛不住,那我們就分幾臺吧,做分庫分表,對表做垂直拆分,對庫做水平拆分。以擴數據庫為例,擴出兩臺數據庫,以一定的單號(例如交易單號),以一定的規則(例如取模),交易單號對2取模為0的丟到數據庫1去,交易單號對2取模為1的丟到數據庫2去,通過這樣的方式將寫數據庫的流量均分到兩臺數據庫上。一般分庫分表會使用Shard的方式,通過一個中間件,便于連接管理、數據監控且客戶端無需感知數據庫ip
關系型數據庫的優點
上面的方式,看似可以解決問題(實際上確實也能解決很多問題),正常對關系型數據庫做一下讀寫分離 + 分庫分表,支撐個1W+的讀寫QPS還是問題不大的。但是受限于關系型數據庫本身,這套架構方案依然有著明顯的不足,下面對利用關系型數據庫方式做存儲的方案的優點先進行一下分析,后一部分再分析一下缺點,對某個技術的優缺點的充分理解是技術選型的前提。
- 易理解
因為行 + 列的二維表邏輯是非常貼近邏輯世界的一個概念,關系模型相對網狀、層次等其他模型更加容易被理解
- 操作方便
通用的SQL語言使得操作關系型數據庫非常方便,支持join等復雜查詢
- 數據一致性
支持ACID特性,可以維護數據之間的一致性,這是使用數據庫非常重要的一個理由之一,例如同銀行轉賬,張三轉給李四100元錢,張三扣100元,李四加100元,而且必須同時成功或者同時失敗,否則就會造成用戶的資損
- 數據穩定
數據持久化到磁盤,沒有丟失數據風險,支持海量數據存儲
- 服務穩定
最常用的關系型數據庫產品MySql、Oracle服務器性能卓越,服務穩定,通常很少出現宕機異常
關系型數據庫的缺點
緊接著的,我們看一下關系型數據庫的缺點,也是比較明顯的。
- 高并發下IO壓力大
數據按行存儲,即使只針對其中某一列進行運算,也會將整行數據從存儲設備中讀入內存,導致IO較高
- 為維護索引付出的代價大
為了提供豐富的查詢能力,通常熱點表都會有多個二級索引,一旦有了二級索引,數據的新增必然伴隨著所有二級索引的新增,數據的更新也必然伴隨著所有二級索引的更新,這不可避免地降低了關系型數據庫的讀寫能力,且索引越多讀寫能力越差。有機會的話可以看一下自己公司的數據庫,除了數據文件不可避免地占空間外,索引占的空間其實也并不少
- 為維護數據一致性付出的代價大
數據一致性是關系型數據庫的核心,但是同樣為了維護數據一致性的代價也是非常大的。我們都知道SQL標準為事務定義了不同的隔離級別,從低到高依次是讀未提交、讀已提交、可重復度、串行化,事務隔離級別月底,可能出現的并發異常越多,但是通常而言能提供的并發能力越強。那么為了保證事務一致性,數據庫就需要提供并發控制與故障恢復兩種技術,前者用于減少并發異常,后者可以在系統異常的時候保證事務與數據庫狀態不會被破壞。對于并發控制,其核心思想就是加鎖,無論是樂觀鎖還是悲觀鎖,只要提供的隔離級別越高,那么讀寫性能必然越差
- 水平擴展后帶來的種種問題難處理
前文提過,隨著企業規模擴大,一種方式是對數據庫做分庫,做了分庫之后,數據遷移(1個庫的數據按照一定規則打到2個庫中)、跨庫join(訂單數據里有用戶數據,兩條數據不在同一個庫中)、分布式事務處理都是需要考慮的問題,尤其是分布式事務處理,業界當前都沒有特別好的解決方案
- 表結構擴展不方便
由于數據庫存儲的是結構化數據,因此表結構schema是固定的,擴展不方便,如果需要修改表結構,需要執行DDL(data definition language)語句修改,修改期間會導致鎖表,部分服務不可用
- 全文搜索功能弱
例如like "%中國真偉大%",只能搜索到"2019年中國真偉大,愛祖國",無法搜索到"中國真是太偉大了"這樣的文本,即不具備分詞能力,且like查詢在"%中國真偉大"這樣的搜索條件下,無法命中索引,將會導致查詢效率大大降低
寫了這么多,我的理解核心還是前三點,它反映出的一個問題是關系型數據庫在高并發下的能力是有瓶頸的,尤其是寫入/更新頻繁的情況下,出現瓶頸的結果就是數據庫CPU高、Sql執行慢、客戶端報數據庫連接池不夠等錯誤,因此例如萬人秒殺這種場景,我們絕對不可能通過數據庫直接去扣減庫存。


