kv持久化資料庫
Ⅰ 一般什麼產品或者系統或網站會使用K/V資料庫型資料庫呢
KV型存儲系統是最常用的Nosql存儲系統之一。Memcached和Redis是其最具代表的兩個產品。本文將詳細介紹Memcached和Redis的常用場景及如何構建一個高可用和自動彈性伸縮的KV存儲系統。
Cache加DB是最常見的存儲層架構。時間局部性原理指出正在被訪問的數據很可能會在近期再次被訪問。根據這一原理應用程序將最近訪問過的數據保存在Cache中,每次讀取請求首先訪問Cache,若Cache中保存有該數據則直接獲取數據返回給前端。若Cache中該數據不存在則從DB獲取數據並將該數據保存到Cache;若數據被更新或刪除則將Cache中對應數據置為失效。使用Cache能夠很好地緩解DB的讀請求壓力。KV存儲系統既可以應用在Cache層也可以應用在DB層。
Memcached使用內存作為存儲介質,因為內存數據的易失性Memcached主要應用在Cache層。Memcached常見的應用場景是存儲一些讀取頻繁但更新較少的數據,如靜態網頁、系統配置及規則數據、活躍用戶的基本數據和個性化定製數據、准實時統計信息等。並不是所有場景都適合Memcached加DB的架構,在某些場景下這一架構存在一些局限。例如這一架構不能提升寫的性能,寫數據時還是數據直接存儲到DB,同時需要將Cache中數據置為失效,所以對以寫請求為主的應用使用Cache提升性能的效果並不是很明顯。如果應用的熱點數據或者活躍用戶分布較為分散也會降低Cache的命中率。如果遇到機器宕機,內存數據會丟失,那麼機器重啟後需要一段時間重新建立熱點數據,建立熱點數據的過程中會對DB會造成較大的壓力,嚴重時會導致系統雪崩。
相比Memcached,Redis做了一些優化。首先,Redis對數據做了持久化,支持AOF和RDB兩種持久化方式,機器重啟後能通過持久化數據自動重建內存。其次,Redis支持主從復制,主機會自動將數據同步到從機,可以進行讀寫分離,主機負責寫操作,從機負責讀操作。那樣既增加了系統的讀寫性能又提升了數據的可靠性。再次,Redis除了支持string類型的value外還支持string、hash、set、sorted set、list等類型的數據結構。因此,Redis既可以應用在Cache層,也可以替換或者部分替換DB存儲持久化數據。使用Redis作為Cache時機器宕機後熱點數據不會丟失,無須像Memcached一樣重建熱點數據。相比Cache加DB的架構方式,使用Redis存儲持久化數據不僅能夠提升讀性能,還能提升寫性能,而且不存在熱點數據分布是否集中而影響命中率的問題。Redis豐富的數據結構也使其擁有更加豐富的應用場景。Redis的命令都是原子性的,可以簡單地利用INCR和DECR實現計數功能。使用list可以實現獲取最近N個數的操作。sort set支持對數據排序,可以應用在排行榜中。set集合可以應用到數據排重。Redis還支持過期時間設置,可以應用到需要設定精確過期時間的應用。只要可以使用Redis支持的數據結構表示的場景,就可以使用Redis進行存儲。但Redis不是萬能的,它不支持關系型資料庫復雜的SQL操作。某些場景下,可結合Redis和關系型DB,將簡單查詢相關的數據保存在Redis中,復雜SQL操作由關系型DB完成。
雖然Redis集很多優點於一身,但在實際運營中也存在一些問題。首先,Redis不具備自動容錯和恢復功能,主機從機的宕機都會導致前端部分讀寫請求失敗,需要等待機器重啟或者手動切換前端的IP才能恢復。如果主機宕機,宕機前有部分數據未能及時同步到從機,切換IP後還會引入數據不一致的問題,降低了系統的可用性。其次,Redis的主從復制採用全量復制,復制過程中主機會fork出一個子進程對內存做一份快照,並將子進程的內存快照保持為文件發送給從機,這一過程需要確保主機有足夠多的空餘內存。若快照文件較大,對集群的服務能力會產生較大的影響,而且復制過程是在從機新加入集群或者從機和主機網路斷開重連時都會進行,也就是網路波動都會造成主機和從機間的一次全量的數據復制,這對實際的系統運營造成了不小的麻煩。最後,Redis較難支持在線擴容,在集群容量達到上限時在線擴容會變得很復雜。為避免這一問題,運維人員在系統上線時必須確保有足夠的空間,這對資源造成了很大的浪費。
Ⅱ 剛剛問我,redis持久化數據到資料庫是怎麼操作的
1、 快照的方式持久化到磁碟
自動持久化規則配置
save 900 1
save 300 10
save 60 10000
上面的配置規則意思如下:
# In the example below the behaviour will be to save:
# after 900 sec (15 min) if at least 1 key changed
# after 300 sec (5 min) if at least 10 keys changed
# after 60 sec if at least 10000 keys changed
redis也可以關閉自動持久化,注釋掉這些save配置,或者save 「」
如果後台保存到磁碟發生錯誤,將停止寫操作.
stop-writes-on-bgsave-error yes
使用LZF壓縮rdb文件,這會耗CPU, 但是可以減少磁碟佔用.
rdbcompression yes
保存rdb和載入rdb文件的時候檢驗,可以防止錯誤,但是要付出約10%的性能,可以關閉他,提高性能。
rdbchecksum yes
導出的rdb文件名
dbfilename mp.rdb
設置工作目錄, rdb文件會寫到該目錄, append only file也會存儲在該目錄下.
dir ./
Redis自動快照保存到磁碟或者調用bgsave,是後台進程完成的,其他客戶端仍然和可以讀寫redis伺服器,後台保存快照到磁碟會佔用大量內存。調用save保存內存中的數據到磁碟,將阻塞客戶端請求,直到保存完畢。
調用shutdown命令,Redis伺服器會先調用save,所有數據持久化到磁碟之後才會真正退出。
對於數據丟失的問題:
如果伺服器crash,從上一次快照之後的數據將全部丟失。所以在設置保存規則的時候,要根據實際業務設置允許的范圍。
如果對於數據敏感的業務,在程序中要使用恰當的日誌,在伺服器crash之後,通過日誌恢復數據。
2、 Append-only file 的方式持久化
另外一種方式為遞增的方式,將會引起數據變化的操作, 持久化到文件中, 重啟redis的時候,通過操作命令,恢復數據.
每次執行寫操作命令之後,都會將數據寫到server.aofbuf中。
# appendfsync always
appendfsync everysec
# appendfsync no
當配置為always的時候,每次server.aofbuf中的數據寫入到文件之後,才會返回給客戶端,這樣可以保證數據不丟,但是頻繁的IO操作,會降低性能。
everysec每秒寫一次,這可能會丟失一秒內的操作。
aof最大的問題就是隨著時間append file會變的很大,所以我們需要bgrewriteaof命令重新整理文件,只保留最新的kv數據。
Ⅲ ios leveldb怎麼打開資料庫
下文例子中演示了如何插入、獲取、刪除一條記錄
LevelDB 簡介
一、LevelDB入門
LevelDB是Google開源的持久化KV單機資料庫,具有很高的隨機寫,順序讀/寫性能,但是隨機讀的性能很一般,也就是說,LevelDB很適合應用在查詢較少,而寫很多的場景。LevelDB應用了LSM (Log Structured Merge) 策略,lsm_tree對索引變更進行延遲及批量處理,並通過一種類似於歸並排序的方式高效地將更新遷移到磁碟,降低索引插入開銷,關於LSM,本文在後面也會簡單提及。
根據LevelDB官方網站的描述,LevelDB的特點和限制如下:
特點:
1、key和value都是任意長度的位元組數組;
2、entry(即一條K-V記錄)默認是按照key的字典順序存儲的,當然開發者也可以重載這個排序函數;
3、提供的基本操作介面:Put()、Delete()、Get()、Batch();
4、支持批量操作以原子操作進行;
5、可以創建數據全景的snapshot(快照),並允許在快照中查找數據;
6、可以通過前向(或後向)迭代器遍歷數據(迭代器會隱含的創建一個snapshot);
7、自動使用Snappy壓縮數據;
8、可移植性;
限制:
1、非關系型數據模型(NoSQL),不支持sql語句,也不支持索引;
2、一次只允許一個進程訪問一個特定的資料庫;
3、沒有內置的C/S架構,但開發者可以使用LevelDB庫自己封裝一個server;
LevelDB本身只是一個lib庫,在源碼目錄make編譯即可,然後在我們的應用程序裡面可以直接include leveldb/include/db.h頭文件,該頭文件有幾個基本的資料庫操作介面,下面是一個測試例子:
#include <iostream>
#include <string>
#include <assert.h>
#include "leveldb/db.h"
using namespace std;
int main(void)
{
leveldb::DB *db;
leveldb::Options options;
options.create_if_missing = true;
// open
leveldb::Status status = leveldb::DB::Open(options,"/tmp/testdb", &db);
assert(status.ok());
string key = "name";
string value = "chenqi";
// write
status = db->Put(leveldb::WriteOptions(), key, value);
assert(status.ok());
// read
status = db->Get(leveldb::ReadOptions(), key, &value);
assert(status.ok());
cout<<value<<endl;
// delete
status = db->Delete(leveldb::WriteOptions(), key);
assert(status.ok());
status = db->Get(leveldb::ReadOptions(),key, &value);
if(!status.ok()) {
cerr<<key<<" "<<status.ToString()<<endl;
} else {
cout<<key<<"==="<<value<<endl;
}
// close
delete db;
return 0;
}
上面的例子演示了如何插入、獲取、刪除一條記錄,編譯代碼:
g++ -o test test.cpp libleveldb.a -lpthread -Iinclude
執行./test後,會在/tmp下面生成一個目錄testdb,裡麵包含若干文件:
------------------------------------------------------------
LevelDB是google開源的一個key-value存儲引擎庫,類似於開源的Lucene索引庫一樣。其他的軟體開發者可以利用該庫做二次開發,來滿足定製需求。LevelDB採用日誌式的寫方式來提高寫性能,但是犧牲了部分讀性能。為了彌補犧牲了的讀性能,一些人提議使用SSD作為存儲介質。
對於本地化的Key-value存儲引擎來說,簡單的使用一般都分成三個基本的步驟:(1)打開一個資料庫實例;(2)對這個資料庫實例進行插入,修改和查詢操作;(3)最後在使用完成之後,關閉該資料庫。下面將詳細討論該三個步驟:
一、打開一個資料庫實例
一個leveldb資料庫有一個對應一個文件系統目錄的名字。該資料庫的所有內容都存儲在這個目錄下。下面的代碼描述了怎樣打開一個資料庫或者建立一個新的資料庫。
#include <assert.h>
#include "leveldb/db.h"
leveldb::DB* db;
leveldb::Options options;
options.create_if_missing = true;
leveldb::Status status = leveldb::DB::Open(options,"/tmp/testdb", &db);
assert(status.ok());
如果打開已存在資料庫的時候,需要拋出錯誤。將以下代碼插在leveldb::DB::Open方法前面:
options.error_if_exists = true;
二、對資料庫的簡單讀、寫操作
LevelDB提供了Put,Delete和Get三個方法對資料庫進行修改和查詢。例如,下面的代碼片段描述了怎樣將key1對應的value值,移到key2對應的值。
std::string value;
leveldb::Status s = db->Get(leveldb::ReadOptions(), key1, &value);
if(s.ok()) s = db->Put(leveldb::WriteOptions(), key2, value);
if(s.ok()) s = db->Delete(leveldb::WriteOptions(), key1);
三、關閉資料庫
在對資料庫進行了一系列的操作之後,需要對資料庫進行關閉。該操作比較簡單:
... open the db as described above...
... do something with db ...
delete db;
上面對levelDB的簡單使用做了基本的介紹,接下來就是如何自己寫一個完成並且能運行的例子。
1、下載源碼 git clone https://code.google.com/p/leveldb/
2、編譯源碼 cd leveldb && make all
3、編寫test.cpp
#include <assert.h>
#include <string.h>
#include <leveldb/db.h>
#include <iostream>
int main(){
leveldb::DB* db;
leveldb::Options options;
options.create_if_missing = true;
leveldb::Status status = leveldb::DB::Open(options,"/tmp/testdb", &db);
assert(status.ok());
//write key1,value1
std::string key="key";
std::string value = "value";
status = db->Put(leveldb::WriteOptions(), key,value);
assert(status.ok());
status = db->Get(leveldb::ReadOptions(), key, &value);
assert(status.ok());
std::cout<<value<<std::endl;
std::string key2 = "key2";
//move the value under key to key2
status = db->Put(leveldb::WriteOptions(),key2,value);
assert(status.ok());
status = db->Delete(leveldb::WriteOptions(), key);
assert(status.ok());
status = db->Get(leveldb::ReadOptions(),key2, &value);
assert(status.ok());
std::cout<<key2<<"==="<<value<<std::endl;
status = db->Get(leveldb::ReadOptions(),key, &value);
if(!status.ok()) std::cerr<<key<<" "<<status.ToString()<<std::endl;
else std::cout<<key<<"==="<<value<<std::endl;
delete db;
return 0;
}
4、編譯鏈接 g++ -o test test.cpp ../leveldb/libleveldb.a -lpthread -I../leveldb/include
注意libleveldb.a 和leveldb include的路徑。
5、運行結果./test:
value
key2===value
key NotFound:
Ⅳ B站分布式KV存儲實踐
在B站的業務場景中,存在很多種不同模型的數據,有些數據關系比較復雜像:賬號、稿件信息。有些數據關系比較簡單,只需要簡單的kv模型即可滿足。此外,又存在某些讀寫吞吐比較高的業務場景,該場景早期的解決方案是通過MySQL來進行數據的持久化存儲,同時通過redis來提升訪問的速度與吞吐。但是這種模式帶來了兩個問題,其一是存儲與緩存一致性的問題,該問題在B站通過canal非同步更新緩存的方式得以解決,其二則是開發的復雜度,對於這樣一套存儲系統,每個業務都需要額外維護一個任務腳本來消費canal數據進行緩存數據的更新。基於這種場景,業務需要的其實是一個介於Redis與MySQL之間的提供持久化高性能的kv存儲。此外對象存儲的元數據,對數據的一致性、可靠性與擴展性有著很高的要求。
基於此背景,我們對自研KV的定位從一開始就是構建一個高可靠、高可用、高性能、高拓展的系統。對於存儲系統,核心是保證數據的可靠性,當數據不可靠時提供再高的可用性也是沒用的。可靠性的一個核心因素就是數據的多副本容災,通過raft一致性協議保證多副本數據的一致性。
分布式系統,如何對數據進行分片放置,業界通常有兩種做法,一是基於hash進行分區,二是基於range進行分區,兩種方式各有優缺點。hash分區,可以有效防止熱點問題,但是由於key是hash以後放置的,無法保證key的全局有序。range分區,由於相鄰的數據都放在一起,因此可以保證數據的有序,但是同時也可能帶來寫入熱點的問題。基於B站的業務場景,我們同時支持了range分區和hash分區,業務接入的時候可以根據業務特性進行選擇。大部分場景,並不需要全局有序,所以默認推薦hash分區的接入方式,比如觀看記錄、用戶動態這些場景,只需要保證同一個用戶維度的數據有序即可,同一個用戶維度的數據可以通過hashtag的方式保證局部有序。
整個系統核心分為三個組件:
Metaserver用戶集群元信息的管理,包括對kv節點的健康監測、故障轉移以及負載均衡。
Node為kv數據存儲節點,用於實際存儲kv數據,每個Node上保存數據的一個副本,不同Node之間的分片副本通過raft保證數據的一致性,並選出主節點對外提供讀寫,業務也可以根據對數據一致性的需求指定是否允許讀從節點,在對數據一致性要求不高的場景時,通過設置允許讀從節點可以提高可用性以及降低長尾。
Client模塊為用戶訪問入口,對外提供了兩種接入方式,一種是通過proxy模式的方式進行接入,另一種是通過原生的SDK直接訪問,proxy本身也是封裝自c++的原生SDK。SDK從Metaserver獲取表的元數據分布信息,根據元數據信息決定將用戶請求具體發送到哪個對應的Node節點。同時為了保證高可用,SDK還實現了重試機制以及backoff請求。
集群的拓撲結構包含了幾個概念,分別是Pool、Zone、Node、Table、Shard 與Replica。
基於不同的業務場景,我們同時支持了range分區和hash分區。對於range場景,隨著用戶數據的增長,需要對分區數據進行分裂遷移。對於hash分區的場景,使用上通常會根據業務的數據量做幾倍的冗餘預估,然後創建合適的分片數。但是即便是幾倍的冗餘預估,由於業務發展速度的不可預測,也很容易出現實際使用遠超預估的場景,從而導致單個數據分片過大。
之所以不在一開始就創建足夠的分片數有兩個原因:其一,由於每一個replica都包含一個獨立的engine,過多的分片會導致數據文件過多,同時對於批量寫入場景存在一定的寫扇出放大。其二,每一個shard都是一組raftgroup,過多的raft心跳會對服務造成額外的開銷,這一點後續我們會考慮基於節點做心跳合並優化減少集群心跳數。
為了滿足業務的需求場景,我們同時支持了range和hash兩種模式下的分裂。兩種模式分裂流程類似,下面以hash為例進行說明。
hash模式下的分裂為直接根據當前分片數進行倍增。分裂的流程主要涉及三個模塊的交互。
metaserver
分裂時,metaserver會根據當前分片數計算出目標分片數,並且下發創建replica指令到對應的Node節點,同時更新shard分布信息,唯一不同的是,處於分裂中的shard狀態為splitting。該狀態用於client流量請求路由識別。當Node完成數據分裂以後上報metaserver,metaserver更新shard狀態為normal從而完成分裂。
Node
node收到分裂請求以後,會根據需要分裂的分片id在原地拉起創建一個新的分片。然後對舊分片的數據進行checkpoint,同時記錄舊分片checkpoint對應的logid。新分片創建完成後,會直接從舊分片的checkpoint進行open,然後在非同步復制logid之後的數據保證數據的一致性。新分片載入完checkpoint後,原來的舊分片會向raftgroup提交一條分裂完成日誌,該日誌處理流程與普通raft日誌一致。分裂完成後上報分裂狀態到metaserver,同時舊分片開始拒絕不再屬於自己分片的數據寫入,client收到分片錯誤以後會請求metaserver更新shard分布。
完成分裂以後的兩個分片擁有的兩倍冗餘數據,這些數據會在engine compaction的時候根據compaction_filter過濾進行刪除。
Client
用戶請求時,根據hash(key) % shard_cnt 獲取目標分片。表分裂期間,該shard_cnt表示分裂完成後的最終分片數。以上圖3分片的分裂為例:
hash(key) = 4, 分裂前shard_cnt為3,因此該請求會被發送到shard1. 分裂期間,由於shard_cnt變為6,因此目標分片應該是shard4, 但是由於shard4為splitting,因此client會重新計算分片從而將請求繼續發送給shard1. 等到最終分裂完成後,shard4狀態變更為Normal,請求才會被發送到shard4.
分裂期間,如果Node返回分片信息錯誤,那麼client會請求metaserver更新分片分布信息。
類似於MySQL的binlog,我們基於raftlog日誌實現了kv的binlog. 業務可以根據binlog進行實時的事件流訂閱,同時為了滿足事件流回溯的需求,我們還對binlog數據進行冷備。通過將binlog冷備到對象存儲,滿足了部分場景需要回溯較長事件記錄的需求。
直接復用raftlog作為用戶行為的binlog,可以減少binlog產生的額外寫放大,唯一需要處理的是過濾raft本身的配置變更信息。learner通過實時監聽不斷拉取分片產生的binlog到本地並解析。根據learner配置信息決定將數據同步到對應的下游。同時binlog數據還會被非同步備份到對象存儲,當業務需要回溯較長時間的事件流的時候,可以直接指定位置從S3拉取歷史binlog進行解析。
基於上述提到的binlog能力,我們還基於此實現了kv的多活。learner模塊會實時將用戶寫入的數據同步到跨數據中心的其他kv集群。對於跨數據中心部署的業務,業務可以選擇就近的kv集群進行讀取訪問,降低訪問延時。
kv的多活分為讀多活和寫多活。對於讀多活,機房A的寫入會被非同步復制到機房B,機房B的服務可以直接讀取本機房的數據,該模式下只有機房A的kv可以寫入。對於寫多活,kv在機房A B 都能同時提供寫入並且進行雙向同步,但是為了保證數據的一致性,需要業務上做數據的單元化寫入,保證兩個機房不會同時修改同一條記錄。通過將用戶劃分單元,提供了寫多活的能力。通過對binlog數據打標,解決了雙向同步時候的數據回環問題。
對於用戶畫像和特徵引擎等場景,需要將離線生成的大量數據快速導入KV存儲系統提供用戶讀取訪問。傳統的寫入方式是根據生成的數據記錄一條條寫入kv存儲,這樣帶來兩個問題。其一,大批量寫入會對kv造成額外的負載與寫入帶寬放大造成浪費。其次,由於寫入量巨大,每次導入需要花費較長的時間。為了減少寫入放大以及導入提速,我們支持了bulk load的能力。離線平台只需要根據kv的存儲格式離線生成對應的SST文件,然後上傳到對象存儲服務。kv直接從對象存儲拉取SST文件到本地,然後直接載入SST文件即可對外提供讀服務。bulk load的另外一個好處是可以直接在生成SST後離線進行compaction,將compaction的負載offload到離線的同時也降低了空間的放大。
由於LSM tree的寫入特性,數據需要被不斷的compaction到更底層的level。在compaction時,如果該key還有效,那麼會被寫入到更底層的level里,如果該key已經被刪除,那麼會判斷當前level是否是最底層的,一條被刪除的key,會被標記為刪除,直到被compaction到最底層level的時候才會被真正刪除。compaction的時候會帶來額外的寫放大,尤其當value比較大的時候,會造成巨大的帶寬浪費。為了降低寫放大,我們參考了Bitcask實現了kv分離的存儲引擎sparrowdb.
sparrowdb 介紹
用戶寫入的時候,value通過append only的方式寫入data文件,然後更新索引信息,索引的value包含實際數據所在的data文件id,value大小以及position信息,同時data文件也會包含索引信息。與原始的bitcask實現不一樣的是,我們將索引信息保存在 rocksdb。
更新寫入的時候,只需要更新對應的索引即可。compaction的時候,只需將索引寫入底層的level,而無需進行data的拷貝寫入。對於已經失效的data,通過後台線程進行檢查,當發現data文件里的索引與rocksdb保存的索引不一致的時候,說明該data已經被刪除或更新,數據可以被回收淘汰。
使用kv存儲分離降低了寫放大的問題,但是由於kv分離存儲,會導致讀的時候多了一次io,讀請求需要先根據key讀到索引信息,再根據索引信息去對應的文件讀取data數據。為了降低讀訪問的開銷,我們針對value比較小的數據進行了inline,只有當value超過一定閾值的時候才會被分離存儲到data文件。通過inline以及kv分離獲取讀性能與寫放大之間的平衡。
在分布式系統中,負載均衡是繞不過去的問題。一個好的負載均衡策略可以防止機器資源的空閑浪費。同時通過負載均衡,可以防止流量傾斜導致部分節點負載過高從而影響請求質量。對於存儲系統,負載均衡不僅涉及到磁碟的空間,也涉及到機器的內存、cpu、磁碟io等。同時由於使用raft進行主從選主,保證主節點盡可能的打散也是均衡需要考慮的問題。
副本均衡
由於設計上我們會盡量保證每個副本的大小盡量相等,因此對於空間的負載其實可以等價為每塊磁碟的副本數。創建副本時,會從可用的zone中尋找包含副本數最少的節點進行創建。同時考慮到不同業務類型的副本讀寫吞吐可能不一樣導致CPU負載不一致,在挑選副本的時候會進一步檢查當前節點的負載情況,如果當前節點負載超過閾值,則跳過該節點繼續選擇其他合適的節點。目前基於最少副本數以及負載校驗基本可以做到集群內部的節點負載均衡。
當出現負載傾斜時,則從負載較高的節點選擇副本進行遷出,從集群中尋找負載最低的節點作為待遷入節點。當出現節點故障下線以及新機器資源加入的時候,也是基於均值計算待遷出以及遷入節點進行均衡。
主從均衡
雖然通過最少副本數策略保證了節點副本數的均衡,但是由於raft選主的性質,可能出現主節點都集中在部分少數節點的情況。由於只有主節點對外提供寫入,主節點的傾斜也會導致負載的不均衡。為了保證主節點的均衡,Node節點會定期向metaserver上報當前節點上副本的主從信息。
主從均衡基於表維度進行操作。metaserver會根據表在Node的分布信息進行副本數的計算。主副本的數量基於最樸素簡單的數學期望進行計算: 主副本期望值 = 節點副本數 / 分片副本數。下面為一個簡單的例子:
假設表a包含10個shard,每個shard 3個replica。在節點A、B、C、D的分布為 10、5、6、9. 那麼A、B、C、D的主副本數期望值應該為 3、1、2、3. 如果節點數實際的主副本數少於期望值,那麼被放入待遷入區,如果大於期望值,那麼被放入待遷出區。同時通過添加誤差值來避免頻繁的遷入遷出。只要節點的實際主副本數處於 [x-δx,x+δx] 則表示主副本數處於穩定期間,x、δx 分別表示期望值和誤差值。
需要注意的是,當對raft進行主從切換的時候,從節點需要追上所有已提交的日誌以後才能成功選為主,如果有節點落後的時候進行主從切換,那麼可能導致由於追數據產生的一段時間無主的情況。因此在做主從切換的時候必須要檢查主從的日誌復制狀態,當存在慢節點的時候禁止進行切換。
3.7 故障檢測&修復
一個小概率的事件,隨著規模的變大,也會變成大概率的事件。分布式系統下,隨著集群規模的變大,機器的故障將變得愈發頻繁。因此如何對故障進行自動檢測容災修復也是分布式系統的核心問題。故障的容災主要通過多副本raft來保證,那麼如何進行故障的自動發現與修復呢。
健康監測
metaserver會定期向node節點發送心跳檢查node的健康狀態,如果node出現故障不可達,那麼metaserver會將node標記為故障狀態並剔除,同時將node上原來的replica遷移到其他健康的節點。
為了防止部分node和metaserver之間部分網路隔離的情況下node節點被誤剔除,我們添加了心跳轉發的功能。上圖中三個node節點對於客戶端都是正常的,但是node3由於網路隔離與metaserver不可達了,如果metaserver此時直接剔除node3會造成節點無必要的剔除操作。通過node2轉發心跳探測node3的狀態避免了誤剔除操作。
除了對節點的狀態進行檢測外,node節點本身還會檢查磁碟信息並進行上報,當出現磁碟異常時上報異常磁碟信息並進行踢盤。磁碟的異常主要通過dmesg日誌進行採集分析。
故障修復
當出現磁碟節點故障時,需要將原有故障設備的replica遷移到其他健康節點,metaserver根據負載均衡策略選擇合適的node並創建新replica, 新創建的replica會被加入原有shard的raft group並從leader復制快照數據,復制完快照以後成功加入raft group完成故障replica的修復。
故障的修復主要涉及快照的復制。每一個replica會定期創建快照刪除舊的raftlog,快照信息為完整的rocksdb checkpoint。通過快照進行修復時,只需要拷貝checkpoint下的所有文件即可。通過直接拷貝文件可以大幅減少快照修復的時間。需要注意的是快照拷貝也需要進行io限速,防止文件拷貝影響在線io.
過期數據淘汰
在很多業務場景中,業務的數據只需要存儲一段時間,過期後數據即可以自動刪除清理,為了支持這個功能,我們通過在value上添加額外的ttl信息,並在compaction的時候通過compaction_filter進行過期數據的淘汰。level之間的容量呈指數增長,因此rocksdb越底層能容納越多的數據,隨著時間的推移,很多數據都會被移動到底層,但是由於底層的容量比較大,很難觸發compaction,這就導致很多已經過期的數據沒法被及時淘汰從而導致了空間放大。與此同時,大量的過期數據也會對scan的性能造成影響。這個問題可以通過設置periodic_compaction_seconds 來解決,通過設置周期性的compaction來觸發過期數據的回收。
scan慢查詢
除了上面提到的存在大批過期數據的時候可能導致的scan慢查詢,如果業務存在大批量的刪除,也可能導致scan的時候出現慢查詢。因為delete對於rocksdb本質也是一條append操作,delete寫入會被添加刪除標記,只有等到該記錄被compaction移動到最底層後該標記才會被真正刪除。帶來的一個問題是如果用戶scan的數據區間剛好存在大量的delete標記,那麼iterator需要迭代過濾這些標記直到找到有效數據從而導致慢查詢。該問題可以通過添加 CompactOnDeletionCollector 來解決。當memtable flush或者sst compaction的時候,collector會統計當前key被刪除的比例,通過設置合理的 deletion_trigger ,當發現被delete的key數量超過閾值的時候主動觸發compaction。
delay compaction
通過設置 CompactOnDeletionCollector 解決了delete導致的慢查詢問題。但是對於某些業務場景,卻會到來嚴重的寫放大。當L0被compaction到L1時候,由於閾值超過deletion_trigger ,會導致L1被添加到compaction隊列,由於業務的數據特性,L1和L2存在大量重疊的數據區間,導致每次L1的compaction會同時帶上大量的L2文件造成巨大的寫放大。為了解決這個問題,我們對這種特性的業務數據禁用了CompactOnDeletionCollector 。通過設置表級別參數來控製表級別的compaction策略。後續會考慮優化delete trigger的時機,通過只在指定層級觸發來避免大量的io放大。
compaction限速
由於rocksdb的compaction會造成大量的io讀寫,如果不對compaction的io進行限速,那麼很可能影響到在線的寫入。但是限速具體配置多少比較合適其實很難確定,配置大了影響在線業務,配置小了又會導致低峰期帶寬浪費。基於此rocksdb 在5.9以後為 NewGenericRateLimiter 添加了 auto_tuned 參數,可以根據當前負載自適應調整限速。需要注意的是,該函數還有一個參數 RateLimiter::Mode 用來限制操作類型,默認值為 kWritesOnly,通常情況該模式不會有問題,但是如果業務存在大量被刪除的數據,只限制寫可能會導致compaction的時候造成大量的讀io。
關閉WAL
由於raft log本身已經可以保證數據的可靠性,因此寫入rocksdb的時候可以關閉wal減少磁碟io,節點重啟的時候根據rocksdb里保存的last_apply_id從raft log進行狀態機回放即可。
降副本容災
對於三副本的raft group,單副本故障並不會影響服務的可用性,即使是主節點故障了剩餘的兩個節點也會快速選出主並對外提供讀寫服務。但是考慮到極端情況,假設同時出現兩個副本故障呢? 這時只剩一個副本無法完成選主服務將完全不可用。根據墨菲定律,可能發生的一定會發生。服務的可用性一方面是穩定提供服務的能力,另一方面是故障時快速恢復的能力。那麼假設出現這種故障的時候我們應該如何快速恢復服務的可用呢。
如果通過創建新的副本進行修復,新副本需要等到完成快照拷貝以後才能加入raft group進行選舉,期間服務還是不可用的。那麼我們可以通過強制將分片降為單副本模式,此時剩餘的單個健康副本可以獨自完成選主,後續再通過變更副本數的方式進行修復。
RaftLog 聚合提交
對於寫入吞吐非常高的場景,可以通過犧牲一定的延時來提升寫入吞吐,通過log聚合來減少請求放大。對於SSD盤,每一次寫入都是4k刷盤,value比較小的時候會造成磁碟帶寬的浪費。我們設置了每5ms或者每聚合4k進行批量提交。該參數可以根據業務場景進行動態配置修改。
非同步刷盤
有些對於數據一致性要求不是非常高的場景,服務故障的時候允許部分數據丟失。對於該場景,可以關閉fsync通過操作系統進行非同步刷盤。但是如果寫入吞吐非常高導致page cache的大小超過了 vm.diry_ratio ,那麼即便不是fsync也會導致io等待,該場景往往會導致io抖動。為了避免內核pdflush大量刷盤造成的io抖動,我們支持對raftlog進行非同步刷盤。
透明多級存儲,和緩存結合,自動冷熱分離,通過將冷數據自動搬遷到kv降低內存使用成本。
新硬體場景接入,使用SPDK 進行IO提速,使用PMEM進行訪問加速。
參考文獻
[1] Bitcask A Log-Structured Hash Table for Fast Key/Value Data
[2] Lethe: A Tunable Delete-Aware LSM Engine
Ⅳ 什麼是kv資料庫
kv資料庫是指Key-value資料庫,是一種以鍵值對存儲數據的一種資料庫,類似java中的map。可以將整個資料庫理解為一個大的map,每個鍵都會對應一個唯一的值。
Key-value資料庫代表的有redis,Redis是一個Key-Value存儲系統。和Memcached類似,它支持存儲的value類型相對更多,包括string(字元串)、list(鏈表)、set(集合)和zset(有序集合)。
另外redis是一種內存型的資料庫,所以可以對外提供很好地讀寫操作,但是同樣也暴露出內存佔用高,數據持久化不易等問題。
(5)kv持久化資料庫擴展閱讀
key-value資料庫的優點(以redis為例):
1、Redis將資料庫完全保存在內存中,僅使用磁碟進行持久化。
2、相比於其他鍵值資料庫,Redis有相對豐富的數據結構。
3、Redis可以將數據復制到任意數量的從機中。
5、快。每秒可以執行大約110000次設置(set)操作,每秒大約可執行81000次讀取(get)操作.
6、支持豐富的數據類型。Redis有5種數據類型。
7、操作具有原子性。所有Redis操作都是原子操作,這確保了兩個客戶端並發訪問,Redis伺服器能接收更新的值。
8、多實用工具。Redis可用於多種用例,如:緩存,消息隊列(支持發布和訂閱),應用程序中的任何短期數據,如:session等。
Ⅵ 怎樣操作leveldb資料庫,實現增刪改查
下文例子中演示了如何插入、獲娶刪除一條記錄 LevelDB 簡介 一、LevelDB入門 LevelDB是Google開源的持久化KV單機資料庫,具有很高的隨機寫,順序讀/寫性能,但是隨機讀的性能很一般,也就是說,LevelDB很適合應用在查詢較少,而寫很多的場景。
Ⅶ 什麼是kv資料庫
一個解決方案是使用鍵值(Key-Value)存儲資料庫,這是一種NoSQL(非
關系型資料庫
)模型,其數據按照鍵值對的形式進行組織、索引和存儲。KV存儲非常適合不涉及過多數據關系業務關系的業務數據,同時能有效減少讀寫磁碟的次數,比
SQL資料庫
存儲擁有更好的讀寫性能。
Ⅷ 什麼是kv資料庫
kv資料庫是指Key-value資料庫,是一種以鍵值對存儲數據的一種資料庫,類似java中的map。可以將整個資料庫理解為一個大的map,每個鍵都會對應一個唯一的值。
key-value分布式存儲系統查詢速度快、存放數據量大、支持高並發,非常適合通過主鍵進行查詢,但不能進行復雜的條件查詢。
如果輔以實時搜索引擎進行復雜條件檢索、全文檢索,就可以替代並發性能較低的MySQL等關系型資料庫,達到高並發、高性能,節省幾十倍伺服器數 量的目的。以MemcacheDB、Tokyo Tyrant為代表的key-value分布式存儲,在上萬並發連接下,輕松地完成高速查詢。
(8)kv持久化資料庫擴展閱讀:
資料庫的安全直接關繫到整個資料庫系統的安全,其防護手段主要有以下八點:
1、使用正版資料庫管理系統並及時安裝相關補丁。
2、做好用戶賬戶管理,禁用默認超級管理員賬戶或者為超級管理員賬戶設置復雜密碼;為應用程序分別分配專用賬戶進行訪問;設置用戶登錄時間及登錄失敗次數限制, 防止暴力破解用戶密碼。
3、分配用戶訪問許可權時,堅持最小許可權分配原則,並限制用戶只能訪問特定資料庫,不能同時訪問其他資料庫。
4、修改資料庫默認訪問埠,使用防火牆屏蔽掉對 外開放的其他埠,禁止一切外部的埠探測行為。
5、對資料庫內存儲的重要數據、敏感數據進行加密存儲,防止資料庫備份或數據文件被盜而造成數據泄露。
6、設置好資料庫的備份策略,保證資料庫被破壞後能迅速恢復。
7、對資料庫內的系統存儲過程進行合理管理,禁用掉不必要的存儲過程,防止利用存儲過程進行資料庫探測與攻擊。
8、啟用資料庫審核功能,對資料庫進行全面的事件跟蹤和日誌記錄。
參考資料來源:
網路-Key-Value
網路-資料庫
Ⅸ 開源高性能KV資料庫
功能支持
使用說明
快速上手
重打開或創建一個資料庫
注冊當TTL超時刪除事件通知
插入一條記錄,(當重復Put同key時操作等同於更新內容操作)
設置一條已存在記錄並8秒後超時自動刪除
刪除一條記錄
性能
插入隊列壓力測試
300,0005865ns/op516B/op9allocs/op
取出隊列壓力測試
200,00014379ns/op1119B/op20allocs/op
KET VALUE 集合操作
import
重打開或創建一個資料庫
注冊當TTL超時刪除事件通知
插入一條記錄,(當重復Put同key時操作等同於更新內容操作)
插入一條記錄並設置3秒後超時自動刪除
設置一條已存在記錄並8秒後超時自動刪除
刪除一條記錄
操量操作(事務) Op為put時操作插入或更新,Op為del時操作刪除
指定key取一條記錄
返回全庫的Key數據
返回所有K,V數據
按key開始位返回後續所有數據
以時間范圍查詢數據示例
匹配正則表達式為開頭的數據
struct對象的相關操作
指定key取一條記錄
返回所有記錄
按key開始過濾返回
按key范圍取數據
插入一條記錄struct對象以json保存
指定key取一條記錄
返回所有記錄
MIX 設計是基於原KV庫只有單維度存儲方式,從而缺失了二維度的存儲方式,所以MIX式庫被設計出來
寫入 raw
取出一個
查詢指定表的欄位是否存在
以raw讀出表數據
寫入及取出object
刪除指定表的指定欄位
刪除整個表所有數據
##創建支持分組的kvdb
寫入數據到分組
刪除分組
消息隊列 (FIFO)[先進先出]原則
import
重打開或創建一個隊列資料庫
推一個字元串到隊列中
推一個對象到隊列中
推一個bytes切片到隊列中
推一批bytes切片到隊列中
取出一條記錄,取出成功後記錄會被刪除
提取一條記錄,但不刪除原記錄
根據偏移量提取記錄
更新一個隊列原記錄bytes類型
更新一個隊列原記錄字元串類型
更新一個隊列原記錄對象類型
import
重打開或創建一個分組隊列資料庫
以對象存儲到隊列中
以切片存儲到隊列中
刪除指定分組
性能指標
開源地址:https://github.com/jacoblai/yiyidb
wiki地址:https://github.com/jacoblai/yiyidb/wiki