嵌入式數據庫BERKELEY DB 之dubbo實戰

berkeley db 時oracle旗下的一款嵌入式數據庫。。。當然,在互聯網業界,他並不火,但是它確實很省內存,,對於一些想要替換redis的解決方案--可以考慮。。想要學習它。甚至在一些主流的網站上看不到它的相關資料。。。此時我們可以結合官網來進行學習和實踐。本文集合dubbo+berkeley 來闡述下小編的實戰以及需要注意的問題。。。。

1.Berkeley DB簡介

Berkeley DB是一個開源的文件數據庫,介於關係數據庫與內存數據庫之間,使用方式與內存數據庫類似,它提供的是一系列直接訪問數據庫的函數,而不是像關係數據庫那樣需要網絡通訊、SQL解析等步驟。它是一個嵌入式數據庫系統,其設計思想是簡單、小巧、可靠、高性能。可以和Java, C++, Python..以及其他很多語言都有綁定。可以保存任意類型的鍵/值對,而且可以爲一個鍵保存多個數據。Berkeley DB可以支持數千的併發線程同時操作數據庫,支持最大256TB的數據

 

數據結構

Berkeley DB以擁有比SQLServer和Mysql等數據庫系統而言更簡單的體系結構。它是通過進程內的API訪問數據庫。不支持表結構和數據列。訪問數據庫的程序自主決定數據如果存儲在記錄裏。每個記錄只有兩部分:鍵、值,所以通常用key/data pair指代一個記錄。記錄和它的鍵都可以達到4G字節的長度。
支持ACID數據庫事務處理,細粒度鎖,熱備份以及同步複製。
支持HASH、Recno、QUEUE、B+Tree 4種算法

B+樹算法
B+樹是一個平衡樹,關鍵字有序存儲,並且其結構能隨數據的插入和刪除進行動態調整。爲了代碼的簡單,DB沒有實現對關鍵字的前綴碼壓縮。B+樹支持對數據查詢、插入、刪除的常數級速度。關鍵字可以爲任意的數據結構.
HASH算法
DB中實際使用的是擴展線性HASH算法(extended linear hashing),可以根據HASH表的增長進行適當的調整。關鍵字可以爲任意的數據結構。要求每一個記錄都有一個邏輯紀錄號,邏輯紀錄號由算法本身生成。
RECNO算法
Recno建立在B+樹算法之上,提供了一個存儲有序數據的接口。記錄的長度可以爲定長或不定長。 
Queue算法
Queue算法只能存儲定長的記錄,在高的併發處理情況下,Queue算法效率較高;如果是其它情況,則選擇Recno算法,Recno算法把數據存儲爲平面文件格式。和Recno方式接近, 只不過記錄的長度爲定長。數據以定長記錄方式存儲在隊列中,插入操作把記錄插入到隊列的尾部,相比之下插入速度是最快的。

對算法的選擇首先要看關鍵字的類型,如果爲複雜類型,則只能選擇B+樹或HASH算法,如果關鍵字爲邏輯記錄號,則應該選擇Recno或Queue算法。當工作集關鍵字有序時,B+樹算法比較合適;如果工作集比較大且基本上關鍵字爲隨機分佈時,選擇HASH算法。

特點

訪問速度快、省硬盤空間Berkeley DB可以輕鬆支持上千個線程同時訪問數據庫,支持多進程、事務等特性。Berkeley DB運行在大多數的操作系統中,例如大多數的UNIX系統, 和windows系統,以及實時操作系統。

Berkeley DB 還擁有對一些老的UNIX數據庫,例如dbm, ndbm und hsearch的兼容接口.
對於在java系統中的使用,Berkeley DB提供了一個壓縮成jar單個文件的java版本。 這個版本可以運行在java虛擬機上使用,並且擁有和C語言版本相同的所有操作和功能。

Berkeley DB只支持單一的數據結構,它的所有數據包括兩個部分:key 和 data.
Berkeley DB原則上是爲嵌入式數據庫設計的。

 

 

2.JE - Java Edition

Berkeley DB Java Edition (JE)是一個完全用JAVA寫的, 提供DB操作的API集合

Environment 相當於一個磁盤上的目錄路徑,你可以用nvironments找到所有的文件也可以用於管理資源,比如事務。當用一個Environment對象實例打開DB,你的環境實例也可以叫做環境句柄.每次訪問、

                每次存儲、訪問數據都需要打開environment, 在你使用完後需要關閉。

Database environments. Database environments provide a unit of encapsulation and management for one or more databases.
Environment myDbEnv;
EnvironmentConfig envConfig = new EnvironmentConfig();
envConfig.setAllowCreate(true);
myDbEnv = new Environment(new File("/export/dbEnv"), envConfig);

In-memory cache:

你需要謹慎的考慮設置多大的cahce. 如果你設置的太低,將會產生一些不必要的磁盤訪問導致性能下降,如果你設置的太高,可能會浪費本地內存。
注意這個in-memory的最大size設置,在項目啓動的時候,這個緩存將會非常小,一般是最大設置的7%被允許緩存數據。後面會根據你程序的操作增長。
這個緩存不是固定的存在內存中,它可能會被你的系統虛擬內存系統移出內存頁。

CacheMode:

DEFAULT 
          The record's hotness is changed to "most recently used" by the operation where this cache mode is specified.

EVICT_BIN 
          The record LN (leaf node) and its parent BIN (bottom internal node) are evicted as soon as possible after the operation where this cache mode is specified.

EVICT_LN 
          The record LN (leaf node) is evicted as soon as possible after the operation where this cache mode is specified.

KEEP_HOT 
          The record is assigned "maximum hotness" by the operation where this cache mode is specified.

MAKE_COLD 
          The record is assigned "maximum coldness" by the operation where this cache mode is specified.

UNCHANGED 
          The record's hotness or coldness is unchanged by the operation where this cache mode is specified.

兩種API用來提供操作數據庫:

 DPL - Direct Persistence Layer (DPL)API 提供JAVA原始的數據類型直接存入DB, 也可以序列化複雜的JAVA對象
 lower-level JE APIs 可以提供put, get方法。用來獲取數據。

數據存儲結構:

Key-Data Pairs 
DB默認不允許重複的數據,當同樣的KEY+value插入的時候,會用新插入的這條替換原來存在的那條數據。
重複的KEY可以通過配置實現,
如果多條數據的key, 默認值會取第一次插入的那條,除非用遊標把這個key下面的全部數據都取出來。

Secondary Keys:

提供一個選擇去定位信息。通常是引用超過一條數據的case,你可以在存儲汽車的DB中用SK來表示車的顏色或其他。

 

 

3.BDB環境配置參數

環境配置

{
    "envHome":"/opt/data/bdb",
    "databaseName":"cache",
    "permissibleLag":"1",
    "electableGroupSize":"1",
    "permissibleTimeout":"3600",
    "ackTimeout":"5",
    "maxMemoryPercent":"60",
    "maxDiskGB":"140",
    "TTLHour":"72",
    "groups":[
        {
            "groupName":"pg1",
            "port":"5000",
            "nodes":[
                {
                    "nodeName":"n183",
                    "ip":"10.1.10.1",
                    "helperHostPort":"10.1.10.1:5000"
                },
                {
                    "nodeName":"n183s",
                    "ip":"10.1.10.2",
                    "helperHostPort":"10.1.10.1:5000"
                }
            ]
        }
    ]
}

envHome:Environment目錄地址

databaseName:數據庫名

permissibleLag:定義master-slave同步時間

permissibleTimeout:定義同步超時時間

electableGroupSize: 可選舉的組內節點數

ackTimeout:從節點確認超時時間

maxMemoryPercent:使用內存的最大百分比

maxDiskGB:最大磁盤存儲容量GB

TTLHour:數據過期時間

Groups:集羣組

groupName:集羣組名

port: 默認端口

nodes: 集羣內的節點

nodeName:節點名稱

ip: 節點IP

helperHostPort:主從節點通信的IP和端口

 

4.bdb基於虛擬機高可用方案

  • 服務發現&治理

dubbo現有的服務發現框架,集合虛擬機上部署的全部機器,包括機器的IP、端口、狀態。可以對單臺BDB虛擬機進行拉入拉出。同時對2個IDC的機器進行一對一分組。默認先發布的爲master。

監聽服務在BDB服務發佈後開始分別對每個cluster進行監聽,任何的BDB機器被拉出或者掛掉重啓,都自動更新機器的主從狀態到服務列表中。

  • 服務路由

對兩個機房所有機器可見

  • 負載均衡,

對請求的key進行MD5加密後取HashCode, 使其命中服務列表中的某一個cluster獲取cluster內機器的狀態,寫請求只讀取master機器,讀請求會根據2:8的權重規則隨機命中master和slaver節點

  • 主從同步策略

寫入主機器後,會在1S內複製分發到從機器。同時一致率控制在cluster集羣內一半機器成功,則認爲此次插入成功。

  • 集羣容錯

寫入master節點使用Failfast, 寫請求只會執行一次,失敗後立即拋出異常。

讀取採用Failfast   ,讀請求只會執行一次,失敗後立即拋出異常。

集羣環境如下:

 

5 berkeley db 服務監控

 

第三方監控服務:

新啓動一個app ,名爲route。根據下面代碼來啓動一個監聽程序。根據監聽的結果來對集羣內的所有機器的狀態進行動態維護。維護好的節點數據需要存儲起來以提供給客戶端使用。

// 監控配置
MonitorConfig monConfig = new MonitorConfig();

// 監控節點名
monConfig.setNodeName(repConfig.getNodeName());

// 集羣組名
monConfig.setGroupName(repConfig.getGroupName());

// 監控節點的IP+端口
monConfig.setNodeHostPort(repConfig.getNodeHostPort());

// 需要監控的服務IP+端口 
monConfig.setHelperHosts(repConfig.getHelperHosts());
Monitor monitor = new Monitor(monConfig);

// 獲取集羣內的master節點信息
ReplicationNode master = monitor.register();

// 註冊服務狀態變更監聽類

monitor.startListener(new RouterChangeListener());

// 監聽類

class RouterChangeListener implements MonitorChangeListener {
// 新主節點通知
public void notify(NewMasterEvent newMasterEvent)
// group信息變更通知
public void notify(GroupChangeEvent groupChangeEvent)
// 新節點加入group通知
public void notify(JoinGroupEvent event)
// 離開group通知
public void notify(LeaveGroupEvent event) 

}

 

6.異常分類

場景

我們的緩存在生產環境中會遇到網絡故障,或者機器宕機等情況,當發生上述現象的時候程序依然要可以正常提供服務。高可用就是在這種場景下發揮作用。使得服務不受單邊機房或者單臺機器的影響。

 

主從切換:

一般的緩存場景下,高可用可以使用一主一從。配置好helperHost後可以指定某一個IP+端口來用於主從數據流同步。主和從分別部署到OY\RB兩個不同的機房,在第一次啓動某一個機房的一個節點後,會自動成爲主。另外一個機房的節點則變爲從。當你下次發佈或者網絡異常的時候。會根據environment.state的狀態來決定當前節點是否主從。

 

異常情況:

從節點出現異常

當group中的從節點出現異常的時候,我們的客戶端在發起讀請求的時候,如果命中的是這個讀節點,會報出healthcheck failure. 從而集羣中的主節點會繼續提供20%的讀功能。當從節點回復後。會繼續同步主節點的數據到從節點。從而繼續提供服務。

 

主節點出現異常

當主節點出現異常。客戶端發起寫請求會失敗,同時服務監控程序會動態修改主節點的IP地址,這個時候寫入請求會自動遷移到新的主節點上。當老主節點起來後,會自動變爲從節點。而客戶端也會刷新讀/寫節點的IP地址。下次發起請求的時候根據新的節點狀態來請求IP上的服務。

 

從節點出現異常比較久,數據checkpoint已經遠落後master節點

這種情況會拋出一個InsufficientLogException, 這時需要採用一個機制讓slave節點的數據與master保持一致。不能使用正常的數據流自動同步。使用NetworkRestore.execute()會讓slave節點從master上覆制缺失的logfile. 一旦slave節點同步好了必須的log文件,它會自動的重新恢復原來的同步流。

try {
node = new ReplicatedEnvironment(envDir, repConfig, envConfig);
} catch (InsufficientLogException insufficientLogEx) {
NetworkRestore restore = new NetworkRestore();
NetworkRestoreConfig config = new NetworkRestoreConfig();
config.setRetainLogFiles(false); // delete obsolete log files.
// Use the members returned by insufficientLogEx.getLogProviders()
// to select the desired subset of members and pass the resulting
// list as the argument to config.setLogProviders(), if the
// default selection of providers is not suitable.
restore.execute(insufficientLogEx, config);
// retry
node = new ReplicatedEnvironment(envDir, repConfig, envConfig);
} ...

 

7 日誌文件

啓動BDB環境後,會在對應目錄下出現這幾種文件。

je.stat.csv: 環境狀態文件

je.config.csv: 環境配置文件,每一次更新配置都會產生一條新的紀錄

je.lck: 鎖文件,當設置寫/讀訪問權限到JE環境目錄的時候,會創建lck文件同時寫入一些標記到其中。

00000001.jdb: 數據文件,是一個8位的16進制數字,從1開始增長。可以通過配置je.log.fileMax屬性來設置一個文件的最大容量

 

 

8 基於dubbo實現berkeley db代碼實現

 需要注意的是,客戶端需要擴展dubbo的cluster,將dubbo自帶的負載均衡打破,擴展cluster 實現自己的負載策略。。。

 

服務端代碼:

 

1.引入je jar包

 

2.初始化環境

 

 

3. 提供接口

 

路由端代碼:

1.監控部分:

 

 

 

 

2.dubbo SPI 部分擴展

 

 

3.路由負載部分

 

高能注意:

1.不管是monitor node還是data node,名稱一定不要帶特殊符號例如 : xxmonitor-1  ,這種會報錯,小編踩過很多坑  ,,,最好xxmonitor1 這種就好了。。。

2.基於docker的存儲不能存儲文件,需要用虛擬機或者物理機

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章