MongoDB閃亮登場
自我介紹
MongoDB 是一個基於分布式文件存儲的資料庫。由 C++ 語言編寫。旨在為 WEB 應用提供可擴展的高性能數據存儲解決方案。
MongoDB 是一個介於關係資料庫和非關係資料庫之間的產品,是非關係資料庫當中功能最豐富,最像關係資料庫的。
MongoDB最大的特點就是無Schema限制,靈活度很高。數據格式是BSON,BSON是一種類似JSON的二進位形式的存儲格式,簡稱Binary JSON 它和JSON一樣,支持內嵌的文檔對象和數組對象。
跟關係型資料庫概念對比
Mysql MongoDB Database(資料庫) Database(資料庫) Table(表) Collection(集合) Row(行) Document(文檔) Column(列) Field(欄位)
數據格式
MongoDB 將數據存儲為一個文檔,BSON格式。由key 和 value組成。
{
"_id" : ObjectId("5e141148473cce6a9ef349c7"),
"title" : "批量更新",
"url" : "http://cxytiandi.com/blog/detail/8",
"author" : "yinjihuan",
"tags" : [
"java",
"mongodb",
"spring"
],
"visit_count" : NumberLong(10),
"add_time" : ISODate("2019-02-11T07:10:32.936+0000")
}
複製代碼
使用場景
- 大數據量存儲場景
MongoDB自帶副本集和分片,天生就適用於大數量場景,無需開發人員通過中間件去分庫分表,非常方便。
- 操作日誌存儲
很多時候,我們需要存儲一些操作日誌,可能只需要存儲比如最近一個月的,一般的做法是定期去清理,在MongoDB中有固定集合的概念,我們在創建集合的時候可以指定大小,當數據量超過大小的時候會自動移除掉老數據。
- 爬蟲數據存儲
爬下來的數據有網頁,也有Json格式的數據,一般都會按照表的格式去存儲,如果我們用了MongoDB就可以將抓下來的Json數據直接存入集合中,無格式限制。
- 社交數據存儲
在社交場景中使用 MongoDB 存儲存儲用戶地址位置信息,通過地理位置索引實現附近的人,附近的地點等。
- 電商商品存儲
不同的商品有不同的屬性,常見的做法是抽出公共的屬性表,然後和SPU進行關聯,如果用MongoDB的話那麼SPU中直接就可以內嵌屬性。
自我陶醉
MongoDB的功能點很多,但是大部分場景下我們只用了最簡單的CRUD操作。下面隆重的介紹下MongoDB的功能點,就像你去相親一樣,不好好介紹自己的優點又怎能讓你對面的菇涼心動呢?
CRUD
CRUD也就是增刪改查,這是資料庫最基本的功能,查詢還支持全文檢索,GEO地理位置查詢等。
- db.collection.insertOne()
單個文檔插入到集合中
- db.collection.insertMany()
多個文檔插入到集合中
- db.collection.insert()
單個或者多個文件插入到集合中
- db.collection.find( )
查詢數據
- db.inventory.updateOne()
更新單條
- db.inventory.updateMany()
更新多條
- db.inventory.deleteOne( )
刪除單條文檔
- db.inventory.deleteMany()
刪除多條文檔
Aggregation
聚合操作用於數據統計方面,比如Mysql中會有count,sum,group by等功能,在MongoDB中相對應的就是Aggregation聚合操作。
聚合下面有兩種方式來實現我們需要對數據進行統計的需求,一個是aggregate,一個是MapReduce。
下圖展示了aggregate的執行原理:
聚合內置了很多函數,使用好了這些函數我們就可以統計出我們想要的數據。
$project:修改輸入文檔的結構。可以用來重命名、增加或刪除域,也可以用於創建計算結果以及嵌套文檔。
match用於過濾數據,只輸出符合條件的文檔。
match使用MongoDB的標準查詢操作。
$limit:用來限制MongoDB聚合管道返回的文檔數。
$skip:在聚合管道中跳過指定數量的文檔,並返回餘下的文檔。
$group:將集合中的文檔分組,可用於統計結果。
$sort:將輸入文檔排序後輸出。
$geoNear:輸出接近某一地理位置的有序文檔。
$unwind:將文檔中的某一個數組類型欄位拆分成多條,每條包含數組中的一個值。
下圖展示了MapReduce的執行原理:
總共4條數據,query指定了查詢條件,只處理status=A的數據。
map階段對數據進行分組聚合,也就是形成了第三部分的效果,根據cust_id去重統計。
reduce中的key也就是cust_id, values也就是匯總的amount集合。然後進行sum操作,最終的結果通過out輸出到一個集合中。
Transactions
MongoDB最開始是不支持事務的,在MongoDB中,對單個文檔的操作是原子性操作。所以再設計的時候可以使用嵌入的文檔和數組來描述數據之間的關係,這樣就不用跨多個文檔和集合進行操作,也就通過了單文檔原子性消除了許多實際用例對多文檔事務的需要。
任何事物都是有限制的,某些場景還是不能完全通過內嵌的方式來描述數據的關係,還是會存在多個集合,對於使用MongoDB的用戶來說,如果能支持事務就很方便了。
不負眾望,MongoDB 4.0 版本的發布,為我們帶來了原生的事務操作。
Indexes
索引不用我多說了,作用大家都知道。單索引,組合索引,全文索引,Hash索引等。
db.collection.createIndex({user_id: 1, add_time: 1}, {background: true})
複製代碼
創建索引特別要注意的是將background設置為true,在建索引的過程會阻塞其它資料庫操作,background可指定以後台方式創建索引,默認為false。這可是血的教訓呀,切記切記。
Security
MongoDB中的安全需要重視,目前啟動不知道有沒有強制的限制,以前啟動的時候可以不指定認證的方式,也就是不需要密碼即可訪問,然後很多人都直接用的默認埠,暴露在公網上,給不法分子有機可乘,出現了數據被刪,需要用比特幣來找回數據的案例比比皆是。
還是要開啟安全認證,內置了很多角色,不同的角色可操作的內容不一樣,控制的比較細。
Replication
副本集是一組相同數據集的MongoDB實例,同時在多個節點存儲數據,提高了可用性。主節點負責寫入,從節點負責讀取,提高整體性能。
副本集由下面的組件構成:
Primary:主節點接收所有的寫操作。
Secondaries:從節點會從主節點進行數據的複製,維護跟主節點相同的數據。用於查詢操作。
Arbiter:仲裁節點本身不存儲數據,只參與選舉。
Sharding
分片是MongoDB絕對的亮點,將數據水平拆分到多個節點。MongoDB的分片是全自動的,我們只需要配置好分片的規則,它就能自動維護數據並存儲到不同節點。MongoDB使用分片來支持大數據量的存儲和高吞吐量的操作。
下圖是Mongodb的分片集群架構圖:
MongoDB分片集群由以下組件夠成:
Shard:每個shard的數據都是獨立完整的一份。並且可以作為副本集部署。
mongos:mongos是查詢路由器,在客戶端和服務端中間的一層,請求會直接到mongos,由mongos路由到具體的Shard。
Config Servers:存儲集群所有節點、分片數據路由信息。
GridFS
GridFS是MongoDB的一個子模塊,主要用於在MongoDB中存儲文件,相當於MongoDB內置的一個分布式文件系統。
本質上還是講文件的數據分塊存儲在集合中,默認的文件集合分為fs.files和fs.chunks。
fs.files是存儲文件的基本信息,比如文件名,大小,上傳時間,md5等。fs.chunks是存儲文件真正數據的地方,一個文件會被分割成多個chunk塊進行存儲,一般為256k/個。
如果你的項目中用到了MongoDB,那麼你可以使用GridFS來構建一個文件系統,這樣就不用去購買第三方的存儲服務了。
GridFS的好處是你不用單獨去搭建一個文件系統,直接使用Mongodb自帶的即可,備份,分片都依賴MongoDB,維護起來也方便。
知識點總結
下圖是我自己總結的一些知識點,作為一個後端開發來說,能掌握下面的內容就已經不錯了,畢竟我們又不是要去搶DBA的飯碗,如果大家業餘時間要學習的話可以按照下面的點進行學習,幾年前我錄製了一套視頻,在我的網站上,大部分內容都覆蓋到了。
工作必用
MongoDB跟Mysql的語法對比
Spring Boot中集成MongoDB
加入MongoDB的依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
複製代碼
配置MongoDB的信息:
spring.data.mongodb.database=test
spring.data.mongodb.host=localhost
spring.data.mongodb.port=27017
// 用戶名,密碼省略.......
複製代碼
直接注入MongoTemplate就可以操作MongoDB:
@Autowired
private MongoTemplate mongoTemplate;
複製代碼
使用示列
創建一個實體類,對應MongoDB的集合
@Data
@Document(collection = "article_info")
public class Article {
@Id
@GeneratedValue
private Long id;
@Field("title")
private String title;
@Field("url")
private String url;
@Field("author")
private String author;
@Field("tags")
private List<String> tags;
@Field("visit_count")
private Long visitCount;
@Field("add_time")
private Date addTime;
}
複製代碼
最終存儲到數據中的格式如下:
{
"_id" : ObjectId("5e141148473cce6a9ef349c7"),
"title" : "批量更新",
"url" : "http://cxytiandi.com/blog/detail/8",
"author" : "yinjihuan",
"tags" : [
"java",
"mongodb",
"spring"
],
"visit_count" : NumberLong(10),
"add_time" : ISODate("2019-02-11T07:10:32.936+0000")
}
複製代碼
插入數據
Article article = new Article();
article.setTitle("MongoTemplate 的基本使用 ");
article.setAuthor("yinjihuan");
article.setUrl("http://cxytiandi.com/blog/detail/1");
article.setTags(Arrays.asList("java", "mongodb", "spring"));
article.setVisitCount(0L);
article.setAddTime(new Date());
mongoTemplate.save(article);
複製代碼
資料庫語法
db.article_info.save({
"title": "批量更新",
"url": "http://cxytiandi.com/blog/detail/8",
"author": "yinjihuan",
"tags": [
"java",
"mongodb",
"spring"
],
"visit_count": NumberLong(10),
"add_time": ISODate("2019-02-11T07:10:32.936+0000")
})
複製代碼
更新數據
Query query = Query.query(Criteria.where("author").is("yinjihuan"));
Update update = Update.update("title", "MongoTemplate")
.set("visitCount", 10);
mongoTemplate.updateMulti(query, update, Article.class);
複製代碼
資料庫語法
db.article_info.updateMany(
{"author":"yinjihuan"},
{"$set":
{
"title":"MongoTemplate",
"visit_count": NumberLong(10)
}
}
)
複製代碼
刪除數據
Query query = Query.query(Criteria.where("author").is("yinjihuan"));
mongoTemplate.remove(query, Article.class);
複製代碼
資料庫語法
db.article_info.remove({"author":"yinjihuan"})
複製代碼
查詢數據
Query query = Query.query(Criteria.where("author").is("yinjihuan"));
List<Article> articles = mongoTemplate.find(query, Article.class);
複製代碼
資料庫語法
db.article_info.find({"author":"yinjihuan"})
複製代碼
存儲文件
File file = new File("/Users/yinjihuan/Downloads/logo.png");
InputStream content = new FileInputStream(file);
// 存儲文件的額外信息,比如用戶ID,後面要查詢某個用戶的所有文件時就可以直接查詢
DBObject metadata = new BasicDBObject("userId", "1001");
ObjectId fileId = gridFsTemplate.store(content, file.getName(), "image/png", metadata);
作者:猿天地連結:https://juejin.im/post/5e1d2108f265da3e0d04760f