NoSQL:在高并發場景下,數據庫和NoSQL如何做到互補?
針對存儲服務的優化,我們通常會著手兩個方面:
1. 提升讀寫性能,特別是優化讀取效率。因為我們的產品多數是讀取頻率高于寫入頻率的。例如,微信朋友圈、微博和淘寶等服務,它們的查詢 QPS明顯高于寫入 QPS。
2. 加強存儲系統的擴展性,以滿足不斷增長的數據存儲需求。
NoSQL,No SQL?
NoSQL是一種非關系型數據庫,與傳統的關系型數據庫有所不同。NoSQL數據庫不使用SQL作為查詢語言,而是提供了其他方式來操作數據。它們通常具有出色的橫向擴展能力和高性能的讀寫操作,非常適合處理互聯網項目中的大數據量和高并發訪問的需求。因此,許多大型公司,如小米、微博和陌陌,都傾向于選擇NoSQL數據庫作為其處理高并發大容量數據的存儲服務。
Redis、LevelDB等鍵值存儲數據庫,以其極高的讀寫性能而著稱,通常在對性能有較高要求的場景下得到廣泛應用。Hbase、Cassandra等列式存儲數據庫則以其以列為存儲單位而聞名,特別適用于離線數據統計等場景。至于MongoDB、CouchDB等文檔型數據庫,其主要特點在于Schema Free,即數據表的字段可以靈活擴展,這使得它們在處理具有多變字段結構的數據,比如電商系統中的商品信息,更加簡單和靈活
使用 NoSQL 提升寫入性能
數據庫系統通常使用傳統的機械硬盤進行存儲,而機械硬盤的訪問方式主要有兩種:隨機IO和順序IO。隨機IO需要花費大量時間進行磁盤尋道,因此其讀寫效率通常比順序IO低兩到三個數量級。為了提升寫入性能,需要盡量減少隨機IO的發生。
以MySQL的InnoDB存儲引擎為例,更新binlog、redo log、undo log等操作通常采用順序IO,而更新datafile和索引文件則需要進行隨機IO。盡管關系數據庫進行了許多優化,比如先將寫入數據存入內存,然后批量刷新到磁盤上,但隨機IO仍然難以避免。
在InnoDB引擎中,索引通常采用B+樹結構組織。由于MySQL的主鍵是聚簇索引,即數據和索引數據存儲在一起,因此在數據插入或更新時需要找到合適的位置并寫入特定位置,這就會引發隨機IO。此外,一旦發生頁分裂,就不可避免地會涉及數據的移動,進一步降低寫入性能。
NoSQL 數據庫是怎么解決這個問題的呢?
許多NoSQL數據庫都采用基于LSM樹的存儲引擎,這種算法在性能上取得了很大突破,因此在這里我想深入探討一下LSM樹的工作原理。LSM樹(Log-Structured Merge Tree)通過犧牲一定的讀性能來實現高效的寫入操作,因此像Hbase、Cassandra和LevelDB等數據庫都采用了這種存儲引擎。
LSM樹的核心思想很簡單:數據首先寫入到一個稱為MemTable的內存結構中,并按照寫入的鍵(Key)進行排序。為了避免因機器掉電或重啟而丟失MemTable中的數據,通常會通過Write Ahead Log的方式將數據備份到磁盤上。當MemTable累積到一定規模時,會將其刷新為一個新文件,我們稱之為SSTable(Sorted String Table)。當SSTable數量達到一定閾值時,會將它們進行合并,以減少文件數量,因為SSTable都是有序的,所以合并速度非常快。
在進行LSM樹的數據讀取時,首先從MemTable中查找數據,如果未找到,則從SSTable中查找。由于數據是有序存儲的,因此查詢效率非常高。然而,由于數據被拆分成多個SSTable,讀取效率可能低于B+樹索引。
圖片
提升擴展性
此外,在可擴展性方面,許多NoSQL數據庫具有天然的優勢。以您的垂直電商系統為例,您已經添加了評論系統,最初對系統的評估比較樂觀,認為評論量級不會迅速增長,因此將數據庫分成了8個庫,每個庫又分成了16張表。但是評論系統上線后,存儲量迅速增長,您不得不將數據庫進一步分割成更多的庫和表,而數據也必須重新遷移到新的庫表中,這個過程非常痛苦且容易出錯。在這種情況下,您是否考慮過使用NoSQL數據庫來徹底解決可擴展性問題呢?經過調查,您會發現NoSQL數據庫在設計之初就考慮到了分布式和大數據存儲的場景,比如像MongoDB就具備三個關鍵的擴展性特性。
另一個關鍵特性是Replica,也稱為副本集。您可以將其視為主從復制,即通過將數據復制多份來確保在主節點故障時數據不會丟失。同時,副本還可以分擔讀請求。在副本中,主節點負責處理寫請求,并將數據變更記錄到oplog中(類似于binlog);從節點接收oplog后,會修改自身的數據以保持與主節點的一致性。一旦主節點故障,MongoDB將從從節點中選取一個節點作為主節點,繼續提供寫入數據的服務。
第二個特性是Shard,也稱為分片,您可以將其視為分庫分表,即根據某種規則將數據拆分成多份,存儲在不同的機器上。MongoDB的Sharding特性通常需要三個角色來支持:Shard Server,實際存儲數據的節點,是一個獨立的Mongod進程;Config Server,也是一組Mongod進程,主要存儲一些元信息,例如哪些分片存儲了哪些數據等;最后是Route Server,它不實際存儲數據,僅用作路由,從Config Server獲取元信息后,將請求路由到正確的Shard Server。
圖片
另外一個關鍵特性是負載均衡,即當MongoDB發現Shard之間數據分布不均勻時,會啟動Balancer進程對數據進行重新分配,以確保不同Shard Server的數據盡可能均衡。當Shard Server的存儲空間不足需要擴容時,數據會自動遷移到新的Shard Server上,從而減少了數據遷移和驗證的成本。
在性能方面,NoSQL數據庫利用一些算法將磁盤上的隨機寫操作轉換為順序寫,從而提升了寫入性能。在某些場景下,如全文搜索功能,傳統的關系型數據庫無法有效支持,而需要借助NoSQL數據庫的特性。
就擴展性而言,NoSQL數據庫天生支持分布式架構,具備數據冗余和數據分片的特性。這些特點使得NoSQL成為傳統關系型數據庫的有效補充。在選擇NoSQL數據庫時,需要深入了解各種組件的實現原理,并具備一定的運維經驗。否則,盲目地引入新的NoSQL數據庫可能導致故障無法解決,成為整個系統的負擔。
我曾在以前的項目中使用Elasticsearch作為持久存儲,支持社區的動態流功能。初期開發進展順利,Elasticsearch提供了靈活高效的查詢功能,業務功能得以快速迭代,代碼也簡潔易懂。然而,隨著流量的增加,由于缺乏成熟的Elasticsearch運維能力,頻繁出現故障。尤其是在高峰期,節點不穩定的問題更加突出。由于業務壓力巨大,無法投入足夠的人力和時間深入學習和理解Elasticsearch,最終不得不做出重大改動,回歸熟悉的MySQL。因此,對于開源組件的使用,不應止步于簡單的入門階段,而應具備足夠的運維能力。
























