天天看點

HBase、Redis、MongoDB、Couchbase、LevelDB主流 NoSQL 資料庫的對比(一)

最近小組準備啟動一個 node 開源項目,從前端親和力、大資料下的IO性能、可擴充性幾點入手挑選了 NoSql 資料庫,但具體使用哪一款産品還需要做一次選型。

我們最終把選項範圍縮窄在 HBase、Redis、MongoDB、Couchbase、LevelDB 五款較主流的資料庫産品中,本文将主要對它們進行分析對比。

鑒于缺乏項目中的實戰經驗沉澱,本文内容和觀點主要還是從各平台資料搜羅彙總,也不會有太多深入或底層原理探讨。

本文所引用的資料來源将示于本文尾部。所彙總的内容僅供參考,若有異議望指正。

HBase

HBase 是 Apache Hadoop 中的一個子項目,屬于 bigtable 的開源版本,所實作的語言為Java(故依賴 Java SDK)。HBase 依托于 Hadoop 的 HDFS(分布式檔案系統)作為最基本存儲基礎單元。

HBase在列上實作了 BigTable 論文提到的壓縮算法、記憶體操作和布隆過濾器。HBase的表能夠作為 MapReduce任務的輸入和輸出,可以通過Java API來通路資料,也可以通過REST、Avro或者Thrift的API來通路。

1. 特點

1.1 資料格式

HBash 的資料存儲是基于列(ColumnFamily)的,且非常松散—— 不同于傳統的關系型資料庫(RDBMS),HBase 允許表下某行某列值為空時不做任何存儲(也不占位),減少了空間占用也提高了讀性能。

不過鑒于其它NoSql資料庫也具有同樣靈活的資料存儲結構,該優勢在本次選型中并不出彩。

我們以一個簡單的例子來了解使用 RDBMS 和 HBase 各自的解決方式:

⑴ RDBMS方案:

其中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個版本,二是儲存最近一段時間内的版本(比如最近七天)。使用者可以針對每個列族進行設定。

1.4 CAP類别

屬于CP類型(了解更多)。

2. Node下的使用

HBase的相關操作可參考下表:

在node環境下,可通過 node-hbase 來實作相關通路和操作,注意該工具包依賴于 PHYTHON2.X(3.X不支援)和Coffee。

如果是在 window 系統下還需依賴 .NET framwork2.0,64位系統可能無法直接通過安裝包安裝。

官方示例:

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);

      });

    });

  });

});

資料檢索:

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主持開發工作。

Redis 通常被稱為資料結構伺服器,因為值(value)可以是 字元串(String), 哈希(Hash/Map), 清單(list), 集合(sets) 和 有序集合(sorted sets)五種類型,操作非常友善。比如,如果你在做好友系統,檢視自己的好友關系,如果采用其他的key-value系統,則必須把對應的好友拼接成字元串,然後在提取好友時,再把value進行解析,而redis則相對簡單,直接支援list的存儲(采用雙向連結清單或者壓縮連結清單的存儲方式)。

我們來看下這五種資料類型。

⑴ String

  • string 是 Redis 最基本的類型,你可以了解成與 Memcached 一模一樣的類型,一個key對應一個value。
  • string 類型是二進制安全的。意思是 Redis 的 string 可以包含任何資料。比如 jpg 圖檔或者序列化的對象 。
  • string 類型是 Redis 最基本的資料類型,一個鍵最大能存儲512MB。

執行個體:

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

  • Redis hash 是一個鍵值對集合。
  • Redis hash 是一個 string 類型的 field 和 value 的映射表,hash 特别适合用于存儲對象。

redis 127.0.0.1:6379> HMSET user:1 username zfpx password 123

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 清單是簡單的字元串清單,按照插入順序排序。你可以添加一個元素導清單的頭部(左邊)或者尾部(右邊)。

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不存在傳回錯誤,指令格式為

sadd key member

redis 127.0.0.1:6379> sadd school zfpx1

(integer) 0

redis 127.0.0.1:6379> sadd school zfpx2

redis 127.0.0.1:6379> smembers school

1) "zfpx1"

注意:以上執行個體中 zfpx1 添加了兩次,但根據集合内元素的唯一性,第二次插入的元素将被忽略。 集合中最大的成員數為 232 – 1 (4294967295, 每個集合可存儲40多億個成員)。

⑸ sorted sets/zset

Redis zset 和 set 一樣也是string類型元素的集合,且不允許重複的成員。 不同的是每個元素都會關聯一個double類型的分數。redis正是通過分數來為集合中的成員進行從小到大的排序。

zset的成員是唯一的,但分數(score)卻可以重複。可以通過 zadd 指令(格式如下) 添加元素到集合,若元素在集合中存在則更新對應score

zadd key score member

redis 127.0.0.1:6379> zadd school 0 zfpx1

redis 127.0.0.1:6379> zadd school 2 zfpx2

redis 127.0.0.1:6379> zadd school 0 zfpx3

redis 127.0.0.1:6379> zadd school 1 zfpx4

redis 127.0.0.1:6379> ZRANGEBYSCORE school 0 100

2) "zfpx3"

3) "zfpx4"

4) "zfpx2"

Redis資料庫完全在記憶體中,是以處理速度非常快,每秒能執行約11萬集合,每秒約81000+條記錄(測試資料的可參考這篇《Redis千萬級的資料量的性能測試》)。

Redis的資料能確定一緻性——所有Redis操作是原子性(Atomicity,意味着操作的不可再分,要麼執行要麼不執行)的,這保證了如果兩個用戶端同時通路的Redis伺服器将獲得更新後的值。

1.3 持久化

通過定時快照(snapshot)和基于語句的追加(AppendOnlyFile,aof)兩種方式,redis可以支援資料持久化——将記憶體中的資料存儲到磁盤上,友善在當機等突發情況下快速恢複。

1.4 CAP類别

node 下可使用 node_redis 來實作 redis 用戶端操作:

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();

1. 非常豐富的資料結構;

2. Redis提供了事務的功能,可以保證一串 指令的原子性,中間不會被任何操作打斷;

3. 資料存在記憶體中,讀寫非常的高速,可以達到10w/s的頻率。

1. Redis3.0後才出來官方的叢集方案,但仍存在一些架構上的問題(出處);

2. 持久化功能體驗不佳——通過快照方法實作的話,需要每隔一段時間将整個資料庫的資料寫到磁盤上,代價非常高;而aof方法隻追蹤變化的資料,類似于mysql的binlog方法,但追加log可能過大,同時所有操作均要重新執行一遍,恢複速度慢;

3. 由于是記憶體資料庫,是以,單台機器,存儲的資料量,跟機器本身的記憶體大小。雖然redis本身有key過期政策,但是還是需要提前預估和節約記憶體。如果記憶體增長過快,需要定期删除資料。