分散式(5)資料儲存 MongoDB/HBase/Redis比對與使用
轉載整理自
https://www.cnblogs.com/vajoy/p/5471308.html
https://www.cnblogs.com/RealWorld/p/9209687.html
文章目錄
MongoDB/HBase/Redis應用場景場景
型別 | 適用場景 |
---|---|
MongoDB | 適用於資料讀效能要求高,表結構變化大,資料規模較大、需要聚合查詢的場景 |
HBase | 適用於大資料量持久化儲存場景 |
Redis | 適用於讀寫要求高,資料量較小並且不需要持久化的場景 |
也可以考慮使用一些雲上儲存方案來解決一些資料儲存的問題,比如阿里雲的OSS、OTS
MongoDB
開源,無模式的文件型資料庫,開發語言是C++。可用於替代傳統的關係型資料庫或鍵/值儲存方式。
1.特點
1.1 資料格式
在 MongoDB 中,文件是對資料的抽象,它的表現形式就是我們常說的 BSON(Binary JSON )。
BSON 是一個輕量級的二進位制資料格式。MongoDB 能夠使用 BSON,並將 BSON 作為資料的儲存存放在磁碟中。
一個“文件”
{“name":"mengxiangyue","sex":"nan"}
對於文件是有一些限制的:有序、區分大小寫的,所以下面的兩個文件是與上面不同的:
{"sex":"nan","name":"mengxiangyue"}
{"Name":"mengxiangyue","sex":"nan"}
另外,對於文件的欄位 MongoDB 有如下的限制:
_id必須存在,如果你插入的文件中沒有該欄位,那麼 MongoDB 會為該文件建立一個ObjectId作為其值。_id的值必須在本集合中是唯一的。
{"name":"mengxiangyue"}
{"Name":"mengxiangyue","sex":"nan"}
1.2 效能
MongoDB 目前支援的儲存引擎為記憶體對映引擎。當 MongoDB 啟動的時候,會將所有的資料檔案對映到記憶體中,然後作業系統會託管所有的磁碟操作。這種儲存引擎有以下幾種特點:
- MongoDB 中關於記憶體管理的程式碼非常精簡,畢竟相關的工作已經有作業系統進行託管。
- MongoDB 伺服器使用的虛擬記憶體將非常巨大,並將超過整個資料檔案的大小。
另外,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.java使用
pom.xml
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver</artifactId>
<version>3.12.8</version>
</dependency>
demo
public class MongoDBJDBC {
public static void main(String args[]) {
try {
/**
// 連線到MongoDB服務 如果是遠端連線可以替換“localhost”為伺服器所在IP地址
// ServerAddress()兩個引數分別為 伺服器地址 和 埠
ServerAddress serverAddress = new ServerAddress("localhost", 27017);
List<ServerAddress> addrs = new ArrayList<ServerAddress>();
addrs.add(serverAddress);
// MongoCredential.createScramSha1Credential()三個引數分別為 使用者名稱 資料庫名稱 密碼
MongoCredential credential = MongoCredential.createScramSha1Credential("username", "databaseName", "password".toCharArray());
List<MongoCredential> credentials = new ArrayList<MongoCredential>();
credentials.add(credential);
// 通過連線認證獲取MongoDB連線
MongoClient mongoClient = new MongoClient(addrs, credentials);
**/
// 連線到 mongodb 服務
MongoClient mongoClient = new MongoClient("172.22.25.14", 27017);
// 連線到資料庫
MongoDatabase mongoDatabase = mongoClient.getDatabase("mycol");
// 集合建立
mongoDatabase.createCollection("test");
// 集合 test 選擇
MongoCollection<Document> collection = mongoDatabase.getCollection("test");
// 插入文件
/**
* 1. 建立文件 org.bson.Document 引數為key-value的格式
* 2. 建立文件集合List<Document>
* 3. 將文件集合插入資料庫集合中 mongoCollection.insertMany(List<Document>)
* 插入單個文件可以用 mongoCollection.insertOne(Document)
*/
Document document = new Document("title", "MongoDB").append("description", "database").append("likes", 100).append("by", "Fly");
List<Document> documents = new ArrayList<Document>();
documents.add(document);
collection.insertMany(documents);
System.out.println("文件插入成功");
// 檢索所有文件
/**
* 1. 獲取迭代器FindIterable<Document>
* 2. 獲取遊標MongoCursor<Document>
* 3. 通過遊標遍歷檢索出的文件集合
*/
FindIterable<Document> findIterable = collection.find();
MongoCursor<Document> mongoCursor = findIterable.iterator();
while (mongoCursor.hasNext()) {
System.out.println(mongoCursor.next());
}
// 更新文件 將文件中likes=100的文件修改為likes=200
collection.updateMany(Filters.eq("likes", 100), new Document("$set", new Document("likes", 200)));
// 刪除符合條件的第一個文件
collection.deleteOne(Filters.eq("likes", 200));
// 刪除所有符合條件的文件
collection.deleteMany(Filters.eq("likes", 200));
}
catch (Exception e) {
System.err.println(e.getClass().getName() + ": " + e.getMessage());
}
}
}
3.優缺點
優勢
- 強大的自動化shading功能,可以快速實現資料複製和節點伸縮
- 快速的查詢,MongoDB支援二維空間索引,比如管道,因此可以快速及精確的從指定位置
獲取資料。MongoDB在啟動後會將資料庫中的資料以檔案對映的方式載入到記憶體中。如果記憶體資源充足的話,這將極大地提高資料庫的查詢速度。 - 非結構化資料的爆發增長,增加列在有些情況下可能鎖定整個資料庫,或者增加負載從而
導致效能下降,由於MongoDB的弱資料結構模式,新增1個新欄位不會對舊錶格有任何影響, 整個過程會非常快速。 - 支援動態查詢,查詢指令也使用JSON形式的標記,可輕易查詢文件中內嵌的物件及陣列。
缺點
- 單個文件大小限制為16M,32位系統上,不支援大於2.5G的資料;
- 對記憶體要求比較大,至少要保證熱資料(索引,資料及系統其它開銷)都能裝進記憶體;
- 非事務機制,無法保證事件的原子性。
適用場景
- 適用於實時的插入、更新與查詢的需求,並具備應用程式實時資料儲存所需的複製及高度伸縮性。
- 非常適合文件化(json)格式的儲存及查詢。
- 高伸縮性的場景。
- 對效能的關注超過對功能的要求。
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 允許表下某行某列值為空時不做任何儲存(也不佔位),減少了空間佔用也提高了讀效能。
使用關係型資料庫(RDBMS)和 HBase 對比:
⑴ RDBMS方案:
其中Article表格式:
Author表格式:
⑵ 等價的HBase方案:
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.java使用
HBase的相關操作可參考下表:
pom.xml
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-client</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-server</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.apache.hbase</groupId>
<artifactId>hbase-common</artifactId>
<version>1.3.1</version>
</dependency>
code
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hbase.*;
import org.apache.hadoop.hbase.client.*;
import java.io.IOException;
public class ExampleForHbase {
public static Configuration configuration;
public static Connection connection;
public static Admin admin;
//主函式中的語句請逐句執行,只需刪除其前的//即可,如:執行insertRow時請將其他語句註釋
public static void main(String[] args)throws IOException{
//建立一個表,表名為Score,列族為sname,course
//createTable("Score",new String[]{"sname","course"});
//在Score表中插入一條資料,其行鍵為95001,sname為Mary(因為sname列族下沒有子列所以第四個引數為空)
//等價命令:put 'Score','95001','sname','Mary'
insertRow("Score", "95001", "sname", "", "Mary");
//在Score表中插入一條資料,其行鍵為95001,course:Math為88(course為列族,Math為course下的子列)
//等價命令:put 'Score','95001','score:Math','88'
//insertRow("Score", "95001", "course", "Math", "88");
//在Score表中插入一條資料,其行鍵為95001,course:English為85(course為列族,English為course下的子列)
//等價命令:put 'Score','95001','score:English','85'
//insertRow("Score", "95001", "course", "English", "85");
//1、刪除Score表中指定列資料,其行鍵為95001,列族為course,列為Math
//執行這句程式碼前請deleteRow方法的定義中,將刪除指定列資料的程式碼取消註釋註釋,將刪除制定列族的程式碼註釋
//等價命令:delete 'Score','95001','score:Math'
//deleteRow("Score", "95001", "course", "Math");
//2、刪除Score表中指定列族資料,其行鍵為95001,列族為course(95001的Math和English的值都會被刪除)
//執行這句程式碼前請deleteRow方法的定義中,將刪除指定列資料的程式碼註釋,將刪除制定列族的程式碼取消註釋
//等價命令:delete 'Score','95001','score'
//deleteRow("Score", "95001", "course", "");
//3、刪除Score表中指定行資料,其行鍵為95001
//執行這句程式碼前請deleteRow方法的定義中,將刪除指定列資料的程式碼註釋,以及將刪除制定列族的程式碼註釋
//等價命令:deleteall 'Score','95001'
//deleteRow("Score", "95001", "", "");
//查詢Score表中,行鍵為95001,列族為course,列為Math的值
//getData("Score", "95001", "course", "Math");
//查詢Score表中,行鍵為95001,列族為sname的值(因為sname列族下沒有子列所以第四個引數為空)
//getData("Score", "95001", "sname", "");
//刪除Score表
//deleteTable("Score");
}
//建立連線
public static void init(){
configuration = HBaseConfiguration.create();
configuration.set("hbase.rootdir","hdfs://localhost:9000/hbase");
try{
connection = ConnectionFactory.createConnection(configuration);
admin = connection.getAdmin();
}catch (IOException e){
e.printStackTrace();
}
}
//關閉連線
public static void close(){
try{
if(admin != null){
admin.close();
}
if(null != connection){
connection.close();
}
}catch (IOException e){
e.printStackTrace();
}
}
/**
* 建表。HBase的表中會有一個系統預設的屬性作為主鍵,主鍵無需自行建立,預設為put命令操作中表名後第一個資料,因此此處無需建立id列
* @param myTableName 表名
* @param colFamily 列族名
* @throws IOException
*/
public static void createTable(String myTableName,String[] colFamily) throws IOException {
init();
TableName tableName = TableName.valueOf(myTableName);
if(admin.tableExists(tableName)){
System.out.println("talbe is exists!");
}else {
HTableDescriptor hTableDescriptor = new HTableDescriptor(tableName);
for(String str:colFamily){
HColumnDescriptor hColumnDescriptor = new HColumnDescriptor(str);
hTableDescriptor.addFamily(hColumnDescriptor);
}
admin.createTable(hTableDescriptor);
System.out.println("create table success");
}
close();
}
/**
* 刪除指定表
* @param tableName 表名
* @throws IOException
*/
public static void deleteTable(String tableName) throws IOException {
init();
TableName tn = TableName.valueOf(tableName);
if (admin.tableExists(tn)) {
admin.disableTable(tn);
admin.deleteTable(tn);
}
close();
}
/**
* 檢視已有表
* @throws IOException
*/
public static void listTables() throws IOException {
init();
HTableDescriptor hTableDescriptors[] = admin.listTables();
for(HTableDescriptor hTableDescriptor :hTableDescriptors){
System.out.println(hTableDescriptor.getNameAsString());
}
close();
}
/**
* 向某一行的某一列插入資料
* @param tableName 表名
* @param rowKey 行鍵
* @param colFamily 列族名
* @param col 列名(如果其列族下沒有子列,此引數可為空)
* @param val 值
* @throws IOException
*/
public static void insertRow(String tableName,String rowKey,String colFamily,String col,String val) throws IOException {
init();
Table table = connection.getTable(TableName.valueOf(tableName));
Put put = new Put(rowKey.getBytes());
put.addColumn(colFamily.getBytes(), col.getBytes(), val.getBytes());
table.put(put);
table.close();
close();
}
/**
* 刪除資料
* @param tableName 表名
* @param rowKey 行鍵
* @param colFamily 列族名
* @param col 列名
* @throws IOException
*/
public static void deleteRow(String tableName,String rowKey,String colFamily,String col) throws IOException {
init();
Table table = connection.getTable(TableName.valueOf(tableName));
Delete delete = new Delete(rowKey.getBytes());
//刪除指定列族的所有資料
//delete.addFamily(colFamily.getBytes());
//刪除指定列的資料
//delete.addColumn(colFamily.getBytes(), col.getBytes());
table.delete(delete);
table.close();
close();
}
/**
* 根據行鍵rowkey查詢資料
* @param tableName 表名
* @param rowKey 行鍵
* @param colFamily 列族名
* @param col 列名
* @throws IOException
*/
public static void getData(String tableName,String rowKey,String colFamily,String col)throws IOException{
init();
Table table = connection.getTable(TableName.valueOf(tableName));
Get get = new Get(rowKey.getBytes());
get.addColumn(colFamily.getBytes(),col.getBytes());
Result result = table.get(get);
showCell(result);
table.close();
close();
}
/**
* 格式化輸出
* @param result
*/
public static void showCell(Result result){
Cell[] cells = result.rawCells();
for(Cell cell:cells){
System.out.println("RowName:"+new String(CellUtil.cloneRow(cell))+" ");
System.out.println("Timetamp:"+cell.getTimestamp()+" ");
System.out.println("column Family:"+new String(CellUtil.cloneFamily(cell))+" ");
System.out.println("row Name:"+new String(CellUtil.cloneQualifier(cell))+" ");
System.out.println("value:"+new String(CellUtil.cloneValue(cell))+" ");
}
}
}
3.優缺點
優點
- 儲存容量大,一個表可以容納上億行,上百萬列。
- 可通過版本進行檢索,能搜到所需的歷史版本資料。
- 負載高時,可通過簡單的新增機器來實現水平切分擴充套件,跟Hadoop的無縫整合保障了其資料可靠性(HDFS)和海量資料分析的高效能(MapReduce)。
- 在第3點的基礎上可有效避免單點故障的發生。
缺點
- 基於Java語言實現及Hadoop架構意味著其API更適用於Java專案;
- 佔用記憶體很大,且鑑於建立在為批量分析而優化的HDFS上,導致讀取效能不高。
- API相比其它 NoSql 的相對笨拙。
適用場景
- bigtable型別的資料儲存。
- 對資料有版本查詢需求。
- 應對超大資料量要求擴充套件簡單的需求。
Redis
Redis 是一個開源的使用ANSI C語言編寫、支援網路、可基於記憶體亦可持久化的日誌型、Key-Value資料庫,並提供多種語言的API。
1.特點
1.1 資料格式
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
OK
redis 127.0.0.1:6379> HGETALL user:11) "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 -11) "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) 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
zadd key score member
例項:
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.java使用
redis單機-哨兵-叢集雲伺服器環境搭建、原理說明及springboot2.0的呼叫
3.優缺點
優勢
- 非常豐富的資料結構;
- Redis提供了事務的功能,可以保證一串 命令的原子性,中間不會被任何操作打斷;
- 資料存在記憶體中,讀寫非常的高速,可以達到10w/s的頻率。
缺點
- 持久化功能體驗不佳——通過快照方法實現的話,需要每隔一段時間將整個資料庫的資料寫到磁碟上,代價非常高;而aof方法只追蹤變化的資料,類似於mysql的binlog方法,但追加log可能過大,同時所有操作均要重新執行一遍,恢復速度慢;
- 由於是記憶體資料庫,所以,單臺機器,儲存的資料量,跟機器本身的記憶體大小。雖然redis本身有key過期策略,但是還是需要提前預估和節約記憶體。如果記憶體增長過快,需要定期刪除資料。
適用場景
- 適用於資料變化快且資料庫大小可遇見(適合記憶體容量)的應用程式。