基本概念
基本介绍
MongoDB
官网地址:www.mongodb.com/
MongoDB
是一个基于分布式文件存储的数据库,它属于NoSQL
数据库,由C++
语言编写,旨在为WEB
应用提供可扩展的高性能数据存储解决方案。
MongoDB
是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似json
的bson
格式,因此可以存储比较复杂的数据类型。MongoDB
最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似于关系型数据库表的查询的绝大部分功能,而且还支持对数据建立索引。
NoSQL分类:键值型(key-value),文档型
MongoDB就是文档型NoSQL数据库,它文档中的数据是以类似于Json的Bson格式进行存储的,我们拿Json去理解,Json中的数据,都是key-value,key一般都是string类型的,而value就多种多样了,记住value中可以再存储一个文档。
概念解析
不管我们学习什么数据库都应该学习其中的基础概念,在MongoDB
中基本的概念是文档、集合、数据库,下面我们挨个介绍,下表格将帮助我们更容易理解MongoDB
中的一些概念。
SQL术语/概念 | MongoDB术语/概念 | 解释/说明 |
---|---|---|
database | database | 数据库 |
table | collection | 数据库表/集合 |
row | document | 数据记录行/文档 |
column | field | 字段/域 |
index | index | 索引 |
table joins | 表连接,MongoDB 不支持 | |
primary key | objectId | 主键 |
通过下图实例,我们也可以更直观的了解MongoDB
中的一些概念:
数据库
一个MongoDB
中可以建立多个数据库。MongoDB
的默认数据库为db
,该数据库存储在data
目录中(安装是明可以默认,可以指定,但是必须该目录是存储的)。MongoDB
的单个实例可以容纳多个独立的数据库,每一个都有自己的集合和权限,不同的数据库也放置在不同的文件中。
show dbs
命令可以显示所有的数据列表
$ ./mongo
MongoDB shell version: 3.0.6
connecting to : test
> show dbs
local 0.078GB
test 0.078GB
执行db
命令可以显示当前数据库对象或者集合
$ ./mongo
MongoDB shell version: 3.0.6
connecting to : test
> db
test
运行use
命令,可以连接到一个指定的数据库。
> use local
switched to db local
> db
local
以上实例命令中,'local'是你要连接的数据库。数据库也通过名字来标识,可以满足以下条件的任意UTF-8
字符串。
- 不能是空字符串
- 不得含有' '(空格)、.、$、/、\和/0(空字符)
- 应全部小写
- 最多64字节
有些数据库名是保留的,可以直接访问这些有特殊作用的数据库。
admin
: 从权限的角度看,这是root
数据库,要是将一个用户添加到这个数据库,这个用户自动继承所有数据库权限。一些特定的服务端命令也只能从这个数据库运行,比如列出所有的数据库或者关闭服务器。local
: 这个数据永远被复制,可以用来存储限于本地单台都武器的任意集合。config
:当MongoDB
用于分片设置时,config
数据库在内部使用,用于保存分片的相关信息。
集合
集合就是MongoDB
的文档组,类似于RDBMS
(关系数据库管理系统)中的表。
集合存在于数据库中,集合没有固定的结构,这意味着你在对集合可以插入不同格式和类型的数据,但是通常情况下我们插入集合的数据都会具有一定的关联性。
比如,我们可以将以下不同数据结构的文档插入到集合中:
{"name":"Job", "age":15}
{"name":"张三", "age":19, "sex":"男"}
{"name":"李四", "age":19, "sex":"男","fullname":"李小四"}
注意:当第一个文档插入时,集合就会被创建。一个collection
(集合)中的所有field
(域)是collection
(集合)中所有document
(文档)中包含的field
(域)的并集。
合法的集合名
- 集合名不能是空字符串
- 集合名不能含有
\0
字符(空字符),这个字符表示集合名的结尾。 - 集合名不能以
system.
开头,这是为系统集合保留的前缀。
文档
文档是一组键值(key-value
)对(即Bson
)。MongoDB
的文档不需要设置相同的字段,并且相同的字段不需要相同的数据类型,这与关系型数据库有很大区别,这也是MongoDB
非常突出的特点。一个简单的文档例子如下:
{"name":"Job", "age":15}
需要注意的是:
- 文档中的键值对都是有序的
- 文档中的值不仅可以是在双引号里面的字符串,还可以是其他几种数据类型(甚至可以是整个文档嵌入的文档)
MongoDB
区分类型和大小写MongoDB
的文档不能有重复的键- 文档的键是字符串,除了少数的例外情况,键可以使用任意的
UTF-8
字符串。
文档键命名规范:
- 键不能含有\0(空字符)。这个字符用来表示键的结尾
- .和$有特别的意义,只有在特定环境下才能使用
- 以下划线_开头的键是保留的。
数据类型
下表为MongoDB
中常用的几种数据类型:
数据类型 | 说明 | 解释 | 举例 |
---|---|---|---|
Strng | 字符串 | UTF-8编码的字符串才是合法的 | {"name":"Job"} |
Integer | 整型数值 | 根据你所采用的服务器,分为32位或64位 | {"age":1} |
Boolean | 双精度浮点值 | 用于存储布尔值 | {"amount":3.14} |
Double | 字符串 | 用于存储浮点值 | {"name":"Job"} |
ObjectID | 对象ID | 用于创建文档的ID | {"id":ObjectId("123123")} |
Array | 数组 | 用于将数组或者列表或多个值存储为一个键 | {"arr":["a","b"]} |
Null | 空值 | 表示空值或者未定义的对象 | {"name":null} |
Date | 日期 | 日期时间 | {"sys_create_time": ISODate("2022-03-30T09:22:09.651Z")} |
例子如下:
{
"_id": "04143590-5b5f-4ec1-a6e9-b496ac67af80",
"sys_creator_id": "0009CBFE-0000-0000-0000-00006A0A862D",
"sys_creator_name": "张三",
"sys_create_time": ISODate("2022-03-30T09:22:09.651Z"),
"status": NumberInt("4"),
"entered_person": null,
"entered_time": null,
"print_Label_flag": false,
"_class": "com.fs.b2b.cangchu.repository.pojo.BoxDetailInfo"
}
应用场景
适用场景
一、更高的写入负载
默认情况下,MongoDB
更侧重于数据写入性能,而非事务安全,MongoDB
很适合业务系统中有大量“低价值”数据的场景,但是应当避免在高事务安全性的系统中使用MongoDB
,除非能从架构设计上办证事务安全。
二、高可用性
MongoDB
的副本集(Master-Slave
)配置非常简便,此外,MongoDB
可以快速响应处理单点故障,自动、安全的完成故障转移。这些特性使得MongoDB
能在一个相对不稳定(如云主机)的环境中,保持高可用性。
三、数据量很大或者未来会变得很大
依赖数据库(MySQL
)自身的特性,完成数据的扩展是比较困难的,在MySQL
中,当一个单表达到5-10GB
时会出现明显的性能降级,此时需要通过数据的水平和垂直拆分、库的拆分完成扩展,使用MySQL
通常需要借助驱动层或代理层完成这类需求,而MongoDB
内建了多种数据分片的特性,可以很好的适应大数据量的需求。
四、基于位置的数据查询
MongoDB
支持二维空间索引,因此可以快速并且精确的从指定的位置获取数据。
五、表结构不明确,且数据在不断变大
在一些传统的RDBMS
中,增加一个字段会锁住整个数据库/表,或者在进行一个高负载的请求时会明显造成其他请求的性能降级。因MongoDB
是文档型数据库,为非结构化的文档,增加一个字段是很快速的操作,并且不会影响已有数据,另外一个好处当业务数据发生变化时,是将不在需要由DBA
修改表结构。
六、没有DBA支持
如果没有专职的DBA
,并且准备不使用标准的关系型思想(结构化、连接等)来处理数据,那么MongoDB
将会是你的首选。
不适用场景
在某些场景下,MongoDB
作为一个非关系型数据库有其局限性,MongoDB
不支持事务操作。所以需要用到事务的应用建议不要用MongoDB
,另外MongoDB
不支持join
操作,需要复杂查询的应用也不建议使用MongoDB
。
安装和常用命令
简单安装
MongoDB
提供了linux
各发行版本64
位的安装包,你可以在官网中下载安装包。下载地址:www.mongodb.com/try/downloa…
选择对应的版本
下载完安装包后,解压tgz
(下面演示的是64
位的linux
上的安装)。
tar -xf mongodb-linux-x86_64-rhel80-4.0.28.tgz
mv mongodb-linux-x86_64-rhel80-4.0.28 mongodb
解压之后的bin
目录下的文件如下图所示
然后必须创建mongodb
默认的数据文件夹,默认路径:/data/db
。当然也可以通过配置文件指定目录。
mkdir -p /data/db
接着可以通过后台进程方式启动mongodb
。
./mongod &
但是上述启动方式只能本地连接MongoDB
,想要远程也可以连接MongoDB
,需要以配置文件方式启动。首先创建MongoDB
配置文件mongodb.cfg
,如下图所示
配置文件的内容如下:
#数据库文件位置
dbpath=/data/db
#日志文件位置
logpath=/root/mongodb/singleton/logs/mongodb.log
# 以追加方式写入日志
logappend=true
# 是否以守护进程方式运行
fork=true
#绑定客户端访问的ip, 0.0.0.0表示任意ip都能访问
bind_ip=0.0.0.0
# 默认27017
port=27017
然后以配置文件方式启动MongoDB
,如下:
# -f表示指定配置文件路径,--fork必须要加的
./mongod -f /opt/mongodb/config/mongodb.cfg --fork
接着可以通过mongo
命令连接mongodb
客户端进行指令操作。
常用命令
创建数据库
> use test
switched to db test
如果你想查看所有数据库,可以使用show dbs
命令
> show dbs
admin 0.000GB
config 0.000GB
hs 0.000GB
local 0.000GB
可以看到,我们刚创建的test
并不在数据库的列表中,要显示它,我们还需要往test
中插入一条数据。db.mycollection.insert({"name":"Job", age:18})
表示往test
库中的mycollection
集合中插入文档数据{"name":"Job", age:18}
。
> db.mycollection.insert({"name":"Job", age:18})
2022-12-29T20:19:14.885+0800 I STORAGE [conn1] createCollection: test.mycollection with generated UUID: d6c039b2-8fa9-48a8-9ebb-31d4391e6b0d
WriteResult({ "nInserted" : 1 })
> show dbs
admin 0.000GB
config 0.000GB
hs 0.000GB
local 0.000GB
test 0.000GB
删除数据库
MongoDB
删除数据库的命令如下:
db.dropDatabase()
演示删除test
库,如下:
> db
test
> db.dropDatabase()
2022-12-29T20:23:13.673+0800 I COMMAND [conn1] dropDatabase test - starting
2022-12-29T20:23:13.674+0800 I COMMAND [conn1] dropDatabase test - dropping 0 collections
2022-12-29T20:23:13.675+0800 I COMMAND [conn1] dropDatabase test - finished
{ "dropped" : "test", "ok" : 1 }
>
> show dbs
admin 0.000GB
config 0.000GB
hs 0.000GB
local 0.000GB
创建集合
MongoDB
中使用createCollection()
方法创建集合(或者简单点直接通过上面的db.mycollection.insert({"name":"Job", age:18})
),语法格式:
db.createCollection(name, options)
参数说明:
name
:要创建的集合名称options
:可选参数,指定有关额内存大小以及索引的选项。
options
可以是如下参数:
字段 | 类型 | 描述 |
---|---|---|
capped | 布尔 | (可选)如果为true,则创建固定集合。固定集合是指有着固定大小的集合,当达到最大值时,它会自动覆盖最早的文档。当该值为true时,必须指定size参数。 |
autoIndexId | 布尔 | (可选)如果为true,自动在_id字段创建索引。默认为false. |
size | 数值 | (可选)为固定集合指定一个最大值(以字节计),如果capped为true,也需要指定该字段 |
max | 数值 | (可选)指定固定集合中包含文档的最大数量 |
在插入文档时,MongoDB
首先检查固定集合的size
字段,然后检查max
字段。
我们接下来演示在hs
数据库中创建student
集合:
> use hs
switched to db hs
> db.createCollection("student")
2022-12-29T20:53:59.396+0800 I STORAGE [conn1] createCollection: hs.student with generated UUID: 08c745c4-2c2a-4292-8f42-febddd660e48
{ "ok" : 1 }
如果要查看所有的集合,可以使用show collections
命令查看
> show collections
mycollection
student
删除集合
删除集合的语法格式如下:
db.集合名.drop()
下面演示一下删除集合student
> show collections
mycollection
student
> db.student.drop()
2022-12-29T20:57:23.037+0800 I COMMAND [conn1] CMD: drop hs.student
2022-12-29T20:57:23.037+0800 I STORAGE [conn1] Finishing collection drop for hs.student (08c745c4-2c2a-4292-8f42-febddd660e48).
true
> show collections
mycollection
插入文档
MongoDB
使用insert()
或者save()
方法向集合中插入文档,简单演示如下:
> db.student.insert({"name":"Job", age:20})
2022-12-29T21:00:33.310+0800 I STORAGE [conn1] createCollection: hs.student with generated UUID: 6a1ac3c5-24db-493b-9413-5a60ed3ef610
WriteResult({ "nInserted" : 1 })
> db.student.find()
{ "_id" : ObjectId("63ad8f71f0a3080dba6d4052"), "name" : "Job", "age" : 20 }
查询文档
MongoDB
查询数据的语法格式如下:
db.collection.find(query, projection)
query
:可选,使用查询操作符指定查询条件projection
:可选,使用投影操作符指定返回的键。查询时候返回所有键值,只需省略该参数即可。
> db.student.find()
{ "_id" : ObjectId("63ad8f71f0a3080dba6d4052"), "name" : "Job", "age" : 20 }
文档分页
我们可以对比mysql
中的分页关键字limit(偏移量, 每页记录数)
,而MongoDB
这边的通过skip()
方法跳过偏移量,通过limit()
截取需要的记录数。简单功能:我们想要查询学生集合student
中第一页的数据,每页展示10条。
db.student.find().skip(0).limit(10)
文档排序
在MongoDB
中使用sort()
方法对文档进行排序,sort()
方法可以通过参数指定排序的字段,并使用1
和-1
来指定升降序,其中1
表示升序,-1
表示降序。
简单功能:将学生集合student
中的数据按照age
降序排序,并返回第一页,每页展示10条数据
db.student.find().sort({"age":-1}).skip(0).limit(10)
聚合查询
高级聚合查询可以参考文章:www.cnblogs.com/zhoujie/p/m…
Java客户端
mongodb-java-driver
一、引入依赖
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>3.10.1</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
二、编写测试代码
public class MongodbDemo {
private MongoClient client;
/**
* 设置mongodb连接信息
*/
@Before
public void init() {
client = new MongoClient("43.139.184.208",27017);
}
/**
* 连接指定名称的数据库,如果数据库不存在则Mongodb会自动创建为该名称的数据库
*/
@Test
public void connectDB() {
MongoDatabase database = client.getDatabase("huangshuai");
}
/**
* 创建集合
*/
@Test
public void createCollection() {
MongoDatabase database = client.getDatabase("huangshuai");
database.createCollection("mycollection");
}
/**
* 获取集合中的文档信息
*/
@Test
public void getCollection() {
MongoDatabase database = client.getDatabase("huangshuai");
MongoCollection<Document> collection = database.getCollection("mycollection");
MongoIterable<String> collectionNames = database.listCollectionNames();
for (String string : collectionNames) {
System.out.println("collection name : "+ string);
}
}
/**
* 插入文档
*/
@Test
public void insertDocument() {
MongoDatabase database = client.getDatabase("huangshuai");
MongoCollection<Document> collection = database.getCollection("mycollection");
Document document = new Document("name", "James");
document.append("age", 34);
document.append("sex", "男");
Document document2 = new Document("name", "wade");
document2.append("age", 36);
document2.append("sex", "男");
Document document3 = new Document("name", "cp3");
document3.append("age", 32);
document3.append("sex", "男");
List<Document> documents = new ArrayList<>();
documents.add(document);
documents.add(document2);
documents.add(document3);
//批量插入
collection.insertMany(documents);
System.out.println("文档插入成功");
}
/**
* 获取集合中的文档信息
*/
@Test
public void findDocuments() {
MongoDatabase database = client.getDatabase("huangshuai");
System.out.println("connect successful");
MongoCollection<Document> collection = database.getCollection("mycollection");
System.out.println("获取集合成功:"+collection.getNamespace());
FindIterable<Document> iterable = collection.find();
for (Document document : iterable) {
System.out.println(document.toJson());
}
}
/**
* 根据条件更新文档信息
*/
@Test
public void updateDocuments() {
MongoDatabase database = client.getDatabase("huangshuai");
System.out.println("connect successful");
MongoCollection<Document> collection = database.getCollection("mycollection");
System.out.println("获取集合成功:"+collection.getNamespace());
collection.updateMany(Filters.eq("age",18), new Document("$set", new Document("age", 20)));
FindIterable<Document> iterable = collection.find();
for (Document document : iterable) {
System.out.println(document.toJson());
}
}
/**
* 根据条件删除文档信息
*/
@Test
public void deleteDocuments() {
MongoDatabase database = client.getDatabase("huangshuai");
System.out.println("connect successful");
MongoCollection<Document> collection = database.getCollection("mycollection");
System.out.println("获取集合成功:"+collection.getNamespace());
// 删除符合条件的第一个文档
collection.deleteOne(Filters.eq("age",20));
collection.deleteOne(Filters.eq("name","James"));
// 删除符合条件的所有文档
collection.deleteMany(Filters.eq("age",20));
FindIterable<Document> iterable = collection.find();
for (Document document : iterable) {
System.out.println(document.toJson());
}
}
}
spring-data-mongodb
一、引入依赖
<dependencies>
<!--mongodb依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
二、在配置文件application.properties
中编写mongodb
数据库连接信息
server.port=8080
mongodb.basicdata.uri=mongodb://43.139.184.208:27017/test?connectTimeoutMS=30000&socketTimeoutMS=20000&maxPoolSize=300&minPoolSize=18&maxIdleTimeMS=8000000&waitQueueMultiple=4&waitQueueTimeoutMS=1000
三、注入MongoTemplate
package com.hs.mongodb.config;
import org.springframework.boot.autoconfigure.mongo.MongoProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* @Auther: huangshuai
* @Date: 2022/12/31 21:38
* @Description:
* @Version:
*/
@Configuration
@ConfigurationProperties(prefix = "mongodb")
public class MultipleMongoProperties {
private MongoProperties basicdata = new MongoProperties();
public MultipleMongoProperties() {
}
public MongoProperties getBasicdata() {
return basicdata;
}
public void setBasicdata(MongoProperties basicdata) {
this.basicdata = basicdata;
}
}
package com.hs.mongodb.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoTransactionManager;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoClientDatabaseFactory;
/**
* @Auther: huangshuai
* @Date: 2022/12/31 21:51
* @Description:
* @Version:
*/
@Configuration
public class MultipleMongoConfig {
@Autowired
private MultipleMongoProperties mongoProperties;//注入mongodb连接信息
/**
* 通过mongodb数据源配置Mongodb工厂
*/
@Bean(name = "basicdataMongoDbFactory")
@Qualifier(value = "basicdataMongoDbFactory")
public SimpleMongoClientDatabaseFactory basicdataMongoDbFactory() {
return new SimpleMongoClientDatabaseFactory(this.mongoProperties.getBasicdata().getUri());
}
/**
* 通过Mongodb工厂创建MongoTemplate
*/
@Bean(name = "basicdataMongoTemplate")
public MongoTemplate basicdataMongoTemplate() {
return new MongoTemplate(basicdataMongoDbFactory());
}
/**
* 通过Mongodb工厂创建MongoTransactionManager事务管理器,
* 后续需要用到mongodb事务时候,
* 只需要在@Transactional(value = "basicdataTransactionManager",propagation = Propagation.REQUIRED)
*/
@Bean(name = "basicdataTransactionManager")
public MongoTransactionManager basicdataTransactionManager() {
return new MongoTransactionManager( basicdataMongoDbFactory());
}
}
四、编写持久层代码
package com.hs.mongodb.pojo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.mongodb.core.mapping.Field;
import java.util.Date;
/**
* @Auther: huangshuai
* @Date: 2022/12/31 21:58
* @Description:
* @Document指定实体类对应mongodb中的集合名
* @Id指定集合的主键
* @Field指定属性对应集合名中的域
*
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Document("t_employee")
public class Employee {
@Id
private String id;
/**
* 员工姓名
*/
@Field("name")
private String name;
/**
* 员工工号
*/
@Field("wk_no")
private String wkNo;
/**
* 员工性别
*/
@Field("sex")
private String sex;
/**
* 员工年龄
*/
@Field("age")
private Integer age;
/**
* 生日
*/
@Field("birthday")
private Date birthday;
}
package com.hs.mongodb.repository;
import com.hs.mongodb.pojo.Employee;
import java.util.List;
/**
* @Auther: huangshuai
* @Date: 2022/12/31 22:04
* @Description:
* @Version:
*/
public interface EmployeeDao {
List<Employee> qryAllEmployee();
boolean insertEmployee(Employee employee);
boolean updateEmployeeAgeByName(String name, Integer age);
}
package com.hs.mongodb.repository.impl;
import com.hs.mongodb.pojo.Employee;
import com.hs.mongodb.repository.EmployeeDao;
import com.mongodb.client.result.UpdateResult;
import org.springframework.beans.factory.annotation.Autowired;
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.Repository;
import java.util.List;
/**
* @Auther: huangshuai
* @Date: 2022/12/31 22:04
* @Description:
* @Version:
*/
@Repository
public class EmployeeDaoImpl implements EmployeeDao {
@Autowired
private MongoTemplate mongoTemplate;
@Override
public List<Employee> qryAllEmployee() {
return mongoTemplate.findAll(Employee.class);
}
@Override
public boolean insertEmployee(Employee employee) {
Employee insertRes = mongoTemplate.insert(employee);
return insertRes != null;
}
@Override
public boolean updateEmployeeAgeByName(String name, Integer age) {
Criteria criteria = new Criteria();
criteria.and("name").is(name);
Query query = new Query(criteria);
Update update = new Update();
update.set("age", age);
UpdateResult updateResult = mongoTemplate.updateMulti(query, update, Employee.class);
return updateResult.getMatchedCount() > 0;
}
}
图解底层原理
MongoDB
的部署方案有单机部署、副本集(主备)部署、分片部署、副本集与分片混合部署。
副本集与分片混合部署
MongoDB
的集群部署方案有三类角色:实际数据存储节点,配置文件存储节点和路由接入节点。
- 实际数据存储节点的作用就是存储数据
- 路由接入节点的作用是在分片的情况下起到负载均衡的作用
- 配置文件存储节点的作用其实存储的是片键与
chunk
以及chunk
与server
的映射关系。
MongoDB
的客户端直接与路由节点相连,从配置节点上查询配置信息,根据配置信息再接着到实际的存储数据节点查询和存储数据。
MongoDB
的副本集与分片混合部署的方式如下图所示
- 相同的副本集中的节点存储的数据是一样的,副本集中的节点分为主节点、从节点,副本集这种主从架构是为了高性能、高可用以及数据备份。
- 不同的副本集中的节点存储的数据是不一样的,这样设计主要是为了解决高扩展问题,理论上是可以无限扩展的(即无限增加副本集,当然也需要在配置节点中配置)。
- 每一个副本集可以看成一个
shard
(分片),多个副本集共同组成一个逻辑上的大数据节点。通过shard
上面进行逻辑分块chunk
(块),每个块都有自己存储的数据范围,所以说客户端请求数据的时候,首先会去读取Config Server
中的映射信息,找到对应的chunk
(块),最终定位到具体的副本集进行数据读写。
可以根据key
的范围,映射到对应的chunk
上,映射信息大致如下表所示
key range | chunk |
---|---|
[0,10) | chunk1 |
[10,20) | chunk2 |
[20,30) | chunk3 |
[30,40) | chunk4 |
[40,50) | chunk5 |
chunk
跟shard
的映射信息大致如下表所示
chunk | shard |
---|---|
chunk1 | shard1 |
chunk2 | shard1 |
chunk3 | shard2 |
chunk4 | shard2 |
chunk5 | shard2 |
副本集集群
对于副本集集群来说,分为主从两种角色,主节点和从节点中的数据是一样的,写数据的过程只能写在主节点中,由主节点以异步的方式同步到从节点中,主节点和从节点中都能读数据,但是只有主节点中才能写数据。
写数据大致示意图如下图所示
读数据则只需要从任意节点中读取,具体到哪个节点读取时可以指定的,大致示意图如下图所示
主从搭建
MongoDB
的主从方式,其实官方已经不推荐了,但是要理解主从的一些特性。默认从机是不可操作,只是作为数据备份的,而且如果主节点挂了,从节点无法自动升级为主节点(副本集是可以自动切换的),另外如果需要从机对外提供读的操作,需要单独发送指令rs.slaveOk()
。
我们这里为了简单演示,就利用伪分布式的方式。
- 伪分布式:在同一台机器,使用多个不同的端口,去启动多个实例,组成一个分布式系统。
- 真正分布式:在不同机器,使用相同的端口,分别启动实例。如果是真正的分布式搭建,一定要保证网络畅通和防火墙问题。
下面我们简单演示一主一从的方式,其实就是准备两个mongodb
配置文件mongodb.cfg
,分别配置主节点和从节点,然后通过mongodb
指定配置文件方式分别启动主节点和从节点。
主节点的配置文件如下:
#数据库文件位置
dbpath=/root/mongodb/ms/master/data
#日志文件位置
logpath=/root/mongodb/ms/master/logs/mongodb.log
# 以追加方式写入日志
logappend=true
# 是否以守护进程方式运行
fork=true
#绑定客户端访问的ip,0.0.0.0表示任意ip都能连接mongodb
bind_ip=0.0.0.0
# 默认27017
port=27001
# 主从模式下,指定我自身的角色是主机
master=true
# 主从模式下,从机的地址信息
source=43.139.184.208:27002
从节点的配置文件如下:
#数据库文件位置
dbpath=/root/mongodb/ms/slave/data
#日志文件位置
logpath=/root/mongodb/ms/slave/logs/mongodb.log
# 以追加方式写入日志
logappend=true
# 是否以守护进程方式运行
fork=true
#绑定客户端访问的ip,0.0.0.0表示任意ip都能连接mongodb
bind_ip=0.0.0.0
# 默认27017
port=27002
# 主从模式下,指定我自身的角色是从机
slave=true
# 主从模式下,主机的地址信息
source=43.139.184.208:27001
接着分别启动主机和从机
# 启动主机
./mongod -f /hs/mongodb/ms/master/mongodb.cfg --fork
# 启动从机
./mongod -f /hs/mongodb/ms/slave/mongodb.cfg --fork
客户端连接主机和从机测试
# 连接主机
./mongo 43.139.184.208:27001
# 连接从机
./mongo 43.139.184.208:27002
测试命令
db.isMaster()
想要从机也具备读操作,需要在连接从机,并执行下面的命令。
rs.slaveOk()
副本集集群
上面的主从方式有个明显的缺点:如果主节点挂了之后,从节点无法自动升级为主节点。但是副本集集群是可以的,分为仲裁副本集集群和无仲裁副本集集群,当主节点宕机了之后,
-
如果采用仲裁副本集集群,那么仲裁节点会选举从节点作为新的主节点,但是一旦仲裁节点挂了,副本集集群就不会具有当主机宕机了选举从机当选主节点的能力了。
-
如果采用无仲裁副本集集群,那么系统也会自动进行选主,切换选中的从节点为新的主节点。
仲裁副本集集群
节点一配置
# 数据库文件位置
dbpath=/root/mongodb/rs/rs01/node01/data
#日志文件位置
logpath=/root/mongodb/rs/rs01/node01/logs/mongodb.log
# 以追加方式写入日志
logappend=true
# 是否以守护进程方式运行
fork = true
bind_ip=0.0.0.0
# 默认27017
port = 27003
#注意:不需要显式的去指定主从,主从是动态选举的
#副本集集群名称,需要指定一个名称,在一个副本集下,名称是相同的
replSet=rs001
节点二配置
# 数据库文件位置
dbpath=/root/mongodb/rs/rs01/node02/data
#日志文件位置
logpath=/root/mongodb/rs/rs01/node02/logs/mongodb.log
# 以追加方式写入日志
logappend=true
# 是否以守护进程方式运行
fork = true
bind_ip=0.0.0.0
# 默认27017
port = 27004
#注意:不需要显式的去指定主从,主从是动态选举的
#副本集集群名称,需要指定一个名称,在一个副本集下,名称是相同的
replSet=rs001
节点三配置
# 数据库文件位置
dbpath=/root/mongodb/rs/rs01/node03/data
#日志文件位置
logpath=/root/mongodb/rs/rs01/node03/logs/mongodb.log
# 以追加方式写入日志
logappend=true
# 是否以守护进程方式运行
fork = true
bind_ip=0.0.0.0
# 默认27017
port = 27005
#注意:不需要显式的去指定主从,主从是动态选举的
#副本集集群名称,需要指定一个名称,在一个副本集下,名称是相同的
replSet=rs001
配置主备和仲裁
以上三个节点的配置中一定要注意创建dbpath
和logpath
,分别启动三个节点,然后需要登录到mongodb
的客户端进行配置主备和仲裁角色。
## 分别启动三个节点
/hs/mongodb/bin/mongod -f /hs/mongodb/rs/rs01/node01/mongodb.cfg
/hs/mongodb/bin/mongod -f /hs/mongodb/rs/rs01/node02/mongodb.cfg
/hs/mongodb/bin/mongod -f /hs/mongodb/rs/rs01/node03/mongodb.cfg
## 连接任意一个节点
./mongo 10.0.12.14:27003
## 使用admin数据库
use admin
## 声明变量
cfg={_id:"rs001",members: [
{_id:0,host:"10.0.12.14:27003",priority:2},
{_id:1,host:"10.0.12.14:27004",priority:1},
{_id:2,host:"10.0.12.14:27005",arbiterOnly:true}
]}
## 初始化
rs.initiate(cfg);
-
cfg
中的_id
的值是副本集名称 -
priority
:数字越大,优先级越高。优先级最高的会被选举为主库 -
arbiterOnly:true
,如果是仲裁节点,必须设置该参数
测试
rs.status()
执行结果如下:
{
"set" : "rs001",
"date" : ISODate("2023-01-04T13:11:50.491Z"),
"myState" : 1,
"term" : NumberLong(1),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1672837905, 1),
"t" : NumberLong(1)
},
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1672837905, 1),
"t" : NumberLong(1)
},
"appliedOpTime" : {
"ts" : Timestamp(1672837905, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1672837905, 1),
"t" : NumberLong(1)
}
},
"lastStableCheckpointTimestamp" : Timestamp(1672837905, 1),
"electionCandidateMetrics" : {
"lastElectionReason" : "electionTimeout",
"lastElectionDate" : ISODate("2023-01-04T13:09:45.317Z"),
"electionTerm" : NumberLong(1),
"lastCommittedOpTimeAtElection" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"lastSeenOpTimeAtElection" : {
"ts" : Timestamp(1672837774, 1),
"t" : NumberLong(-1)
},
"numVotesNeeded" : 2,
"priorityAtElection" : 2,
"electionTimeoutMillis" : NumberLong(10000),
"numCatchUpOps" : NumberLong(0),
"newTermStartDate" : ISODate("2023-01-04T13:09:45.323Z"),
"wMajorityWriteAvailabilityDate" : ISODate("2023-01-04T13:09:45.400Z")
},
## 副本集的三个节点成员
"members" : [
{
"_id" : 0,
"name" : "10.0.12.14:27003",
"health" : 1,
"state" : 1,
## PRIMARY表示10.0.12.14:27003节点是主节点
"stateStr" : "PRIMARY",
"uptime" : 191,
"optime" : {
"ts" : Timestamp(1672837905, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2023-01-04T13:11:45Z"),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1672837785, 1),
"electionDate" : ISODate("2023-01-04T13:09:45Z"),
"configVersion" : 1,
"self" : true,
"lastHeartbeatMessage" : ""
},
{
"_id" : 1,
"name" : "10.0.12.14:27004",
"health" : 1,
"state" : 2,
## SECONDARY表示10.0.12.14:27004节点是从节点
"stateStr" : "SECONDARY",
"uptime" : 136,
"optime" : {
"ts" : Timestamp(1672837905, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1672837905, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2023-01-04T13:11:45Z"),
"optimeDurableDate" : ISODate("2023-01-04T13:11:45Z"),
"lastHeartbeat" : ISODate("2023-01-04T13:11:49.330Z"),
"lastHeartbeatRecv" : ISODate("2023-01-04T13:11:49.903Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncingTo" : "10.0.12.14:27003",
"syncSourceHost" : "10.0.12.14:27003",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 1
},
{
"_id" : 2,
"name" : "10.0.12.14:27005",
"health" : 1,
"state" : 7,
## ARBITER表示10.0.12.14:27005节点是仲裁点
"stateStr" : "ARBITER",
"uptime" : 136,
"lastHeartbeat" : ISODate("2023-01-04T13:11:49.325Z"),
"lastHeartbeatRecv" : ISODate("2023-01-04T13:11:50.279Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"configVersion" : 1
}
],
"ok" : 1,
"operationTime" : Timestamp(1672837905, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1672837905, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
从上述的节点状态可知,此时
10.0.12.14:27003
是主节点,即PRIMARY
10.0.12.14:27004
是从节点,即SECONDARY
10.0.12.14:27005
是仲裁节点,即ARBITER
接着模拟主节点10.0.12.14:27003
宕机
此时使用./mongo 10.0.12.14:27004
连接,通过rs.status()
查看节点状态信息,如下:
{
"set" : "rs001",
"date" : ISODate("2023-01-04T13:26:32.146Z"),
"myState" : 1,
"term" : NumberLong(2),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1672838595, 1),
"t" : NumberLong(1)
},
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1672838595, 1),
"t" : NumberLong(1)
},
"appliedOpTime" : {
"ts" : Timestamp(1672838787, 1),
"t" : NumberLong(2)
},
"durableOpTime" : {
"ts" : Timestamp(1672838787, 1),
"t" : NumberLong(2)
}
},
"lastStableCheckpointTimestamp" : Timestamp(1672838595, 1),
"electionCandidateMetrics" : {
"lastElectionReason" : "electionTimeout",
"lastElectionDate" : ISODate("2023-01-04T13:23:27.251Z"),
"electionTerm" : NumberLong(2),
"lastCommittedOpTimeAtElection" : {
"ts" : Timestamp(1672838595, 1),
"t" : NumberLong(1)
},
"lastSeenOpTimeAtElection" : {
"ts" : Timestamp(1672838595, 1),
"t" : NumberLong(1)
},
"numVotesNeeded" : 2,
"priorityAtElection" : 1,
"electionTimeoutMillis" : NumberLong(10000),
"numCatchUpOps" : NumberLong(0),
"newTermStartDate" : ISODate("2023-01-04T13:23:27.254Z")
},
"electionParticipantMetrics" : {
"votedForCandidate" : true,
"electionTerm" : NumberLong(1),
"lastVoteDate" : ISODate("2023-01-04T13:09:45.320Z"),
"electionCandidateMemberId" : 0,
"voteReason" : "",
"lastAppliedOpTimeAtElection" : {
"ts" : Timestamp(1672837774, 1),
"t" : NumberLong(-1)
},
"maxAppliedOpTimeInSet" : {
"ts" : Timestamp(1672837774, 1),
"t" : NumberLong(-1)
},
"priorityAtElection" : 1
},
"members" : [
{
"_id" : 0,
"name" : "10.0.12.14:27003",
"health" : 0,
"state" : 8,
## 原先的主节点状态变成不健康,不可用了
"stateStr" : "(not reachable/healthy)",
"uptime" : 0,
"optime" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"optimeDurable" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"optimeDate" : ISODate("1970-01-01T00:00:00Z"),
"optimeDurableDate" : ISODate("1970-01-01T00:00:00Z"),
"lastHeartbeat" : ISODate("2023-01-04T13:26:31.263Z"),
"lastHeartbeatRecv" : ISODate("2023-01-04T13:23:15.332Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "Error connecting to 10.0.12.14:27003 :: caused by :: Connection refused",
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"configVersion" : -1
},
{
"_id" : 1,
"name" : "10.0.12.14:27004",
"health" : 1,
"state" : 1,
## 原先的从节点变成新的主节点
"stateStr" : "PRIMARY",
"uptime" : 1072,
"optime" : {
"ts" : Timestamp(1672838787, 1),
"t" : NumberLong(2)
},
"optimeDate" : ISODate("2023-01-04T13:26:27Z"),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"electionTime" : Timestamp(1672838607, 1),
"electionDate" : ISODate("2023-01-04T13:23:27Z"),
"configVersion" : 1,
"self" : true,
"lastHeartbeatMessage" : ""
},
{
"_id" : 2,
"name" : "10.0.12.14:27005",
"health" : 1,
"state" : 7,
## 还是原来的仲裁节点
"stateStr" : "ARBITER",
"uptime" : 1015,
"lastHeartbeat" : ISODate("2023-01-04T13:26:31.256Z"),
"lastHeartbeatRecv" : ISODate("2023-01-04T13:26:30.289Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "",
"configVersion" : 1
}
],
"ok" : 1,
"operationTime" : Timestamp(1672838787, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1672838787, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
从节点状态信息可知,原来的10.0.12.14:27004
是从节点转变为新的主节点(当然这是仲裁节点的功劳)。
无仲裁副本集集群
节点一配置
# 数据库文件位置
dbpath=/root/mongodb/rs/rs02/node01/data
#日志文件位置
logpath=/root/mongodb/rs/rs02/node01/logs/mongodb.log
# 以追加方式写入日志
logappend=true
# 是否以守护进程方式运行
fork = true
bind_ip=0.0.0.0
# 默认27017
port = 27006
#注意:不需要显式的去指定主从,主从是动态选举的
#副本集集群名称,需要指定一个名称,在一个副本集下,名称是相同的
replSet=rs002
节点二配置
# 数据库文件位置
dbpath=/root/mongodb/rs/rs02/node02/data
#日志文件位置
logpath=/root/mongodb/rs/rs02/node02/logs/mongodb.log
# 以追加方式写入日志
logappend=true
# 是否以守护进程方式运行
fork = true
bind_ip=0.0.0.0
# 默认27017
port = 27007
#注意:不需要显式的去指定主从,主从是动态选举的
#副本集集群名称,需要指定一个名称,在一个副本集下,名称是相同的
replSet=rs002
节点三配置
# 数据库文件位置
dbpath=/root/mongodb/rs/rs02/node03/data
#日志文件位置
logpath=/root/mongodb/rs/rs02/node03/logs/mongodb.log
# 以追加方式写入日志
logappend=true
# 是否以守护进程方式运行
fork = true
bind_ip=0.0.0.0
# 默认27017
port = 27008
#注意:不需要显式的去指定主从,主从是动态选举的
#副本集集群名称,需要指定一个名称,在一个副本集下,名称是相同的
replSet=rs002
和有仲裁的副本集基本上完全一样,只是在admin
数据库下去执行配置的时候,不需要指定优先级和仲裁节点。这种情况,如果节点挂掉,那么他们都会进行选举。
## 分别启动三个节点
/hs/mongodb/bin/mongod -f /hs/mongodb/rs/rs02/node01/mongodb.cfg
/hs/mongodb/bin/mongod -f /hs/mongodb/rs/rs02/node02/mongodb.cfg
/hs/mongodb/bin/mongod -f /hs/mongodb/rs/rs02/node03/mongodb.cfg
## 连接任意一个节点
./mongo 10.0.12.14:27006
## 使用admin数据库
use admin
cfg={_id:"rs002",members: [
{_id:0,host:"10.0.12.14:27006"},
{_id:1,host:"10.0.12.14:27007"},
{_id:2,host:"10.0.12.14:27008"}
]}
## 初始化
rs.initiate(cfg);
测试
rs.status()
执行结果如下:
{
"set" : "rs002",
"date" : ISODate("2023-01-04T13:46:19.816Z"),
"myState" : 1,
"term" : NumberLong(1),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"heartbeatIntervalMillis" : NumberLong(2000),
"optimes" : {
"lastCommittedOpTime" : {
"ts" : Timestamp(1672839968, 1),
"t" : NumberLong(1)
},
"readConcernMajorityOpTime" : {
"ts" : Timestamp(1672839968, 1),
"t" : NumberLong(1)
},
"appliedOpTime" : {
"ts" : Timestamp(1672839968, 1),
"t" : NumberLong(1)
},
"durableOpTime" : {
"ts" : Timestamp(1672839968, 1),
"t" : NumberLong(1)
}
},
"lastStableCheckpointTimestamp" : Timestamp(1672839967, 2),
"electionCandidateMetrics" : {
"lastElectionReason" : "electionTimeout",
"lastElectionDate" : ISODate("2023-01-04T13:46:07.939Z"),
"electionTerm" : NumberLong(1),
"lastCommittedOpTimeAtElection" : {
"ts" : Timestamp(0, 0),
"t" : NumberLong(-1)
},
"lastSeenOpTimeAtElection" : {
"ts" : Timestamp(1672839957, 1),
"t" : NumberLong(-1)
},
"numVotesNeeded" : 2,
"priorityAtElection" : 1,
"electionTimeoutMillis" : NumberLong(10000),
"numCatchUpOps" : NumberLong(0),
"newTermStartDate" : ISODate("2023-01-04T13:46:07.946Z"),
"wMajorityWriteAvailabilityDate" : ISODate("2023-01-04T13:46:08.764Z")
},
"members" : [
{
"_id" : 0,
"name" : "10.0.12.14:27006",
"health" : 1,
"state" : 1,
## 10.0.12.14:27006为主节点
"stateStr" : "PRIMARY",
"uptime" : 68,
"optime" : {
"ts" : Timestamp(1672839968, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2023-01-04T13:46:08Z"),
"syncingTo" : "",
"syncSourceHost" : "",
"syncSourceId" : -1,
"infoMessage" : "could not find member to sync from",
"electionTime" : Timestamp(1672839967, 1),
"electionDate" : ISODate("2023-01-04T13:46:07Z"),
"configVersion" : 1,
"self" : true,
"lastHeartbeatMessage" : ""
},
{
"_id" : 1,
"name" : "10.0.12.14:27007",
"health" : 1,
"state" : 2,
## 10.0.12.14:27007为从节点
"stateStr" : "SECONDARY",
"uptime" : 22,
"optime" : {
"ts" : Timestamp(1672839968, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1672839968, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2023-01-04T13:46:08Z"),
"optimeDurableDate" : ISODate("2023-01-04T13:46:08Z"),
"lastHeartbeat" : ISODate("2023-01-04T13:46:17.944Z"),
"lastHeartbeatRecv" : ISODate("2023-01-04T13:46:19.263Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncingTo" : "10.0.12.14:27006",
"syncSourceHost" : "10.0.12.14:27006",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 1
},
{
"_id" : 2,
"name" : "10.0.12.14:27008",
"health" : 1,
"state" : 2,
## 10.0.12.14:27008为从节点
"stateStr" : "SECONDARY",
"uptime" : 22,
"optime" : {
"ts" : Timestamp(1672839968, 1),
"t" : NumberLong(1)
},
"optimeDurable" : {
"ts" : Timestamp(1672839968, 1),
"t" : NumberLong(1)
},
"optimeDate" : ISODate("2023-01-04T13:46:08Z"),
"optimeDurableDate" : ISODate("2023-01-04T13:46:08Z"),
"lastHeartbeat" : ISODate("2023-01-04T13:46:17.946Z"),
"lastHeartbeatRecv" : ISODate("2023-01-04T13:46:19.259Z"),
"pingMs" : NumberLong(0),
"lastHeartbeatMessage" : "",
"syncingTo" : "10.0.12.14:27006",
"syncSourceHost" : "10.0.12.14:27006",
"syncSourceId" : 0,
"infoMessage" : "",
"configVersion" : 1
}
],
"ok" : 1,
"operationTime" : Timestamp(1672839968, 1),
"$clusterTime" : {
"clusterTime" : Timestamp(1672839968, 1),
"signature" : {
"hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
"keyId" : NumberLong(0)
}
}
}
由以上可知,
10.0.12.14:27006
是主节点,即PRIMARY
10.0.12.14:27007
是从节点,即SECONDARY
10.0.12.14:27008
是从节点,即SECONDARY
无仲裁的副本集集群会自动进行选主,当然主节点宕机之后也会选取从节点作为新的主节点。
副本集与分片混合部署
我们这里为了简单演示,通过伪分布式方式,部署图如下:
数据服务器配置
副本集1配置
节点一配置
# 数据库文件位置
dbpath=/root/mongodb/datasvr/rs1/node01/data
#日志文件位置
logpath=/root/mongodb/datasvr/rs1/node01/logs/mongodb.log
# 以追加方式写入日志
logappend=true
# 是否以守护进程方式运行
fork = true
bind_ip=0.0.0.0
# 默认27017
port = 27003
#注意:不需要显式的去指定主从,主从是动态选举的
#副本集集群,需要指定一个名称,在一个副本集下,名称是相同的
replSet=rs001
shardsvr=true
节点二配置
# 数据库文件位置
dbpath=/root/mongodb/datasvr/rs1/node02/data
#日志文件位置
logpath=/root/mongodb/datasvr/rs1/node02/logs/mongodb.log
# 以追加方式写入日志
logappend=true
# 是否以守护进程方式运行
fork = true
bind_ip=0.0.0.0
# 默认27017
port = 27004
#注意:不需要显式的去指定主从,主从是动态选举的
#副本集集群,需要指定一个名称,在一个副本集下,名称是相同的
replSet=rs001
shardsvr=true
节点三配置
# 数据库文件位置
dbpath=/root/mongodb/datasvr/rs1/node03/data
#日志文件位置
logpath=/root/mongodb/datasvr/rs1/node03/logs/mongodb.log
# 以追加方式写入日志
logappend=true
# 是否以守护进程方式运行
fork = true
bind_ip=0.0.0.0
# 默认27017
port = 27005
#注意:不需要显式的去指定主从,主从是动态选举的
#副本集集群,需要指定一个名称,在一个副本集下,名称是相同的
replSet=rs001
shardsvr=true
副本集2配置
节点一配置
# 数据库文件位置
dbpath=/root/mongodb/datasvr/rs2/node01/data
#日志文件位置
logpath=/root/mongodb/datasvr/rs2/node01/logs/mongodb.log
# 以追加方式写入日志
logappend=true
# 是否以守护进程方式运行
fork = true
bind_ip=0.0.0.0
# 默认27017
port = 27006
#注意:不需要显式的去指定主从,主从是动态选举的
#副本集集群,需要指定一个名称,在一个副本集下,名称是相同的
replSet=rs002
shardsvr=true
节点二配置
# 数据库文件位置
dbpath=/root/mongodb/datasvr/rs2/node02/data
#日志文件位置
logpath=/root/mongodb/datasvr/rs2/node02/logs/mongodb.log
# 以追加方式写入日志
logappend=true
# 是否以守护进程方式运行
fork = true
bind_ip=0.0.0.0
# 默认27017
port = 27007
#注意:不需要显式的去指定主从,主从是动态选举的
#副本集集群,需要指定一个名称,在一个副本集下,名称是相同的
replSet=rs002
shardsvr=true
节点三配置
# 数据库文件位置
dbpath=/root/mongodb/datasvr/rs2/node03/data
#日志文件位置
logpath=/root/mongodb/datasvr/rs2/node03/logs/mongodb.log
# 以追加方式写入日志
logappend=true
# 是否以守护进程方式运行
fork = true
bind_ip=0.0.0.0
# 默认27017
port = 27008
#注意:不需要显式的去指定主从,主从是动态选举的
#副本集集群,需要指定一个名称,在一个副本集下,名称是相同的
replSet=rs002
shardsvr=true
配置服务器配置
节点一配置
# 数据库文件位置
dbpath=/root/mongodb/configsvr/node01/data
#日志文件位置
logpath=/root/mongodb/configsvr/node01/logs/mongodb.log
# 以追加方式写入日志
logappend=true
# 是否以守护进程方式运行
fork = true
bind_ip=0.0.0.0
# 默认27017
port = 28001
# 表示是一个配置服务器
configsvr=true
#配置服务器副本集名称
replSet=configsvr
节点二配置
# 数据库文件位置
dbpath=/root/mongodb/configsvr/node02/data
#日志文件位置
logpath=/root/mongodb/configsvr/node02/logs/mongodb.log
# 以追加方式写入日志
logappend=true
# 是否以守护进程方式运行
fork = true
bind_ip=0.0.0.0
# 默认27017
port = 28002
# 表示是一个配置服务器
configsvr=true
#配置服务器副本集名称
replSet=configsvr
节点三配置
# 数据库文件位置
dbpath=/root/mongodb/configsvr/node03/data
#日志文件位置
logpath=/root/mongodb/configsvr/node03/logs/mongodb.log
# 以追加方式写入日志
logappend=true
# 是否以守护进程方式运行
fork = true
bind_ip=0.0.0.0
# 默认27017
port = 28003
# 表示是一个配置服务器
configsvr=true
#配置服务器副本集名称
replSet=configsvr
路由服务器配置
#指定配置服务器
configdb=configsvr/10.0.12.14:28001,10.0.12.14:28002,10.0.12.14:28003
#日志文件位置
logpath=/root/mongodb/routersvr/node01/logs/mongodb.log
# 以追加方式写入日志
logappend=true
# 是否以守护进程方式运行
fork = true
bind_ip=0.0.0.0
# 默认28001
port=30000
启动
首先启动两个副本集的数据服务器
## 启动副本集1
/hs/mongodb/bin/mongod -f /hs/mongodb/datasvr/rs1/node01/mongodb.cfg
/hs/mongodb/bin/mongod -f /hs/mongodb/datasvr/rs1/node02/mongodb.cfg
/hs/mongodb/bin/mongod -f /hs/mongodb/datasvr/rs1/node03/mongodb.cfg
## 连接任意一个节点
./mongo 10.0.12.14:27003
## 使用admin数据库
use admin
cfg={_id:"rs001",members: [
{_id:0,host:"10.0.12.14:27003"},
{_id:1,host:"10.0.12.14:27004"},
{_id:2,host:"10.0.12.14:27005"}
]}
## 初始化
rs.initiate(cfg);
## 启动副本集2
/hs/mongodb/bin/mongod -f /hs/mongodb/datasvr/rs2/node01/mongodb.cfg
/hs/mongodb/bin/mongod -f /hs/mongodb/datasvr/rs2/node02/mongodb.cfg
/hs/mongodb/bin/mongod -f /hs/mongodb/datasvr/rs2/node03/mongodb.cfg
## 连接任意一个节点
./mongo 10.0.12.14:27006
## 使用admin数据库
use admin
cfg={_id:"rs001",members: [
{_id:0,host:"10.0.12.14:27006"},
{_id:1,host:"10.0.12.14:27007"},
{_id:2,host:"10.0.12.14:27008"}
]}
## 初始化
rs.initiate(cfg);
接着启动配置服务器
/hs/mongodb/bin/mongod -f /hs/mongodb/configsvr/node01/mongodb.cfg
/hs/mongodb/bin/mongod -f /hs/mongodb/configsvr/node02/mongodb.cfg
/hs/mongodb/bin/mongod -f /hs/mongodb/configsvr/node03/mongodb.cfg
## 连接任意一个节点
./mongo 10.0.12.14:28001
## 使用admin数据库
use admin
cfg={_id:"rs001",members: [
{_id:0,host:"10.0.12.14:28001"},
{_id:1,host:"10.0.12.14:28002"},
{_id:2,host:"10.0.12.14:28003"}
]}
## 初始化
rs.initiate(cfg);
然后启动路由服务器(注意这里是mongos
命令而不是mongod
命令)
/hs/mongodb/bin/mongos -f /hs/mongodb/routersvr/node01/mongodb.cfg
关联切片和路由
登录路由服务器
# 登录路由服务器
./mongo 10.0.12.14:30000
#查看shard相关的命令
sh.help()
sh.addShard("切片名称/地址")
# 只要选取副本集的任意一台服务器即可
sh.addShard("rs001/10.0.12.14:27003");
sh.addShard("rs002/10.0.12.14:27006");
use kkb
# 设置对hs数据库允许切片
sh.enableSharding("hs");
# 对hs数据库的mycollection集合按照name的hash进行分片
sh.shardCollection("hs.mycollection",{name:"hashed"});
# 插入数据,分别连接副本集1和副本集2查看分片结果
for(var i=1;i<=100;i++) db.mycollection.insert({name:"James"+i,age:i});