概述:大家对数据库肯定不陌生,肯定也有很多人用过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服务
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的核心概念,是数据的基本单元,非常类似于关系数据库中的行。在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!"的文档。
集合是一组文档的集,相当于关系型数据库中的数据表。
集合是动态模式的。什么意思呢?具体来说就是一个集合里面的文档可以是各式各样的。举例来说,下面的两种文档完全可以存储在同一个集合里面:
1 {"title":"hello!"}
2 {"recommend":5}
可以看出,上面两个文档不仅值得类型不同,连键也完全不一样。这和关系型数据库中一个表中只能存放相同模型的数据结构显得很不一样。但是这也就产生了一个问题:既然一个集合中可以存放任意的文档,那么多个集合的存在还有什么必要性呢?这其实可以和关系型数据表可以对应起来理解,我们可以创建一张表容纳下上面提到的title和recommend列,但是总有一个列是NULL的。这还仅仅是两个列的情况,如果出现无数的列,那么这种情况就非常糟糕了。所以不难想出一个数据库中存在多个集合的原因应该至少有如下几点:
show collections 查看当前数据库中存在哪些集合,将展示集合的名称列表。如下图所示:
help() 获取集合上的可执行命令的列表。执行语句如下:
1 db.users.help()
insert(obj) 向集合中插入一个文档。
drop() 删除当前集合,删除之后不可恢复。
dropIndex(index) 删除集合上的索引,参数为空时,删除所有索引(除了_id上的索引)
ensureIndex(keypattern[,options]) 创建索引
update(query,object[,upsert_bool,multi_bool]) 更新集合中满足条件的文档
find([query,fields]) 根据条件查询满足条件的文档
当然还有很多命令在这里没有列出,但是可以通过help()命令轻松的查看能在集合上执行的命令。
多个文档构成集合,多个集合组成数据库。一个MongoDB实例可以承载多个数据库,每个数据库可以拥有0到多个集合。
每个数据库有相应的数据文件和命名空间文件。文件的前缀是数据库的名称,后缀.ns表示命名空间文件,后缀以.0、.1等数字结尾的,表示数据文件。
数据文件的大小从64MB开始(这是在64位Windows Server 2012上看到的结果,其他环境可能有些差异),新的数据文件比上一个文件大一倍。所以能看到,chen.0的大小是64MB,chen.1的大小是128MB,chen.2是256MB。
文件使用MMAP进行内存映射,会将所有的数据文件映射到内存中,但是只是虚拟内存,只有访问到这块数据时才会交换到物力内存中。
每个数据文件会被分成一个一个的数据块,块与块之间用双向链表链接。
在命名空间文件中,保存了每个命名空间的存储信息元数据,包括其大小、块数、第一块的位置、最后一块的位置、被删除的块的链表以及索引信息。
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 pull mongo:3.4
docker run -d --name mongodb --volume /usr/local/mongodata:/data/db -p 27017:27017 mongo:3.4 --auth
docker exec -it mongodb mongo
use admin;db.createUser({ user: 'root', pwd: '123', roles: [ { role: "root", db: "admin" } ] });
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
索引也是一种常见的查询优化的方式
索引
我们在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值,就会报错
作用类似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有多少个。
MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。
这一片文章介绍一个springboot整合mongodb,如果你了解整合mysql之类的数据库,可以一带而过。
还是同样的套路,pom文件中加入mongodb依赖,完整pom文件如下:
4.0.0
com.mongo.web
springboot_mongo
0.0.1-SNAPSHOT
war
springboot_mongo
Demo project for Spring Boot
org.springframework.boot
spring-boot-starter-parent
2.0.4.RELEASE
UTF-8
UTF-8
1.8
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-tomcat
provided
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-starter-data-mongodb
org.springframework.boot
spring-boot-maven-plugin
配置文件中,配置对应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 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);
}
/**
* 分页查找
*
* user代表过滤条件
*
* pageable代表分页bean
*/
@Override
public List 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 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 users) {
mongoTemplate.insertAll(users);
}
/**
* test 测试mongodb
* @param user
* @return
*/
@Override
public List show(User user) {
mongoTemplate.insert(user);
List 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 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 find(){
return userService.findAll();
}
@GetMapping("/find/{start}")
public List 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 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,直接就可以运行mongo Client,我们要创建自己的mongod实例,
首先把容器自行运行的先停止
mongod --shutdown
mongod --master --port=27081 --dbpath=/data/masterdb --logpath=/data/masterlog --fork
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
mongo --port 27081
> 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()
1.副本集具有2个或者多个节点(一般最少3个)
2.副本集具有一个主节点,其他都是从节点
3.所有数据都是从主节点到从节点的.
4.当主节点故障,从节点会自行推举一个新的主节点
5.当失败节点恢复后,连接副本集后,重新作为从节点
分片的目的:改善单台机器数据的存储及数据吞吐性能。提高在大量数据下随机访问性能
几乎所有数据库软件都能进行手动分片(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()
另外一个副本集:
# 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
测试分片集群:
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上,如图:
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的点)划分.
分片过程中利用哈希索引作为分片的单个键.
哈希分片的片键只能使用一个字段.
哈希片键最大的好处就是保证数据在各个节点分布基本均匀.
简单通过路由查询(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:副本集
比如游戏、物流、电商、内容管理、社交、物联网、视频直播等,以下是几个实际的应用案例。
游戏场景:使用 MongoDB 存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,方便查询、更新
物流场景:使用 MongoDB 存储订单信息,订单状态在运送过程中会不断更新,以 MongoDB 内嵌数组的形式来存储,一次查询就能将订单所有的变更读取出来。
社交场景:使用 MongoDB 存储存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实现附近的人、地点等功能
物联网场景:使用 MongoDB 存储所有接入的智能设备信息,以及设备汇报的日志信息,并对这些信息进行多维度的分析
视频直播:使用 MongoDB 存储用户信息、礼物信息等