CouchDB瞭解(-) 特性及實現

概述

CouchDB,大家或多或少都聽說過。它到底有什麼特性,適合哪些應用場景,和我們常用的關係型數據庫有什麼區別? 這些問題,可能我們心裏都不是非常清楚。在以前的Blog中(PS,不是在javaeye哦),我提及了幾次CouchDB,但是僅僅 限於編譯,安裝這些浮在水面上的工作。今天抽出時間把最近關於CouchDB的一些瞭解整理一下。

CouchDB是什麼

CouchDB一種半結構化面向文檔的分佈式,高容錯的數據庫系統,其提供RESTFul HTTP/JSON接口。其擁有MVCC特性,用戶可以通過自定義Map/Reduce函數生成對應的View。

在CouchDB中,數據是以JSON字符的方式存儲在文件中。

特性

  • RESTFul API:HTTP GET/PUT/POST/DELETE + JSON
  • 基於文檔存儲,數據之間沒有關係範式要求
  • 每個數據庫對應單個個文件(以JSON保存),Hot backup
  • MVCC(Multi-Version-Concurrency-Control),讀寫均不鎖定數據庫
  • 用戶自定義View
  • 內建備份機制
  • 支持附件
  • 使用Erlang開發(更多的特性)

應用場景 在我們的生活中,有很多document,比如信件,賬單,筆記等,他們只是簡單的信息,沒有關係的需求,我們可能僅僅需要存儲這些數據。 這樣的情況下,CouchDB應該是很好的選擇。當然其他使用關係型數據庫的環境,也可以使用CouchDB來解決。

 

根據CouchDB的特性,在某些偶 爾連接網絡的應用中,我們可以用CouchDB暫存數據,隨後進行同步。也可以在Cloud環境中,作爲分佈式的數據存儲。CouchDB提供給予 HTTP的API,這樣所有的常見語言都可以使用CouchDB。

 

使用CouchDB,意味着我們不需要在像使用RMDBS一樣,在設計應用前首先設計負責數據Table。我們的開發更加快速,靈活。

實現

在CouchDB中,Database表示一個數據庫,每個Database對應一個"Storage"(後綴爲.couch)以及多個View Index(用來存儲View結果支持query)。

 

Database Storage中可以存儲任意的Document,用戶可以在Database中自定義View,方便對數據進行查詢, View 默認使用JavaScript進行定義,定義好的相關函數保存在 design document中,而View對應的具體數據是保存在View Index文件中。我們可以通過HTTP API請求Database,Document,View,可以進行簡單的Query,以及其他各種系統相關的信息。

Storage File結構

數據庫文件的後綴爲.couch,由Header和Body組成。

Header
包含兩個完全相同的Header信息,每個Header的Size爲2048
Header中前4字節爲magic code:$g, $m, $k, 0
隨後爲header的payload,通過term_to_binary(db_header_record)產生
接下來是填充padding
最後是16字節的摘要信息(md5(payload+padding)產生)
Header總長度爲:單個Header * 2 = 2048
Body
由兩個B+Tree組成,其中一個B+Tree根據Document id進行組織,另一個B+Tree以 seqnum(CouchDB內部使用的序號,用來指示最新Revision的文檔)爲key。
  • fulldocinfo_by_id_btree

    使用Document id作爲key,常用來根據id來查找對應的document,full_doc_info中包含對應document的所有的Revision信息,通過這些信息,我們可以獲取指定Revision的document

  • docinfo_by_seq_btree

    使用seq作爲key,當document被更新時,對應的seq會增加。 具體的document數據(json格式),以及B+Tree混合存儲與這個.couch文件之中。通過B+Tree,我們可以快速的定位到指定的document。

文件結構示意圖

 

所有的更新操作(包括document的創建,修改和刪除)都是以在couch文件尾部追加的方式(即Append方式)進行。我們進行更新時,首 先拷貝原有的數據信息(僅僅針對修改,如果是Create那麼就沒有copy可言了),隨後將其追加到文件的結尾,這個時候就激發B+Tree從leaf 到root的更新過程,更新的Node信息也是採用Append的方式寫入到文件的結尾,到達根節點時,我們將根節點信息寫入到Header中。這樣一次 更新操作涉及1次數據寫入,以及LogN次節點更新,所以其複雜度爲O(logN)

 

因此採用追加的方式,所以在數據庫運行一段時間後,我們需要對其進行“瘦身”,情理那些舊的Document數據。這個過程成爲 Compaction。在Compation的過程,數據庫仍然可用,只是請注意,在Compation的時候,是通過遍歷DBName.couch文 件,將最新的數據拷貝到一個DBName.compat文件中,因此這個過程可能會耗費很大的存儲空間,如果您在系統繁忙(主要是write)的情況下進 行Compation,可能會導致你的硬盤空間耗盡,一定注意哦!

ACID

CouchDB支持ACID特性。Document的更新(add,edit,delete)是順序進行的,但是Database的read爲併發 執行,其不必等待任何其他的read,write的完成。這樣的特性與CouchDB存儲文件的Append增加方式關係密切。

當CouchDB的文檔更新時,爲了保證數據的一致性,Commit分爲以下兩步:

  1. Document數據和index數據首先寫入到disk數據庫文件
  2. 生成兩個連續的頭信息(4kb),隨後寫入數據庫文件

在上面兩個過程中,如果在過程1,發生異常(系統崩潰或斷電),那麼couch文件的頭信息沒有發生變化,那麼所有Append的數據都會被忽略; 如果在過程2發生異常,此時Header可能會發生損壞,我們驗證第一個Header和第二個Header,如果任意一個Header可用,那麼數據庫文 件可用。如果兩個Header都不可用,則對應數據庫文件損壞,拋出異常。

 

一些數據庫系統,爲了實現 Atomic Commit ,提交數據前,將內容寫入到一個rollback log文件,等提交完成後,刪除log文件。

View Server

除了存儲數據,我們還需要依據我們的要求展現數據,乃至一些統計,因此CouchDB中引入了View的概念。View的引入讓CouchDB從一個有趣的文件存儲系統,步入了數據庫的殿堂。也使CouchDB能夠融入到真正的應用環境中。

 

CouchDB中所有的Document都可以具有自己不同的結構,數據,這和關係型數據庫中,嚴格的表結構,嚴格的關聯完全不同。這樣的特點對於數據的備份同步卻非常有好處!

View Model

通過用戶自定義View,我們可以彙集,統計數據,採用一個類似Map/Reduce的過程。這裏的Map將原始的Document進行映射處理,Reduce將Map的中間結果進行重新歸併統計,總而生成最終結果。這裏和並行計算中的Map/Reduce有些不同。

 

CouchDB的View針對每個Database,但是其與Database關聯性不是很大,View是一些用戶自定義函數,處理從數據庫的 Document輸入,產生中間數據(如果沒有reduce過程則爲最終數據),然後再通過Reduce處理中間輸出,產生最終結果。同樣的View可以 使用在不同的Database上。

 

View存儲在design Document中,請注意這裏design Document和View Index是不同的。design Document保存的是view的定義,View Index保存的是針對某個Database進行View操作,產生的結果。

JavaScript View Function

CouchDB內部默認使用JavaScript作爲View的編寫語言,之所以採用Javascript,是和CouchDB面向Web開發相關 的。CouchDB使用Mozilla的spidermonkey作爲JavaScript的解析運行平臺,爲了和Erlang進行交互,其使用c書寫了 一個Port程序couchjs,/server/main.js作爲View Server服務器。

在啓動CouchDB時,通過Command:couchjs main.js即可啓動基於JavaScript的View Server。

View中包含兩個函數:

(map函數,必須)
function(doc) {
  emit(null, doc);
}
(reduce函數,可選)
function (key, values, rereduce) {
   return sum(values);
}

doc ,爲我們數據庫對應的Document,因爲我們採用JSON格式存儲數據,所以Document在 JavaScript中轉化爲Object。`emit(null, doc)`用來生成map的中間結果,其中第一個參數null表示結果的key,第二個參數爲結果的value,上面的例子中我們的結果爲:

null, value1
...
null, valueN

function (key, values, rereduce)中,根據rereduce變量不同這裏有兩種情況:

  1. rereduce爲false
  • key爲array,element爲:[key,id],key爲map function產生的key,id爲Document對應id
  • values爲array,elements爲map function產生的結果
  • 比如 reduce([ [key1,id1], [key2,id2], [key3,id3] ], [value1,value2,value3], false)
  1. rereduce爲true
  • key爲null
  • values爲array,element爲前一次reduce返回的結果
  • 比如reduce(null, [intermediate1,intermediate2,intermediate3], true)

很多時候,我們一次調用reduce就可以生成最終結果,我們會忽略rereduce參數。

 

發佈了10 篇原創文章 · 獲贊 0 · 訪問量 2862
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章