圖解|什么是高并發利器NoSQL
本文轉載自微信公眾號「后端技術指南針」,作者指南針氪金入口。轉載本文請聯系后端技術指南針公眾號。
1.迷茫的小黑
小黑最近有點郁悶。
手頭的工作不是特別喜歡,技術退步有點嚴重,于是想出去看看機會。
小黑通過朋友內推,前幾天去北京CBD附近的一家名叫宇節蹦跶的公司面試,被一些問題三連擊直接跪掉了。
大白安撫小黑說:"黑哥,你要知道沒有好工作,只有好工人,其實哪兒都差不多,都是打工嘛!"
小黑說:"那咋能一樣,工具人么得意思,俺的目標是架構師!"
小黑把大白數落了一頓,畢竟不想當架構師的后端不算好的程序員,然后開始說他碰到的問題,原來事情是這樣的:
小黑開場白介紹完自己之后,面試官問他喜歡哪個領域,小黑說分布式存儲。
面試官附和道:"確實是個不錯的領域,那問你幾個存儲相關的問題吧!"
小黑竊喜以為問題在自己知識射程范圍內,于是面試官拋出了幾個問題:
1.在實際工作中用到過哪些NoSQL?
2.NoSQL相比MySQL有什么優缺點?
3.WAL和LSM這些底層機制了解嗎?
4.如何設計一個NoSQL。
聽完這幾個問題,小黑慌得一批,東扯西扯了幾句后,面試官也看出小黑關于存儲的知識邊界了,很有禮貌地不再追問了。
出于友好禮節,問了幾個小黑簡歷上的東西,最后結束了這場面試。
大白聽完也有點慌,故作鎮定說:"黑哥,你這幾個問題都比較典型,周末寫一篇文章,給你講講這塊東西。"
小黑笑道:"哈哈哈,就等你這個呢,老規矩周五放松一下!上周恰燒烤了,這周恰個火鍋唄"
害,大白和小黑真是吃喝二人組啊,言歸正傳,開始NoSQL之旅吧!
2. 大器晚成NoSQL
NoSQL一詞最早出現于1998年,受限于當時的技術場景和應用情況,并沒有折騰出什么大浪,但是在2009年NoSQL再次被提出,這一次出場有點炸裂,頗有明日之星的趕腳。
一般來說,這事行不行往往和口號有很大關系,像我大白的口號就是:人生實苦,早點退休。
2009年在亞特蘭大的一次重要會議上對NoSQL提出了個文雅&響亮的口號:
- select fun, profit from real_world where relational=false;
本質上NoSQL是一類數據庫的泛稱,具體的可以分為以下幾種:
本文只介紹鍵值對key-value型數據庫,先看下NoSQL名字的來源和含義的幾種解讀吧:
- 解讀一
NoSQL 意為"No SQL" 翻譯為SQL已死。潛臺詞是這類數據庫沒有SQL語句,摒棄了老大哥MySQL的路子,讓它退休。
嚯,好家伙,口氣不小,事實證明,這種"SQL已死"的自信確實有點扯犢子了。
- 解讀二
NoSQL 意為"Not Only SQL",顯然沒有那么囂張了,倒添了幾分謙虛,不僅僅是SQL,除了繼承了老大哥MySQL的一些功能,還增加了新東西,聽起來還不錯的樣子。
- 解讀三
NoSQL本質上是非關系型數據庫,我們都知道關系型數據庫一般縮寫為RDS或者RDB,所以有人覺得NoSQL應該稱為"No Relational Database",簡稱"沒關系數據庫"。
綜上,我們更傾向于NoSQL為"Not Only SQL",作為關系型數據庫的補充而存在的一種新形式的數據庫。
3. 給NoSQL一首歌的時間
工欲善其事 必先利其器,大家都這么忙,必須要給個學習NoSQL的理由。
我們試想幾種熟悉的場景(點擊查看大圖):
- 場景一
Leader大熊給小黑一個需求,這個需求在幾千萬行的MySQL中加個字段,由于是生產環境需要找DBA手動執行,好家伙排隊大半天才給執行,給小黑氣的,太耽誤事了太不方便了。
- 場景二
Leader大熊又給了小黑一個需求,讓他存儲一個數據包,數據包其實是Json的串,里面有很多字段,但是現在也不明確PM要怎么用,字段是否會調整,所以用MySQL存儲的話,字段還不能確定,于是小黑加了個extra字段來存儲后續的擴展,暫時解決了。
- 場景三
Leader大熊讓小黑設計一個并發訪問大一些的服務,來完成用戶賬號體系查詢,目前差不多有4億賬號ID,大熊搞了C++服務&存儲選擇了MySQL,性能怎么也提不上去,真是一籌莫展。
MySQL橫行江湖數十載,無人匹敵,尤其在事務、數據一致性、關聯查詢等場景具有絕對的統治力,確實是數據庫藍波萬。
科技進步和新常用新形式的出現也讓MySQL有些捉襟見肘,畢竟MySQL不是萬金油,要保住地位必須與時俱進才行。
在很多場景中我們并不需要事務、強數據一致性、多表關聯等特性,所以我們需要一類更輕快的數據庫,它就是NoSQL。
4. MySQL vs NoSQL
我們有必要將MySQL和NoSQL進行一番對比,來加深印象:
- MySQL是高度組織化結構化的數據存儲,NoSQL無結構化存儲
- MySQL使用結構化查詢語句,NoSQL無查詢語言
- MySQL需要定義字段和模式,NoSQL自由擴展
- MySQL海量數據時讀寫性能劣于NoSQL
- MySQL擴展性較差
上面這些好像全是diss老大哥MySQL的,但是并不是說MySQL很弱,相反是MySQL非常強悍。
高并發&高可用&高可擴展的新要求成就了NoSQL,NoSQL之所以可以應對這些新場景,和它的設計思想有很大的關系。
或許可以用葡萄來說明為啥NoSQL更適用于高并發場景。
- NoSQL是一粒一粒的葡萄,存取都非常方便,讀寫速度快
- MySQL是一串葡萄,每一粒都是相互關聯的,存取較為麻煩,讀寫速度慢
兩類數據庫的對比就說這么多,我們來看看幾款大白在實際工作中用過的NoSQL吧!
5. NoSQL明星項目
開源的NoSQL非常多,大白按照層次挑幾個典型的代表來和大家分享一下。
NoSQL可以是單機的,也可以是分布式的,可以根據自己的目的來使用。
今天要介紹的幾款數據庫:Redis、Pika、SSDB、RocksDB、LevelDB。
其中LevelDB是谷歌開發的,RocksDB是Facebook在LevelDB的基礎上增加新特性開發的,Redis則不用多說,SSDB和Pika則是國內開源的類Redis的數據庫,也非常棒。
接下來看看這幾款數據庫的特點、聯系、底層原理等有趣的東西。
5.1 谷歌出品LevelDB
LevelDB是谷歌的Sanjay Ghemawat和Jeff Dean使用C++開發的單進程/單機版持久化的key-value數據庫,于2011年7月開源,可以說是重磅產品。
LevelDB支持了最基礎的key-value操作:Get/Put/Delete,但是并沒有封裝其他的東西,嚴格意義上來說只是NoSQL存儲引擎。

一般來說,機械磁盤最害怕的就是隨機讀寫,磁盤呼嚕嚕轉起來就意味著讀寫效率在下降。
LevelDB具有很高的隨機寫,順序讀/寫性能,因此LevelDB很適合應用在寫多讀少的場景,真讓人好奇高性能的隨機寫怎么做到的。
5.1.1 LSM樹
很多數據在邏輯上相近,但是在物理存儲上卻可能相隔很遠,這樣就會造成大量的隨機讀寫問題,從而降低性能。
LevelDB實現高性能隨機寫的秘密武器在于使用LSM樹存儲結構,LSM樹又稱為日志結構合并樹(Log-Structured Merge-Tree),它并不是具體的數據結構,而是一種設計思想。
LSM樹對于每次寫入操作,并不是直接將最新的數據駐留在磁盤中,而是將數據先放在內存。
當內存數據達到一定的閾值,再將這部分數據真正刷新到磁盤文件中,從而將磁盤隨機寫轉換為內存順序寫,因而獲得了極高的寫性能,但是這種機制會降低讀的性能,總體來說降低部分讀性能來大幅提升寫性能是值得的。
5.1.2 LevelDB整體架構
LevelDB 存儲結構主要由六個部分組成:
- MemTable:內存數據結構,使用SkipList實現,新的數據修改會首先在這里寫入,并且有容量限制。
- Immutable MemTable:待落盤的數據庫內存結構,當 MemTable的大小達到設定的閾值時,會變成 Immutable MemTable,只接受讀操作,不再接受寫操作,后續會Flush到磁盤上。
- SST Files:Sorted String Table Files,磁盤數據存儲文件,分為 Level0 到 LevelN 多層,每一層包含多個 SST 文件,文件內數據有序。
- Manifest Files:leveldb元信息清單文件。Manifest記錄 SST 文件在不同 Level 的分布,相當于SST文件的索引。
- Current File:當前正在使用的文件清單文件。
LevelDB的讀寫過程和上述的整體架構關系密切,也是先內存后磁盤,一層層讀取搜索數據的。
5.2 臉書出品RocksDB
青出于藍而勝于藍。
RocksDB在LevelDB的基礎上進行了改進和優化,也成為后續很多NoSQL所選擇的存儲引擎。
RocksDB仍然是采用C++開發的,并且完全向后兼容了LevelDB的接口,可以說是個平滑升級。
5.2.1 RocksDB提升點
來看看RocksDB做了哪些優化和提升:
- RocksDB支持一次獲取多個Key,還支持Key范圍查找,LevelDB只能獲取單個Key。
- RocksDB支持多線程合并,而LevelDB是單線程合并的,多核時代前者效率更高。
- RocksDB增加了合并時過濾器,對不符合條件的Key進行丟棄。
- RocksDB可采用多種壓縮算法,除了LevelDB用的snappy,還有zlib、bzip2。
- RocksDB支持增量備份和全量備份。
RocksDB支持管道式的Memtable,使用多個Memtable,LevelDB只有一個Memtable。

5.2.2 RocksDB整體架構
Rocksdb中引入了Column Family(列族的概念,所謂列族也就是一系列kv組成的數據集,所有的讀寫操作都需要先指定列族。
每個ColumnFamily有自己的Memtable, SST文件,所有ColumnFamily共享WAL、Current、Manifest文件。
如果說LevelDB是個平民版的NoSQL存儲引擎,那么RocksDB絕對是尊享版,所以很多優秀的NoSQL成品都是基于RocksDB來封裝上層協議和代理支持完成的。
5.3 ideawu的SSDB
SSDB是基于SSD作為底層存儲介質的類Redis數據庫。
Redis過于迷人和好用,但是又太昂貴了,所以我們幻想著有一款支持Redis數據結構且容量沒限制的數據庫。
這種數據庫將Redis的數據結構優勢和廉價磁盤介質聯合起來,著實讓人著迷。
沒錯,SSDB就是這樣一款NoSQL數據庫。
目前SSDB的維護者并不是特別多,并且在集群化等方面還存在一些問題,不過也算是非常優秀的開源NoSQL了。
來簡單看下SSDB的基本架構:

簡單來說,SSDB在LevelDB和Redis協議之間做了一層轉換,從而實現命令和數據的切換,這就是使用存儲引擎之前封裝附加部分,從而形成完整的NoSQL。
5.4 360出品Pika
其實SSDB和Pika很有淵源,SSDB的作者曾經在360工作,并且SSDB當時在360的生產環境中使用廣泛,Pika數據庫是360基于RocksDB開發的集群化高性能NoSQL。
Pika是360DBA團隊和基礎架構團隊使用C++語言聯合開發的類Redis存儲系統,所以完全支持Redis協議。
Pika是一個可持久化的大容量Redis存儲服務,解決Redis由于存儲數據量巨大而導致內存不夠用的容量瓶頸,并且支持全同步和部分同步。
Pika還提供了遷移工具,實現Redis數據到Pika的平滑遷移,Pika的定位目標并不是取代redis, 而是作為redis的補充,在性能上肯定會低于Redis。
從整體架構可知,Pika是多線程實現的,因此對多核使用效率更高,雖然單線程性能不如Redis,但是多個線程一起上性能也還不錯。
Pika目前在360內部使用非常廣泛,在一些其他的互聯網公司也有使用。
說到底NoSQL是一個實踐類的項目,本文也不能講述太多,否則很空洞,意義不大。
5.5 其他NoSQL
基本上很多互聯網公司都會基于LevelDB或者RocksDB開發一款自己的Key-Vaule數據庫,有的只支持簡單的string結構,有的完全兼容Redis協議和客戶端。
大都是解析Redis協議、使用Redis的客戶端、增加一層命令解析和數據格式解析層、再有可能有多線程支持&主從化&集群化等。
自己開發一款簡單的NoSQL,可以極大提升自己對于NoSQL的理解,在github上有很多這樣的項目,可以參考學習下。
6. 本文小結
本文粗淺闡述了NoSQL的一些相關知識點,以及一些筆者實踐過的NoSQL。
大致原理本質上差不多,但是要打造一款高性能&高可用的工業級NoSQL是非常困難的。