MongoDB從入門到放棄!

概述:大家對數據庫肯定不陌生,肯定也有很多人用過MySQL,但是在用MySQL的時候各種建表,寫表之間的關聯讓人非常頭疼。

MongoDB也是一種數據庫,但是它不是用表,而是用集合來裝數據的。

MongoDB官方:https://www.mongodb.com/

什麼是MongoDB:Mongodb是面向文檔數據庫(Document Oriented Databases),同時,它也是“NoSQL數據庫”。

MongoDB 是由C++語言編寫的,是一個基於分佈式文件存儲的開源數據庫系統。

MongoDB 將數據存儲爲一個文檔,數據結構由鍵值(key=>value)對組成。MongoDB 文檔類似於 JSON 對象。字段值可以包含其他文檔,數組及文檔數組。

下表將幫助您更容易理解Mongo中的一些概念:

SQL術語/概念

MongoDB術語/概念

解釋/說明

database

database

數據庫

table

collection

數據庫表/集合

row

document

數據記錄行/文檔

column

field

數據字段/域

index

index

索引

table joins

 

表連接,MongoDB不支持

primary key

primary key

主鍵,MongoDB自動將_id字段設置爲主鍵

通過下圖實例,我們也可以更直觀的瞭解Mongo中的一些概念:

MongoDB的安裝

1.去mongodb的官網http://www.mongodb.org/downloads下載msi安裝包(CommunityServer版本)。安裝的默認路徑是:C:\Program Files\MongoDB\Server\3.6\bin

2.爲了啓動mongodb方便,將mongod.exe路徑加入環境變量。電腦->屬性->高級系統設置->環境變量,在path里加入默認路徑:C:\Program Files\MongoDB\Server\3.6\bin

3.在D盤新建一個mongodb文件夾用來放數據文件,並在mongodb文件夾下建立data,logs文件夾,在logs文件夾下建立mongodb.log文件

4.以管理員啓動cmd,並且輸入:mongod --dbpath D:\mongodb\data\ --logpath D:\mongodb\logs\mongodb.log --install --serviceName"MongoDB"

5.以管理員啓動cmd,net start mongodb啓動mongodb服務;mongo 127.0.0.1:27017進入mongo數據庫;net stop MongoDB關閉mongodb服務

 

mongoDB採用BSON結構來存儲數據和網絡數據交換

BSON是一種類json的一種二進制形式的存儲格式,簡稱Binary JSON,它和JSON一樣,支持內嵌的文檔對象和數組對象,但是BSON有JSON沒有的一些數據類型,如Date和BinData類型。

BSON可以做爲網絡數據交換的一種存儲形式,這個有點類似於Google的Protocol Buffer,但是BSON是一種schema-less的存儲形式,它的優點是靈活性高,但它的缺點是空間利用率不是很理想,BSON有三個特點:輕量性、可遍歷性、高效性。

把這種格式轉化成一文檔這個概念(Document),因爲BSON是schema-free的,所以在MongoDB中所對應的文檔也有這個特徵,這裏的一個Document也可以理解成關係數據庫中的一條記錄(Record),只是這裏的Document的變化更豐富一些,如Document可以嵌套。


MongoDB以BSON做爲其存儲結構的一種重要原因是其可遍歷性
 
幾個BSON的例子

一個Document的BSON表示:

代碼如下:

{  
title:"MongoDB",  
last_editor:"192.168.1.122",  
last_modified:new Data("27/06/2011"),  
body:"MongoDB introduction",  
categories:["Database","NoSQL","BSON"],  
revieved:false  
}  

這是一個簡單的BSON結構體,其中每一個element都是由key/value對組成的

一個嵌套的例子

代碼如下:

{  
name:"lemo",  
age:"12",  
address:{  
city:"suzhou",  
country:"china",  
code:215000  
}  
scores:[  
{"name":"english","grade:3.0},  
{"name":"chinese","grade:2.0}  
]  
}

 

MongoDb可以操作的數據類型(文檔,集合)

1:文檔

概述

文檔是MongoDB的核心概念,是數據的基本單元,非常類似於關係數據庫中的行。在MongoDB中,文檔表示爲鍵值對的一個有序集。MongoDB使用Javascript shell,文檔的表示一般使用Javascript裏面的對象的樣式來標記,如下:

1 {"title":"hello!"}
2 {"title":"hello!","recommend":5}
3 {"title":"hello!","recommend":5,"author":{"firstname":"paul","lastname":"frank"}}

從上面的例子可以看到,文檔的值有不同的數據類型,甚至可以是一個完整的內嵌文檔(最後一個示例的author是有一個完整的文檔表示的,文檔裏面定義了firstname和lastname。當然還可以包含更多其他信息甚至於在內嵌文檔中還可以有內嵌文檔)。

說明

文檔區分大小寫和數據類型,所以以下兩組文檔是不同的:

1 {"recommend":"5"}  
2 {"recommend":5}

1 {"Recommend":"5"}
1 {"recommend":"5"}
MongoDB的文檔不能有重複的鍵。下面的文檔是非法的:

{"title":"hello!","title":"Mongo"}

操作

創建

創建文檔非常簡單,通過插入語句就能向數據庫中創建一個文檔記錄。

1 > db.blogs.insert({"title":"hello!"})
如果在執行這條語句之前,數據庫和blogs集合並沒有創建,會分別創建數據庫和集合,同時插入文檔。

刪除

1 > db.blogs.remove()   // 刪除集合中所有文檔。
2 > db.blogs.remove({"title":"hello!"})   // 刪除指定條件的文檔,當前語句刪除"title"爲"hello!"的文檔。

2:集合

集合是一組文檔的集,相當於關係型數據庫中的數據表。

動態模式

集合是動態模式的。什麼意思呢?具體來說就是一個集合裏面的文檔可以是各式各樣的。舉例來說,下面的兩種文檔完全可以存儲在同一個集合裏面:

1 {"title":"hello!"}
2 {"recommend":5}

可以看出,上面兩個文檔不僅值得類型不同,連鍵也完全不一樣。這和關係型數據庫中一個表中只能存放相同模型的數據結構顯得很不一樣。但是這也就產生了一個問題:既然一個集合中可以存放任意的文檔,那麼多個集合的存在還有什麼必要性呢?這其實可以和關係型數據表可以對應起來理解,我們可以創建一張表容納下上面提到的title和recommend列,但是總有一個列是NULL的。這還僅僅是兩個列的情況,如果出現無數的列,那麼這種情況就非常糟糕了。所以不難想出一個數據庫中存在多個集合的原因應該至少有如下幾點:

  1. 數據混亂。開發人員要區分每次查詢只返回特定類型的文檔,或者把這個區分交給處理查詢結果的應用程序來處理。這對於開發和維護來說都會帶來很大的麻煩。
  2. 性能。分別在不同的集合上查詢要比在一個集合中去查詢不同數據快得多。
  3. 數據更集中。同種類型的文檔放在一個集合裏,數據更加集中,查詢數據時。需要的磁盤尋道操作更少,效率更高。
  4. 更高效的利用索引。索引是按照集合來定義的。創建索引時,需要使用文檔的附加結構。在一個集合中只放入一種類型的文檔,可以更有效的對集合進行索引。

常用命令

  1. show collections  查看當前數據庫中存在哪些集合,將展示集合的名稱列表。如下圖所示:

  2. help()  獲取集合上的可執行命令的列表。執行語句如下:

    1 db.users.help()
  3. insert(obj) 向集合中插入一個文檔。

  4. drop() 刪除當前集合,刪除之後不可恢復。

  5. dropIndex(index) 刪除集合上的索引,參數爲空時,刪除所有索引(除了_id上的索引)

  6. ensureIndex(keypattern[,options]) 創建索引

  7. update(query,object[,upsert_bool,multi_bool]) 更新集合中滿足條件的文檔

  8. find([query,fields]) 根據條件查詢滿足條件的文檔

當然還有很多命令在這裏沒有列出,但是可以通過help()命令輕鬆的查看能在集合上執行的命令。

 

數據庫

多個文檔構成集合,多個集合組成數據庫。一個MongoDB實例可以承載多個數據庫,每個數據庫可以擁有0到多個集合。

  1. 每個數據庫有相應的數據文件和命名空間文件。文件的前綴是數據庫的名稱,後綴.ns表示命名空間文件,後綴以.0、.1等數字結尾的,表示數據文件。

  2. 數據文件的大小從64MB開始(這是在64位Windows Server 2012上看到的結果,其他環境可能有些差異),新的數據文件比上一個文件大一倍。所以能看到,chen.0的大小是64MB,chen.1的大小是128MB,chen.2是256MB。

  3. 文件使用MMAP進行內存映射,會將所有的數據文件映射到內存中,但是隻是虛擬內存,只有訪問到這塊數據時纔會交換到物力內存中。

  4. 每個數據文件會被分成一個一個的數據塊,塊與塊之間用雙向鏈表鏈接。

  5. 在命名空間文件中,保存了每個命名空間的存儲信息元數據,包括其大小、塊數、第一塊的位置、最後一塊的位置、被刪除的塊的鏈表以及索引信息。

數據庫操作

1.創建並進入數據庫

use DATABASE_NAME

創建名字爲TEST的數據庫,並進入數據庫;如果數據庫已存在,則直接進入數據庫。

use TEST

2.顯示數據庫。

show dbs

顯示所有數據庫

show dbs

3.刪除數據庫

db.dropDatabase()

刪除TEST數據庫

use TEST
db.dropDatabase()

集合操作

1.創建集合

db.createCollection(name, options)

創建集合名imooc的數據庫

db.createCollection("imooc")

2.查看集合。

show collections

查看所有集合

show collections

3.刪除集合

db.COLLECTION_NAME.drop()

刪除集合imooc

db.imooc.drop()

數據操作

1.create操作

db.collection.insertOne()
db.collection.insertMany()
db.collection.insert()

寫入單條和多條數據:

db.inventory.insertOne(
   { item: "canvas", qty: 100, tags: ["cotton"], size: { h: 28, w: 35.5, uom: "cm" } }
)
db.inventory.insertMany([
   { item: "journal", qty: 25, tags: ["blank", "red"], size: { h: 14, w: 21, uom: "cm" } },
   { item: "mat", qty: 85, tags: ["gray"], size: { h: 27.9, w: 35.5, uom: "cm" } },
   { item: "mousepad", qty: 25, tags: ["gel", "blue"], size: { h: 19, w: 22.85, uom: "cm" } }
])

2.Read操作

db.collection.find()

查找status爲"D"的數據,並且顯示5條。

db.inventory.find( { status: "D" } ).limit(5)

查找status爲"D"的數據,並且以格式化顯示。

db.inventory.find( { status: "D" } ).pretty()

查找status爲"A"或"D"的數據。

db.inventory.find( { status: { $in: [ "A", "D" ] } } )

查找status爲"A"並且qty爲30的數據。

db.inventory.find( { status: "A", qty: 30} )

查找status爲"A"或者qty爲30的數據。

db.inventory.find( { $or: [ { status: "A" }, { qty:30 } ] } )

查找status爲"A"的第二條數據。

db.inventory.find( { "status.1":  "A" } )

查找instock屬性中qty爲20的數據。(instock屬性是一個集合)

db.inventory.find( { 'instock.qty': 20 } )

查找instock屬性中qty爲20的第一條數據。(instock屬性是一個集合)

db.inventory.find( { 'instock.0.qty': 20 } )

查找status爲"A"的數據,並且只返回_id,item和status字段

db.inventory.find( { status: "A" }, { item: 1, status: 1 } )

查找status爲"A"的數據,並且只返回item字段,不返回status和_id字段

db.inventory.find( { status: "A" }, { item: 1, status: 0, _id: 0 } )

查找status爲"A"的數據,並且只返回_id和item字段,以及size字段的uom屬性

db.inventory.find({ status: "A" }, { item: 1, "size.uom": 1 })

查找item爲null或者不存在item屬性的數據

db.inventory.find( { item: null } )

查找item屬性爲null的數據

db.inventory.find( { item : { $type: 10 } } )

查找不存在item屬性的數據

db.inventory.find( { item : { $exists: false } } )

相當於db.users.find( { type: 2 } ),因爲結果返回一個循環指針

var myCursor = db.users.find( { type: 2 } );
myCursor

3.Update操作

db.collection.updateOne()
db.collection.updateMany()
db.collection.replaceOne()
db.collection.update()

將item爲"paper"的第一條數據的size.uom改爲"cm",status改爲"P"

db.inventory.updateOne(
   { item: "paper" },
   {
     $set: { "size.uom": "cm", status: "P" },
   }
)

將item爲"paper"的所有數據的size.uom改爲"cm",status改爲"P"

db.inventory.updateMany(
   { item: "paper" },
   {
     $set: { "size.uom": "cm", status: "P" },
   }
)

把item爲"paper"的第一個數據替換爲後一個數據

db.inventory.replaceOne(
   { item: "paper" },
   { item: "paper", instock: [ { warehouse: "A", qty: 60 }, { warehouse: "B", qty: 40 } ] }
)

4.delete操作

db.collection.deleteOne()
db.collection.deleteMany()
db.collection.remove()

刪除第一個status爲"D"的數據;刪除所有status爲"D"的數據

db.inventory.deleteOne( { status: "D" } )
db.inventory.deleteMany( { status: "D" } )

 

Docker 部署 MongoDB

1. 拉取 docker 鏡像

docker pull mongo:3.4

2. 運行

docker run -d --name mongodb --volume /usr/local/mongodata:/data/db -p 27017:27017 mongo:3.4 --auth

3. 進入 mongo

docker exec -it mongodb mongo

4. 創建數據庫帳號

use admin;db.createUser({ user: 'root', pwd: '123', roles: [ { role: "root", db: "admin" } ] });

5. 安裝 mongo-express 可視化工具

docker run -d --name mongo-express -p 8081:8081 --link mongodb:mongo --env ME_CONFIG_MONGODB_ADMINUSERNAME='root' --env ME_CONFIG_MONGODB_ADMINPASSWORD='123' mongo-express

mongoDB索引的使用

索引也是一種常見的查詢優化的方式

Index

索引

我們在users的collection上面爲name字段創建索引

db.users.ensureIndex({"name":1})

查詢該collection上面已經創建了哪些索引

 db.users.getIndexes()

通過查詢發現該collection上面爲id和name創建了索引,其中_id是默認的索引。

刪除字段name上面的索引

db.users.dropIndex({"name":1})

複合索引

在name和age上面創建複合索引

 db.users.ensureIndex({"name":1,"age":-1})

該複合索引被創建後,基於name和age的查詢將會用到該索引,或者是基於name的查詢也會用到該索引,但是隻是基於age的查詢將不會用到該複合索引。

因此,如果想用到複合索引,必須在查詢條件中包含複合索引中的前N個索引列。然而如果查詢條件中的鍵值順序和複合索引中的創建順序不一致的話,MongoDB可以智能的幫助我們調整該順序,以便使複合索引可以爲查詢所用。

唯一索引

默認情況下創建的索引都不是唯一索引,如果創建唯一索引需要unique參數設置爲true

爲name創建唯一索引

 db.users.ensureIndex({"name":1},{"unique":true})

如果這時插入重複的name值,就會報錯

 

[MongoDB]count,gourp,distinct

作用類似sql中的count函數,用來計數。

如上圖所示,列舉了不帶參數,帶參數,以及先find後count的方式。

distinct

去重,接收字段參數,語義:按某字段去重。比如上圖中,我們按name去重。

group

顧名思義是分組的意思,與sql中group by相同。但在mongodb中group就比較複雜了。

參數:

key:按照key進行分組。

initial:每組都分享的“初始化函數”。可以在此處初始化一些變量,供每組進行使用。

$reduce:該函數有兩個參數,第一個參數是當前document對象,第二個參數是上次操作的累計對象。collection中有多少個document就會調用多少次$reduce。

condition:過濾條件。

finalize:該函數會在每組document執行完成後,就會調用該函數,可以在這個函數中,做一些後續的工作,比如進行計數操作,統計結果的個數。

例子:根據age進行分組,查找每個年齡段的人員姓名。如圖一所示:

圖一

 計數,分組後,符合條件的user有多少個。

SpringBoot整合mongoDB

MongoDB 是一個介於關係數據庫和非關係數據庫之間的產品,是非關係數據庫當中功能最豐富,最像關係數據庫的。

這一片文章介紹一個springboot整合mongodb,如果你瞭解整合mysql之類的數據庫,可以一帶而過。

還是同樣的套路,pom文件中加入mongodb依賴,完整pom文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>

	<groupId>com.mongo.web</groupId>
	<artifactId>springboot_mongo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>

	<name>springboot_mongo</name>
	<description>Demo project for Spring Boot</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.4.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<!-- spring-boot-starter-data-mongodb -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-mongodb</artifactId>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>
配置文件中,配置對應mongodb的數據庫信息,這裏只配置了數據庫地址,端口號,數據庫名稱,配置如下:
spring:
  data:
    mongodb:
      uri: mongodb://10.2.4.35:27017/test
  application:
    name: mongoweb
server:
  port: 8888
也是一樣的創建一個實體類,如下:
package com.mongo.mongoweb.serviceimpl;


import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Service;

import com.mongo.mongoweb.model.User;
import com.mongo.mongoweb.service.UserService;

@Service
public class UserServiceImpl implements UserService{

	/**
	 * 由springboot自動注入,默認配置會產生mongoTemplate這個bean
	 */
	@Autowired
	private MongoTemplate mongoTemplate;

	/**
	 * 查找全部
	 */
	@Override
	public List<User> findAll() {
		return mongoTemplate.findAll(User.class);
	}

	/**
	 * 根據id得到對象
	 */
	@Override
	public User getUser(String id) {
		return mongoTemplate.findOne(new Query(Criteria.where("id").is(id)), User.class);
	}

	/**
	 * 插入一個用戶
	 */
	@Override
	public void insert(User user) {
		mongoTemplate.insert(user);
	}

	/**
	 * 根據id刪除一個用戶
	 */
	@Override
	public void remove(Integer id) {
		Criteria criteria = Criteria.where("id").is(id);
		Query query = new Query(criteria);
		mongoTemplate.remove(query, User.class);
	}

	/**
	 * 分頁查找
	 * <p>
	 * user代表過濾條件
	 * <p>
	 * pageable代表分頁bean
	 */
	@Override
	public List<User> findByPage(User user, Pageable pageable) {
		Query query = new Query();
		if (user != null && user.getName() != null) {
			//模糊查詢
			query = new Query(Criteria.where("name").regex("^" + user.getName()));
		}
		List<User> list = mongoTemplate.find(query.with(pageable), User.class);
		return list;
	}


	/**
	 * 根據id更新
	 */
	@Override
	public void update(User user) {
		Criteria criteria = Criteria.where("id").is(user.getId());
		Query query = new Query(criteria);
		Update update = Update.update("name", user.getName()).set("age", user.getAge());
		mongoTemplate.updateMulti(query, update, User.class);
	}

	/**
	 * 插入一個集合
	 */
	@Override
	public void insertAll(List<User> users) {
		mongoTemplate.insertAll(users);
	}

	/**
	 * test  測試mongodb
	 * @param user
	 * @return
	 */
	@Override
	public List<User> show(User user) {
		mongoTemplate.insert(user);
		List<User> all = mongoTemplate.findAll(User.class);
		return all;
	}

}
創建一個數據操作層,繼承MongoRepository,代碼如下:
package com.mongo.mongoweb.controller;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.mongo.mongoweb.model.User;
import com.mongo.mongoweb.service.UserService;

/**
 * user控制器
 *
 */
@RestController
@RequestMapping("/user")
public class UserController {

	@Autowired
	private UserService userService;

	@GetMapping("/get/{id}")
	public User getUser(@PathVariable String id) {
		return userService.getUser(id);
	}

	@GetMapping("/delete/{id}")
	public String delete(@PathVariable int id) {
		userService.remove(id);
		return "delete sucess";
	}

	@GetMapping("/add")
	public String insert() {
		User user =new User(16, ""+16, 16);
		userService.insert(user);
		return "sucess";
	}

	@GetMapping("/insert")
	public String insertAll() {
		List<User> list = new ArrayList<>();
		for (int i = 10; i < 15; i++) {
			list.add(new User(i, "" + i, i));
		}
		userService.insertAll(list);
		return "sucess";
	}

	@GetMapping("/find/all")
	public List<User> find(){
		return userService.findAll();
	}

	@GetMapping("/find/{start}")
	public List<User> findByPage(@PathVariable int start,User user){
		Pageable pageable=new PageRequest(start, 2);
		return userService.findByPage(user, pageable);
	}

	@GetMapping("/update/{id}")
	public String update(@PathVariable int id){
		User user =new User(id, ""+1, 1);
		userService.update(user);
		return "sucess";
	}

	@GetMapping("/test")
	public Object test(){
		User user =new User(123, "test1234", 123);
		List<User> list = userService.show(user);
		return list;
	}

}
到這裏就結束了,可以啓動項目訪問http://localhost:8888/test

然後訪問http://localhost:8888/user/test可以查看剛纔創建的數據,如下圖:

[{
	"_id": "5b7b742ae4b89c144cb976a0",
	"id": 123,
	"name": "test1234",
	"age": 123
}, {
	"_id": "5b7b7443e4b89c2a10cb3dc2",
	"id": 123,
	"name": "test1234",
	"age": 123
}, {
	"_id": "5b7b7445e4b89c2a10cb3dc3",
	"id": 123,
	"name": "test1234",
	"age": 123
}, {
	"_id": "5b7c1673e4b89c213cec3021",
	"id": 123,
	"name": "test1234",
	"age": 123
}]

 

修改和刪除這裏就不做測試了,在方法上有對應的測試訪問地址。

這裏做一個簡單的總結,通過整合幾種數據庫,包含關係型數據mysql,文件式數據庫mongodb,甚至說elasticsearch等等其實步驟都大致如下:

1.加入對應依賴
2.配置文件配置對應數據庫信息
3.數據操作層繼承想要的repository

 

mongoDB主從複製

主從複製這種方式很靈活.可用於備份,故障恢復,讀擴展等. 
最基本的設置方式就是建立一個主節點和一個或多個從節點,每個從節點要知道主節點的地址. 
這裏我們用一主一從實現mongodb的複製 

服務器已經安裝好了mongoDB,直接就可以運行mongo Client,我們要創建自己的mongod實例, 
首先把容器自行運行的先停止

mongod --shutdown

創建master實例

mongod --master --port=27081 --dbpath=/data/masterdb --logpath=/data/masterlog --fork

建 slave實例

mongod --slave  --port=27083 --dbpath=/data/slavedb --logpath=/data/slavelog   --source=27081    --fork

查看mongo進程

root@mongodb-671984-3837ac52-ky93c:~# ps -ef|grep mongod
root       116     1  0 13:09 ?        00:00:05 mongod --master --port=27081 --dbpath=/data/masterdb --logpath=/data/masterlog --fork
root       173     1  0 13:20 ?        00:00:01 mongod --slave --port=27083 --dbpath=/data/slavedb --logpath=/data/slavelog --source=27081 --fork
root       279    64  0 13:24 pts/0    00:00:00 grep mongod

測試 master,slave

連接master

 mongo --port 27081
  • 在mongo client 執行下列命令
> rs.isMaster()
{
    "ismaster" : true,
    "maxBsonObjectSize" : 16777216,
    "maxMessageSizeBytes" : 48000000,
    "maxWriteBatchSize" : 1000,
    "localTime" : ISODate("2016-09-13T05:29:37.302Z"),
    "maxWireVersion" : 4,
    "minWireVersion" : 0,
    "ok" : 1
}
> use blogs
switched to db blogs
> db.blog.insert({BlogName:"ike's Blog",writer:"ike" })
WriteResult({ "nInserted" : 1 })

連接slave

 mongo --port 27083

在mongo client 執行下列命令

> rs.isMaster()

> use blogs
switched to db blogs
> db.blog.find()
Error: error: { "ok" : 0, "errmsg" : "not master and slaveOk=false", "code" : 13435 }

Error的原因是從機默認不支持讀寫 ,解決辦法:

> rs.slaveOk()

mongoDB主重複值特點

1.副本集具有2個或者多個節點(一般最少3個) 
2.副本集具有一個主節點,其他都是從節點 
3.所有數據都是從主節點到從節點的. 
4.當主節點故障,從節點會自行推舉一個新的主節點 
5.當失敗節點恢復後,連接副本集後,重新作爲從節點

MongoDB集羣之分片技術應用

分片的目的:改善單臺機器數據的存儲及數據吞吐性能。提高在大量數據下隨機訪問性能

 

MongoDB分片成員節點(Shard,Config,Mongos)

 

 

 

 

分片:  分片是指將數據拆分,將其分散存放在不同的機器上的過程。有時也用分區(partitioning)來表示這個概念。

  幾乎所有數據庫軟件都能進行手動分片(manual sharding)。應用需要維護與若干不同數據庫服務器的連接,每個連接還是完全獨立的。應用程序管理不同服務器上不同數據的存儲,還管理在合適的數據庫上查詢數據的工作。

  Mongodb支持自動分片(autosharding),可以使數據庫架構對應用程序不可見,也可以簡化系統管理。Mongodb自動處理數據在分片上的分佈,也更容易添加和刪除分片。

 

  Mongodb的分片機制允許你創建一個包含許多臺機器(分片)的集羣。將數據子集分散在集羣中,每個分片維護着一個數據集合的子集。與單個服務器和副本集相比,使用集羣架構可以使應用程序具有更大的數據處理能力。

  複製是讓多臺服務器都擁有同樣的數據副本,每一臺服務器都是其它服務器的鏡像,而每一個分片和其它分片擁有不同的數據子集。

  爲了對應用程序隱藏數據庫架構的細節,在分片之前要先執行mongos進行一次路由過程。這個路由服務器維護着一個“內容列表”,指明瞭每個分片包含什麼數據內容。應用程序只需要連接到路由服務器,就可以像使用單機服務器一樣進行正常的請求了。路由服務器知道哪些數據位於哪個分片,可以將請求轉發給相應的分片。每個分片對請求的響應都會發送給路由服務器,路由服務器將所有響應合併在一起,返回給應用程序。對應用程序來說,它只知道自己是連接到了一臺單機mongod服務器。

在分片之前,集合實際上是一個單一的數據塊。分片依據片鍵將集合拆分爲多個數據塊,這塊數據塊被分佈在集羣中的每個分片上:

注意,數據塊列表開始的鍵值和結束的鍵值:$minkey和$maxkey。可以將$minkey認爲是”負無窮“,它比MongoDB中的任何值都要小。類似地,可以將$maxkey認爲是”正無窮“,它比MongoDB中的任何值都要大。片鍵值的範圍始終位於$minkey和$maxkey之間。這些值實際上是BSON類型。只是用於內部使用,不應該被用在應用程序中。如果希望在shell中使用的話,可以用Minkey和Maxkey常量代替。

 

現在數據已經分佈在多個分片上了,接下來做一個查詢操作。首先,做一個基於指定的用戶名的查詢:

>db.users.find({username : "user12345"})
{
  "_id" : ObjectId("50b0451951d30ac5782499e6"),
  "username" : "user12345",
  "created_at" : ISODate("2012-11-24T03:55:05.636Z")
}

可以看到查詢可以正常工作,現在運行explain()來看看MongoDB到底是如何處理這次查詢的:

>db.users.find(username : "user12345").explain()
    {
      "clusteredType" : "ParallelSort",
      "shards" : {
	    	"localhost : 30001" : [{
	    		"cursor" : "BtreeCursor username_1",
	    		"nscanned" : 1,
	    		"nscannedObjects" : 1,
	    		"n" : 1,
	    		"millis" : 0,
	    		"nYields" : 0,
	    		"nChunkSkips" : 0,
	    		"isMultiKey" : false,
	    		"indexOnly" : false,
	    		"indexBounds" : {
	    			"username" : [[
	    			               "user12345",
	    			               "user12345"
	    			               ]
	    			              ]
	    		}
	    	}]
     },
    "n" : 1,
    "nChunkSkips" : 0,
    "nYields" : 0,
    "nscanned" : 1,
    "nscannedObjects" : 1,
    "millisTotal" : 0,
    "millsAvg" : 0,
    "numQueries" : 1,
    "numShards" : 1
    }

輸出信息包含兩個部分,一個看起來比較普遍的explain()輸出嵌套在另一個explain()輸出中。外層的explain()輸出來自mongos:描述了爲了處理這個查詢,mongos所做的工作。內層的explain()輸出來自查詢所使用的分片。在本例中是localhost:30001。

由於”username“是片鍵,所以mongos能夠直接將查詢發送到正確的分片上。作爲對比,來看一下查詢所有數據的過程:

>db.users.find().explain()
    {
    	"clusteredType" : "ParallelSort",
    	"shards" : {
	    	"localhost : 30000" : [{
	    		"cursor" : "BasicCursor",
	    		"nscanned" : 37393,
	    		"nscannedObjects" : 37393,
	    		"n" : 37393,
	    		"millis" : 38,
	    		"nYields" : 0,
	    		"nChunkSkips" : 0,
	    		"isMultiKey" : false,
	    		"indexOnly" : false,
	    		"indexBounds" : {
	    			
	    		}
	    	}],
	    	"localhost : 30001" : [{
	    		"cursor" : "BasicCursor",
	    		"nscanned" : 31303,
	    		"nscannedObjects" : 31303,
	    		"n" : 31303,
	    		"millis" : 37,
	    		"nYields" : 0,
	    		"nChunkSkips" : 0,
	    		"isMultiKey" : false,
	    		"indexOnly" : false,
	    		"indexBounds" : {
 
	    		}
	    	}],
	    	"localhost : 30002" : [{
	    		"cursor" : "BasicCursor",
	    		"nscanned" : 31304,
	    		"nscannedObjects" : 31304,
	    		"n" : 31304,
	    		"millis" : 36,
	    		"nYields" : 0,
	    		"nChunkSkips" : 0,
	    		"isMultiKey" : false,
	    		"indexOnly" : false,
	    		"indexBounds" : {
 
	    		}
	    	}]
    	},
    "n" : 100000,
    "nChunkSkips" : 0,
    "nYields" : 0,
    "nscanned" : 100000,
    "nscannedObjects" : 100000,
    "millisTotal" : 111,
    "millsAvg" : 37,
    "numQueries" : 3,
    "numShards" : 3
    }
 

可以看到,這次查詢不得不訪問所有3個分片,查詢出所有數據。通常來說,如果沒有在查詢中使用片鍵,mongos就不得不將查詢發送到每個分片。包含片鍵的查詢能夠直接被髮送到目標分片或者是集羣分片的一個子集,這樣的查詢叫做定向查詢(targeted query)。有些查詢必須被髮送到所有分片,這樣的查詢叫做分散--聚集查詢(scatter-gather query):mongos將查詢分散到所有分片上,然後將各個分片的查詢結果聚集起來。

運行cluster.stop()就可以關閉整個集羣了。

>cluster.stop()

 

mongoDB分片成員節點

另外一個副本集:

# mongod --port 27011 --dbpath /data/smail_data1 --logpath /var/smail_log1/mongodb.log --keyFile /var/key/mongodb.key --shardsvr  --replSet rs_1 --clusterAut
hMode keyFile --fork --logappend
# mongod --port 27012 --dbpath /data/smail_data2 --logpath /var/smail_log2/mongodb.log --keyFile /var/key/mongodb.key --shardsvr  --replSet rs_1 --clusterAut
hMode keyFile --fork --logappend
# mongod --port 27013 --dbpath /data/smail_data3 --logpath /var/smail_log3/mongodb.log --keyFile /var/key/mongodb.key --shardsvr  --replSet rs_1 --clusterAut
hMode keyFile --fork --logappend

# mongo  --port 27011
> conf={_id:'rs_1',members:[{_id:1,host:'10.166.224.7:27011'}]}
{
    "_id" : "rs_1",
    "members" : [
        {
            "_id" : 1,
            "host" : "10.166.224.7:27011"   ## 10.166.224.7
        }
    ]
}
> rs.initiate(conf)
{ "ok" : 1 }


rs_1:PRIMARY> db.createUser({user:'root',pwd:'root',roles:["root"]})
Successfully added user: { "user" : "root", "roles" : [ "root" ] }
rs_1:PRIMARY> db.auth('root','root')
1
rs_1:PRIMARY> rs.add('10.166.224.7:27013')
{ "ok" : 1 }
rs_1:PRIMARY> rs.add('10.166.224.7:27012')
{ "ok" : 1 }

裏有兩個細節: 
1.節點之間需要keyfile認證,集羣內節點使用keyFile使用的祕鑰必須相同 
2.不同的服務器使用同一網段註冊節點,config server和 副本集 的配置信息不要使用127.0.0.1 或者localhost

config server

mongod --configsvr --port 27019  --dbpath=/data/medium_conf --logpath=/var/conf_log/mongodbconf.log --fork  --logappend --keyFile /var/key/mongodb.key  --clusterAuthMode keyFile
root@medium-701747-230f8712-ee7xm:~# mongo --port 27019
configsvr> use admin
switched to db admin
configsvr> db.createUser(user:'root',pwd:'root',roles:["root"])   

mongos

mongos --port 27021  --logpath=/var/mongosdb_log/mongos.log --fork --logappend  --configdb 10.166.224.4:27019 --keyFile /var/key/mongodb.key  --clusterAuthMode keyFile
                                                                                          ## 使用和分片同一網段的ip

​​​​Add Shard

momgo --port 27021

mongos> use admin
switched to db admin
mongos> db.auth('root','root')

mongos> sh.addShard("rs_2/10.166.224.4:27011")   ##添加同一主機下的shard成員
{ "shardAdded" : "rs_2", "ok" : 1 }

mongos> sh.addShard("rs_1/10.166.224.7:27011")  ##添加10.166.224.7下的shard成員
{ "shardAdded" : "rs_1", "ok" : 1 }

mongos> sh.status()   ## sharding status

Enable Sharding

測試分片集羣:

mongos> use test  ##database
mongos> for(i=0;i<20000;i++){db.shardtest.insert({'url':'intricate-sutra.com','name':"ike's blog",'i':i})};   ##collection:shardtest
mongos> db.shardtest.stats()   ##查看集合狀態,發現shards內只有rs_1一個shard

##數據分片
mongos> sh.enableSharding("test")  
mongos> sh.shardCollection("test.shardtest",{_id:1})  

 

 

 

束後balance會根據chunk的數量進行數據遷移,直到chunk的數量平均分配到每個shard上,如圖:

 

chunk和balance

balancing: 
均衡器負責數據的遷移,會週期性的檢查分片是否存在不均衡,如果存在balance進程會進行chunk的遷移. 
balance進行均衡的條件是chunk數量的多少,而不是chunk大小

mongos> use config
mongos> db.locks.find({_id:"balancer"}).pretty()  ##查看balance的狀態

chunk: 
chunkSize的大小默認是64M,可以修改chunk的大小,使數據分佈更均衡

mongos> use config
mongos> db.settings.save({"_id" : "chunksize", "value" : NumberLong(32)})
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })

也可以在數據插入前配置數據存儲chunk

mongos> sh.enableSharding('test')
mongos> db.createCollection("job" )  ##創建集合:test.job
mongos> sh.shardCollection('test.job',{'offer':1})
mongos> sh.splitAt("test.job",{offer:20})   ##按offer的20作爲split的點

splitting是後臺進程,按照middle或者chunk大小splitting數據,上面的操作就是按照middle(split的點)劃分.

哈希分片(hash key)

分片過程中利用哈希索引作爲分片的單個鍵. 
哈希分片的片鍵只能使用一個字段. 
哈希片鍵最大的好處就是保證數據在各個節點分佈基本均勻.

手動分片

請求查詢機制

 

這裏寫圖片描述

 

簡單通過路由查詢(Routed Request):當一個查詢請求發送到mongos之後,mongos會根據內部的一些機制定位到某一個shard上面,然後shard將查詢結果返回給mongos。

這裏寫圖片描述

 

方式二、分散聚合查詢(scatter gather request):當一個查詢請求發送到mongos之後,mongos會將此查詢分散到多個shard節點進行查詢。然後shard成員節點返回對應的結果由mongos進行結合將最終的結果返回給用戶。

這裏寫圖片描述

方式三、分佈式排序查詢機制(distributed merge sort request):當一個排序查詢請求發送到mongos之後,mongos會將此查詢分散到多個shard節點進行查詢。然後shard成員節點返回對應的結果(此結果是在shard內部排好序的)由mongos進行結合將最終的結果返回給用戶。

 

注:Shard:分片  Replocation:副本集

 

MongDB行業應用

比如遊戲、物流、電商、內容管理、社交、物聯網、視頻直播等,以下是幾個實際的應用案例。

遊戲場景:使用 MongoDB 存儲遊戲用戶信息,用戶的裝備、積分等直接以內嵌文檔的形式存儲,方便查詢、更新

物流場景:使用 MongoDB 存儲訂單信息,訂單狀態在運送過程中會不斷更新,以 MongoDB 內嵌數組的形式來存儲,一次查詢就能將訂單所有的變更讀取出來。

社交場景:使用 MongoDB 存儲存儲用戶信息,以及用戶發表的朋友圈信息,通過地理位置索引實現附近的人、地點等功能

物聯網場景:使用 MongoDB 存儲所有接入的智能設備信息,以及設備彙報的日誌信息,並對這些信息進行多維度的分析

視頻直播:使用 MongoDB 存儲用戶信息、禮物信息等

 

 

 

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