最近小組準備啓動一個 node 開源項目,從前端親和力、大數據下的IO性能、可擴展性幾點入手挑選了 NoSql 數據庫,但具體使用哪一款產品還須要作一次選型。javascript
咱們最終把選項範圍縮窄在 HBase、Redis、MongoDB、Couchbase、LevelDB 五款較主流的數據庫產品中,本文將主要對它們進行分析對比。html
鑑於缺少項目中的實戰經驗沉澱,本文內容和觀點主要仍是從各平臺資料蒐羅彙總,也不會有太多深刻或底層原理探討。前端
本文所引用的資料來源將示於本文尾部。所彙總的內容僅供參考,如有異議望指正。java
HBasenode
HBase 是 Apache Hadoop 中的一個子項目,屬於 bigtable 的開源版本,所實現的語言爲Java(故依賴 Java SDK)。HBase 依託於 Hadoop 的 HDFS(分佈式文件系統)做爲最基本存儲基礎單元。mysql
HBase在列上實現了 BigTable 論文提到的壓縮算法、內存操做和布隆過濾器。HBase的表可以做爲 MapReduce任務的輸入和輸出,能夠經過Java API來訪問數據,也能夠經過REST、Avro或者Thrift的API來訪問。
1. 特色
1.1 數據格式linux
HBash 的數據存儲是基於列(ColumnFamily)的,且很是鬆散—— 不一樣於傳統的關係型數據庫(RDBMS),HBase 容許表下某行某列值爲空時不作任何存儲(也不佔位),減小了空間佔用也提升了讀性能。c++
不過鑑於其它NoSql數據庫也具備一樣靈活的數據存儲結構,該優點在本次選型中並不出彩。
咱們以一個簡單的例子來了解使用 RDBMS 和 HBase 各自的解決方式:git
⑴ RDBMS方案:github
其中Article表格式:
Author表格式:
⑵ 等價的HBase方案:
對於前端而言,這裏的 Column Keys 和 Column Family 能夠看爲這樣的關係:
1
2
3
4
5
6
7
8
9
10
11
|
columId1 = { //id=1的行
article: { //ColumnFamily-article
title: XXX, //ColumnFamily-article下的key之一
content: XXX,
tags: XXX
},
author: { //ColumnFamily-author
name: XXX
nickname: XXX
}
}
|
1.2 性能
HStore存儲是HBase存儲的核心,它由兩部分組成,一部分是MemStore,一部分是StoreFiles。
MemStore 是 Sorted Memory Buffer,用戶寫入的數據首先會放入MemStore,當MemStore滿了之後會Flush成一個StoreFile(底層實現是HFile),當StoreFile文件數量增加到必定閾值,會觸發Compact合併操做,將多個StoreFiles合併成一個StoreFile,合併過程當中會進行版本合併和數據刪除,所以能夠看出HBase其實只有增長數據,全部的更新和刪除操做都是在後續的compact過程當中進行的,這使得用戶的寫操做只要進入內存中就能夠當即返回,保證了HBase I/O的高性能。
1.3 數據版本
Hbase 還能直接檢索到往昔版本的數據,這意味着咱們更新數據時,舊數據並無即時被清除,而是保留着:
Hbase 中經過 row+columns 所指定的一個存貯單元稱爲cell。每一個 cell都保存着同一份數據的多個版本——版本經過時間戳來索引。
時間戳的類型是 64位整型。時間戳能夠由Hbase(在數據寫入時自動 )賦值,此時時間戳是精確到毫秒的當前系統時間。時間戳也能夠由客戶顯式賦值。若是應用程序要避免數據版本衝突,就必須本身生成具備惟一性的時間戳。每一個 cell中,不一樣版本的數據按照時間倒序排序,即最新的數據排在最前面。
爲了不數據存在過多版本形成的的管理 (包括存貯和索引)負擔,Hbase提供了兩種數據版本回收方式。一是保存數據的最後n個版本,二是保存最近一段時間內的版本(好比最近七天)。用戶能夠針對每一個列族進行設置。
2. Node下的使用
HBase的相關操做可參考下表:
在node環境下,可經過 node-hbase 來實現相關訪問和操做,注意該工具包依賴於 PHYTHON2.X(3.X不支持)和Coffee。
若是是在 window 系統下還需依賴 .NET framwork2.0,64位系統可能沒法直接經過安裝包安裝。
官方示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
var assert = require('assert');
var hbase = require('hbase');
hbase({ host: '127.0.0.1', port: 8080 })
.table('my_table' )
//建立一個Column Family
.create('my_column_family', function(err, success){
this.row('my_row') //定位到指定行
.put('my_column_family:my_column', 'my value', function(err, success){
this.get('my_column_family', function(err, cells){
this.exists(function(err, exists){
assert.ok(exists);
});
});
});
});
|
數據檢索:
1
2
3
4
5
6
7
8
|
client
.table('node_table')
.scan({
startRow: 'my_row', //起始行
maxVersions: 1 //版本
}, function(err, rows){
console.log(err, rows);
});
|
另有 hbase-client 也是一個不錯的選擇,具體API參照其文檔。
3. 優缺點
優點
1. 存儲容量大,一個表能夠容納上億行,上百萬列;
2. 可經過版本進行檢索,能搜到所需的歷史版本數據;
3. 負載高時,可經過簡單的添加機器來實現水平切分擴展,跟Hadoop的無縫集成保障了其數據可靠性(HDFS)和海量數據分析的高性能(MapReduce);
4. 在第3點的基礎上可有效避免單點故障的發生。
缺點
1. 基於Java語言實現及Hadoop架構意味着其API更適用於Java項目;
2. node開發環境下所需依賴項較多、配置麻煩(或不知如何配置,如持久化配置),缺少文檔;
3. 佔用內存很大,且鑑於創建在爲批量分析而優化的HDFS上,致使讀取性能不高;
4. API相比其它 NoSql 的相對笨拙。
適用場景
1. bigtable類型的數據存儲;
2. 對數據有版本查詢需求;
3. 應對超大數據量要求擴展簡單的需求。
Redis
Redis 是一個開源的使用ANSI C語言編寫、支持網絡、可基於內存亦可持久化的日誌型、Key-Value數據庫,並提供多種語言的API。目前由VMware主持開發工做。
1. 特色
1.1 數據格式
Redis 一般被稱爲數據結構服務器,由於值(value)能夠是 字符串(String), 哈希(Hash/Map), 列表(list), 集合(sets) 和 有序集合(sorted sets)五種類型,操做很是方便。好比,若是你在作好友系統,查看本身的好友關係,若是採用其餘的key-value系統,則必須把對應的好友拼接成字符串,而後在提取好友時,再把value進行解析,而redis則相對簡單,直接支持list的存儲(採用雙向鏈表或者壓縮鏈表的存儲方式)。
咱們來看下這五種數據類型。
⑴ String
實例:
1
2
3
4
|
redis 127.0.0.1:6379> SET name zfpx
OK
redis 127.0.0.1:6379> GET name
"zfpx"
|
在以上實例中咱們使用了 Redis 的 SET 和 GET 命令。鍵爲 name,對應的值爲」zfpx」。 注意:一個鍵最大能存儲512MB。
⑵ Hash
實例:
1
2
3
4
5
6
7
|
redis 127.0.0.1:6379> HMSET user:1 username zfpx password 123
OK
redis 127.0.0.1:6379> HGETALL user:1
1) "username"
2) "zfpx"
3) "password"
4) "123"
|
以上實例中 hash 數據類型存儲了包含用戶腳本信息的用戶對象。 實例中咱們使用了 Redis HMSET, HGETALL 命令,user:1 爲鍵值。 每一個 hash 能夠存儲 232 – 1 鍵值對(40多億)。
⑶ List
Redis 列表是簡單的字符串列表,按照插入順序排序。你能夠添加一個元素導列表的頭部(左邊)或者尾部(右邊)。
實例:
1
2
3
4
5
6
7
8
9
10
|
redis 127.0.0.1:6379> lpush name zfpx1
(integer) 1
redis 127.0.0.1:6379> lpush name zfpx2
(integer) 2
redis 127.0.0.1:6379> lpush name zfpx3
(integer) 3
redis 127.0.0.1:6379> lrange name 0 -1
1) "zfpx3"
2) "zfpx2"
3) "zfpx1"
|
列表最多可存儲 232 – 1 元素 (4294967295, 每一個列表可存儲40多億)。
⑷ Sets
Redis的Set是string類型的無序集合。 集合是經過哈希表實現的,因此添加,刪除,查找的複雜度都是O(1)。
添加一個string元素到 key 對應的 set 集合中,成功返回1,若是元素已經在集合中返回0,key對應的set不存在返回錯誤,指令格式爲
1
|
sadd key member
|
實例:
1
2
3
4
5
6
7
8
9
10
11
12
|
redis 127.0.0.1:6379> sadd school zfpx1
(integer) 1
redis 127.0.0.1:6379> sadd school zfpx1
(integer) 0
redis 127.0.0.1:6379> sadd school zfpx2
(integer) 1
redis 127.0.0.1:6379> sadd school zfpx2
(integer) 0
redis 127.0.0.1:6379> smembers school
1) "zfpx1"
2) "zfpx2"
|
注意:以上實例中 zfpx1 添加了兩次,但根據集合內元素的惟一性,第二次插入的元素將被忽略。 集合中最大的成員數爲 232 – 1 (4294967295, 每一個集合可存儲40多億個成員)。
⑸ sorted sets/zset
Redis zset 和 set 同樣也是string類型元素的集合,且不容許重複的成員。 不一樣的是每一個元素都會關聯一個double類型的分數。redis正是經過分數來爲集合中的成員進行從小到大的排序。
zset的成員是惟一的,但分數(score)卻能夠重複。能夠經過 zadd 命令(格式以下) 添加元素到集合,若元素在集合中存在則更新對應score
1
|
zadd key score member
|
實例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
redis 127.0.0.1:6379> zadd school 0 zfpx1
(integer) 1
redis 127.0.0.1:6379> zadd school 2 zfpx2
(integer) 1
redis 127.0.0.1:6379> zadd school 0 zfpx3
(integer) 1
redis 127.0.0.1:6379> zadd school 1 zfpx4
(integer) 0
redis 127.0.0.1:6379> ZRANGEBYSCORE school 0 100
1) "zfpx1"
2) "zfpx3"
3) "zfpx4"
4) "zfpx2"
|
1.2 性能
Redis數據庫徹底在內存中,所以處理速度很是快,每秒能執行約11萬集合,每秒約81000+條記錄(測試數據的可參考這篇《Redis千萬級的數據量的性能測試》)。
Redis的數據能確保一致性——全部Redis操做是原子性(Atomicity,意味着操做的不可再分,要麼執行要麼不執行)的,這保證了若是兩個客戶端同時訪問的Redis服務器將得到更新後的值。
1.3 持久化
經過定時快照(snapshot)和基於語句的追加(AppendOnlyFile,aof)兩種方式,redis能夠支持數據持久化——將內存中的數據存儲到磁盤上,方便在宕機等突發狀況下快速恢復。
1.4 CAP類別
屬於CP類型(瞭解更多)。
2. Node下的使用
node 下可以使用 node_redis 來實現 redis 客戶端操做:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
var redis = require("redis"),
client = redis.createClient();
// if you'd like to select database 3, instead of 0 (default), call
// client.select(3, function() { /* ... */ });
client.on("error", function (err) {
console.log("Error " + err);
});
client.set("string key", "string val", redis.print);
client.hset("hash key", "hashtest 1", "some value", redis.print);
client.hset(["hash key", "hashtest 2", "some other value"], redis.print);
client.hkeys("hash key", function (err, replies) {
console.log(replies.length + " replies:");
replies.forEach(function (reply, i) {
console.log(" " + i + ": " + reply);
});
client.quit();
});
|
3. 優缺點
優點
1. 很是豐富的數據結構;
2. Redis提供了事務的功能,能夠保證一串 命令的原子性,中間不會被任何操做打斷;
3. 數據存在內存中,讀寫很是的高速,能夠達到10w/s的頻率。
缺點
1. Redis3.0後纔出來官方的集羣方案,但仍存在一些架構上的問題(出處);
2. 持久化功能體驗不佳——經過快照方法實現的話,須要每隔一段時間將整個數據庫的數據寫到磁盤上,代價很是高;而aof方法只追蹤變化的數據,相似於mysql的binlog方法,但追加log可能過大,同時全部操做均要從新執行一遍,恢復速度慢;
3. 因爲是內存數據庫,因此,單臺機器,存儲的數據量,跟機器自己的內存大小。雖然redis自己有key過時策略,可是仍是須要提早預估和節約內存。若是內存增加過快,須要按期刪除數據。
適用場景
適用於數據變化快且數據庫大小可碰見(適合內存容量)的應用程序。更具體的可參照這篇《Redis 的 5 個常見使用場景》譯文。
MongoDB
MongoDB 是一個高性能,開源,無模式的文檔型數據庫,開發語言是C++。它在許多場景下可用於替代傳統的關係型數據庫或鍵/值存儲方式。
1.特色
1.1 數據格式
在 MongoDB 中,文檔是對數據的抽象,它的表現形式就是咱們常說的 BSON(Binary JSON )。
BSON 是一個輕量級的二進制數據格式。MongoDB 可以使用 BSON,並將 BSON 做爲數據的存儲存放在磁盤中。
BSON 是爲效率而設計的,它只須要使用不多的空間,同時其編碼和解碼都是很是快速的。即便在最壞的狀況下,BSON格式也比JSON格式再最好的狀況下存儲效率高。
對於前端開發者來講,一個「文檔」就至關於一個對象:
1
|
{「name":"mengxiangyue","sex":"nan"}
|
對於文檔是有一些限制的:有序、區分大小寫的,因此下面的兩個文檔是與上面不一樣的:
1
2
|
{」sex「:"nan","name":"mengxiangyue"}
{"Name":"mengxiangyue","sex":"nan"}
|
另外,對於文檔的字段 MongoDB 有以下的限制:
_id必須存在,若是你插入的文檔中沒有該字段,那麼 MongoDB 會爲該文檔建立一個ObjectId做爲其值。_id的值必須在本集合中是惟一的。
多個文檔則組合爲一個「集合」。在 MongoDB 中的集合是無模式的,也就是說集合中存儲的文檔的結構能夠是不一樣的,好比下面的兩個文檔能夠同時存入到一個集合中:
1
2
|
{"name":"mengxiangyue"}
{"Name":"mengxiangyue","sex":"nan"}
|
1.2 性能
MongoDB 目前支持的存儲引擎爲內存映射引擎。當 MongoDB 啓動的時候,會將全部的數據文件映射到內存中,而後操做系統會託管全部的磁盤操做。這種存儲引擎有如下幾種特色:
* MongoDB 中關於內存管理的代碼很是精簡,畢竟相關的工做已經有操做系統進行託管。
* MongoDB 服務器使用的虛擬內存將很是巨大,並將超過整個數據文件的大小。不用擔憂,操做系統會去處理這一切。
在《Mongodb億級數據量的性能測試》一文中,MongoDB 展示了強勁的大數據處理性能(數據甚至比Redis的漂亮的多)。
另外,MongoDB 提供了全索引支持:包括文檔內嵌對象及數組。Mongo的查詢優化器會分析查詢表達式,並生成一個高效的查詢計劃。一般可以極大的提升查詢的效率。
1.3 持久化
MongoDB 在1.8版本以後開始支持 journal,就是咱們常說的 redo log,用於故障恢復和持久化。
當系統啓動時,MongoDB 會將數據文件映射到一塊內存區域,稱之爲Shared view,在不開啓 journal 的系統中,數據直接寫入shared view,而後返回,系統每60s刷新這塊內存到磁盤,這樣,若是斷電或down機,就會丟失不少內存中未持久化的數據。
當系統開啓了 journal 功能,系統會再映射一塊內存區域供 journal 使用,稱之爲 private view,MongoDB 默認每100ms刷新 privateView 到 journal,也就是說,斷電或宕機,有可能丟失這100ms數據,通常都是能夠忍受的,若是不能忍受,那就用程序寫log吧(但開啓journal後使用的虛擬內存是以前的兩倍)。
1.4 CAP類別
MongoDB 比較靈活,能夠設置成 strong consistent (CP類型)或者 eventual consistent(AP類型)。
但其默認是 CP 類型(瞭解更多)。
2. Node下的使用
MongoDB 在 node 環境下的驅動引擎是 node-mongodb-native ,做爲依賴封裝到 mongodb 包裏,咱們直接安裝便可:
1
|
npm install mongodb
|
實例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
var mongodb = require('mongodb');
var mongodbServer = new mongodb.Server('localhost', 27017, { auto_reconnect: true, poolSize: 10 });
var db = new mongodb.Db('mydb', mongodbServer);
/* open db */
db.open(function() {
/* Select 'contact' collection */
db.collection('contact', function(err, collection) {
/* Insert a data */
collection.insert({
name: 'Fred Chien',
email: 'cfsghost@gmail.com',
tel: [
'0926xxx5xx',
'0912xx11xx'
]
}, function(err, data) {
if (data) {
console.log('Successfully Insert');
} else {
console.log('Failed to Insert');
}
});
/* Querying */
collection.find({ name: 'Fred Chien' }, function(err, data) {
/* Found this People */
if (data) {
console.log('Name: ' + data.name + ', email: ' + data.email);
} else {
console.log('Cannot found');
}
});
});
});
|
另外咱們也可使用MongoDB的ODM(面向對象數據庫管理器) —— mongoose 來作數據庫管理,具體參照其API文檔。
3. 優缺點
優點
1. 強大的自動化 shading 功能(更多戳這裏);
2. 全索引支持,查詢很是高效;
3. 面向文檔(BSON)存儲,數據模式簡單而強大。
4. 支持動態查詢,查詢指令也使用JSON形式的標記,可輕易查詢文檔中內嵌的對象及數組。
5. 支持 javascript 表達式查詢,可在服務器端執行任意的 javascript函數。
缺點
1. 單個文檔大小限制爲16M,32位系統上,不支持大於2.5G的數據;
2. 對內存要求比較大,至少要保證熱數據(索引,數據及系統其它開銷)都能裝進內存;
3. 非事務機制,沒法保證事件的原子性。
適用場景
1. 適用於實時的插入、更新與查詢的需求,並具有應用程序實時數據存儲所需的複製及高度伸縮性;
2. 很是適合文檔化格式的存儲及查詢;
3. 高伸縮性的場景:MongoDB 很是適合由數十或者數百臺服務器組成的數據庫。
4. 對性能的關注超過對功能的要求。
Couchbase
本文之因此沒有介紹 CouchDB 或 Membase,是由於它們合併了。合併以後的公司基於 Membase 與 CouchDB 開發了一款新產品,新產品的名字叫作 Couchbase。
Couchbase 能夠說是集合衆家之長,目前應該是最早進的Cache系統,其開發語言是 C/C++。
Couchbase Server 是個面向文檔的數據庫(其所用的技術來自於Apache CouchDB項目),可以實現水平伸縮,而且對於數據的讀寫來講都能提供低延遲的訪問(這要歸功於Membase技術)。
1.特色
1.1 數據格式
Couchbase 跟 MongoDB 同樣都是面向文檔的數據庫,不過在往 Couchbase 插入數據前,須要先創建 bucket —— 能夠把它理解爲「庫」或「表」。
由於 Couchbase 數據基於 Bucket 而致使缺少表結構的邏輯,故若是須要查詢數據,得先創建 view(跟RDBMS的視圖不一樣,view是將數據轉換爲特定格式結構的數據形式如JSON)來執行。
Bucket的意義 —— 在於將數據進行分隔,好比:任何 view 就是基於一個 Bucket 的,僅對 Bucket 內的數據進行處理。一個server上能夠有多個Bucket,每一個Bucket的存儲類型、內容佔用、數據複製數量等,都須要分別指定。從這個意義上看,每一個Bucket都至關於一個獨立的實例。在集羣狀態下,咱們須要對server進行集羣設置,Bucket只側重數據的保管。
每當views創建時, 就會創建indexes, index的更新和以往的數據庫索引更新區別很大。 好比如今有1W數據,更新了200條,索引只須要更新200條,而不須要更新全部數據,map/reduce功能基於index的懶更新行爲,大大得益。
要留意的是,對於全部文件,couchbase 都會創建一個額外的 56byte 的 metadata,這個 metadata 功能之一就是代表數據狀態,是否活動在內存中。同時文件的 key 也做爲標識符和 metadata 一塊兒長期活動在內存中。
1.2 性能
couchbase 的精髓就在於依賴內存最大化下降硬盤I/O對吞吐量的負面影響,因此其讀寫速度很是快,能夠達到亞毫秒級的響應。
couchbase在對數據進行增刪時會先體如今內存中,而不會馬上體如今硬盤上,從內存的修改到硬盤的修改這一步驟是由 couchbase 自動完成,等待執行的硬盤操做會以write queue的形式排隊等待執行,也正是經過這個方法,硬盤的I/O效率在 write queue 滿以前是不會影響 couchbase 的吞吐效率的。
鑑於內存資源確定遠遠少於硬盤資源,因此若是數據量小,那麼所有數據都放在內存上天然是最優選擇,這時候couchbase的效率也是異常高。
可是數據量大的時候過多的數據就會被放在硬盤之中。固然,最終全部數據都會寫入硬盤,不過有些頻繁使用的數據提早放在內存中天然會提升效率。
1.3 持久化
其前身之一 memcached 是徹底不支持持久化的,而 Couchbase 添加了對異步持久化的支持:
Couchbase提供兩種核心類型的buckets —— Couchbase 類型和 Memcached 類型。其中 Couchbase 類型提供了高可用和動態重配置的分佈式數據存儲,提供持久化存儲和複製服務。
Couchbase bucket 具備持久性 —— 數據單元異步從內存寫往磁盤,防範服務重啓或較小的故障發生時數據丟失。持久性屬性是在 bucket 級設置的。
1.4 CAP類型
Couchbase 羣集全部點都是對等的,只是在建立羣或者加入集羣時須要指定一個主節點,一旦結點成功加入集羣,全部的結點對等。
對等網的優勢是,集羣中的任何節點失效,集羣對外提供服務徹底不會中斷,只是集羣的容量受影響。
因爲 couchbase 是對等網集羣,全部的節點均可以同時對客戶端提供服務,這就須要有方法把集羣的節點信息暴露給客戶端,couchbase 提供了一套機制,客戶端能夠獲取全部節點的狀態以及節點的變更,由客戶端根據集羣的當前狀態計算 key 所在的位置。
就上述的介紹,Couchbase 明顯屬於 CP 類型。
2. Node下的使用
Couchbase 對 Node SDK 提供了官方文檔:http://docs.couchbase.com/couchbase-sdk-node-1.2/index.html
實例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
var couchbase = require("couchbase");
var bucket = new couchbase.Connection({
'bucket':'beer-sample',
'host':'127.0.0.1:8091'
}, function(err) {
if (err) {
// Failed to make a connection to the Couchbase cluster.
throw err;
}
// 獲取文檔
bucket.get('aass_brewery-juleol', function(err, result) {
if (err) {
// Failed to retrieve key
throw err;
}
var doc = result.value;
console.log(doc.name + ', ABV: ' + doc.abv);
doc.comment = "Random beer from Norway";
bucket.replace('aass_brewery-juleol', doc, function(err, result) {
if (err) {
// Failed to replace key
throw err;
}
console.log(result);
// Success!
process.exit(0);
});
});
});
|
3. 優缺點
優點
1. 高併發性,高靈活性,高拓展性,容錯性好;
2. 以 vBucket 的概念實現更理想化的自動分片以及動態擴容(瞭解更多);
缺點
1. Couchbase 的存儲方式爲 Key/Value,但 Value 的類型很爲單一,不支持數組。另外也不會自動建立doc id,須要爲每一文檔指定一個用於存儲的 Document Indentifer;
2. 各類組件拼接而成,都是c++實現,致使複雜度太高,遇到奇怪的性能問題排查比較困難,(中文)文檔比較欠缺;
3. 採用緩存所有key的策略,須要大量內存。節點宕機時 failover 過程有不可用時間,而且有部分數據丟失的可能,在高負載系統上有假死現象;
4. 逐漸傾向於閉源,社區版本(免費,但不提供官方維護升級)和商業版本之間差距比較大。
適用場景
1. 適合對讀寫速度要求較高,但服務器負荷和內存花銷可碰見的需求;
2. 須要支持 memcached 協議的需求。
LevelDB
LevelDB 是由谷歌重量級工程師(Jeff Dean 和 Sanjay Ghemawat)開發的開源項目,它是能處理十億級別規模 key-value 型數據持久性存儲的程序庫,開發語言是C++。
除了持久性存儲,LevelDB 還有一個特色是 —— 寫性能遠高於讀性能(固然讀性能也不差)。
1.特色
LevelDB 做爲存儲系統,數據記錄的存儲介質包括內存以及磁盤文件,當LevelDB運行了一段時間,此時咱們給LevelDb進行透視拍照,那麼您會看到以下一番景象:
(圖1)
LevelDB 所寫入的數據會先插入到內存的 Mem Table 中,再由 Mem Table 合併到只讀且鍵值有序的 Disk Table(SSTable) 中,再由後臺線程不時的對 Disk Table 進行歸併。
內存中存在兩個 Mem Table —— 一個是能夠往裏面寫數據的table A,另外一個是正在合併到硬盤的 table B。
Mem Table 用 skiplist 實現,寫數據時,先寫日誌(.log),再往A插入,由於一次寫入操做只涉及一次磁盤順序寫和一次內存寫入,因此這是爲什麼說LevelDb寫入速度極快的主要緣由。若是當B還沒完成合並,而A已經寫滿時,寫操做必須等待。
DiskTable(SSTable,格式爲.sst)是分層的(leveldb的名稱起源),每個大小不超過2M。最早 dump 到硬盤的 SSTable 的層級爲0,層級爲0的 SSTable 的鍵值範圍可能有重疊。若是這樣的 SSTable 太多,那麼每次都須要從多個 SSTable 讀取數據,因此LevelDB 會在適當的時候對 SSTable 進行 Compaction,使得新生成的 SSTable 的鍵值範圍互不重疊。
進行對層級爲 level 的 SSTable 作 Compaction 的時候,取出層級爲 level+1 的且鍵值空間與之重疊的 Table,以順序掃描的方式進行合併。level 爲0的 SSTable 作 Compaction 有些特殊:會取出 level 0 全部重疊的Table與下一層作 Compaction,這樣作保證了對於大於0的層級,每一層裏 SSTable 的鍵值空間是互不重疊的。
SSTable 中的某個文件屬於特定層級,並且其存儲的記錄是 key 有序的,那麼必然有文件中的最小 key 和最大 key,這是很是重要的信息,LevelDB 應該記下這些信息 —— Manifest 就是幹這個的,它記載了 SSTable 各個文件的管理信息,好比屬於哪一個Level,文件名稱叫啥,最小 key 和最大 key 各自是多少。下圖是 Manifest 所存儲內容的示意:
圖中只顯示了兩個文件(Manifest 會記載全部 SSTable 文件的這些信息),即 Level0 的 Test1.sst 和 Test2.sst 文件,同時記載了這些文件各自對應的 key 範圍,好比 Test1.sstt 的 key 範圍是「an」到 「banana」,而文件 Test2.sst 的 key 範圍是「baby」到「samecity」,能夠看出二者的 key 範圍是有重疊的。
那麼上方圖1中的 Current 文件是幹什麼的呢?這個文件的內容只有一個信息,就是記載當前的 Manifest 文件名。由於在 LevleDB 的運行過程當中,隨着 Compaction 的進行,SSTable 文件會發生變化,會有新的文件產生,老的文件被廢棄,Manifest 也會跟着反映這種變化,此時每每會新生成 Manifest 文件來記載這種變化,而 Current 則用來指出哪一個 Manifest 文件纔是咱們關心的那個 Manifest 文件。
注意,鑑於 LevelDB 不屬於分佈式數據庫,故CAP法則在此處不適用。
2. Node下的使用
Node 下可使用 LevelUP 來操做 LevelDB 數據庫:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
var levelup = require('levelup')
// 1) Create our database, supply location and options.
// This will create or open the underlying LevelDB store.
var db = levelup('./mydb')
// 2) put a key & value
db.put('name', 'LevelUP', function (err) {
if (err) return console.log('Ooops!', err) // some kind of I/O error
// 3) fetch by key
db.get('name', function (err, value) {
if (err) return console.log('Ooops!', err) // likely the key was not found
// ta da!
console.log('name=' + value)
})
})
|
LevelUp 的API很是簡潔實用,具體可參考官方文檔。
3. 優缺點
優點
1. 操做接口簡單,基本操做包括寫記錄,讀記錄和刪除記錄,也支持針對多條操做的原子批量操做;
2. 寫入性能遠強於讀取性能,
3. 數據量增大後,讀寫性能降低趨平緩。
缺點
1. 隨機讀性能通常;
2. 對分佈式事務的支持還不成熟。並且機器資源浪費率高。
適應場景
適用於對寫入需求遠大於讀取需求的場景(大部分場景其實都是這樣)。
References
來源:http://blog.jobbole.com/100934/