一篇文章带你了MongoDB

174 阅读18分钟

基本概念

基本介绍

MongoDB官网地址:www.mongodb.com/

MongoDB是一个基于分布式文件存储的数据库,它属于NoSQL数据库,由C++语言编写,旨在为WEB应用提供可扩展高性能数据存储解决方案。

MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似jsonbson格式,因此可以存储比较复杂的数据类型。MongoDB最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似于关系型数据库表的查询的绝大部分功能,而且还支持对数据建立索引。

NoSQL分类:键值型(key-value),文档型

MongoDB就是文档型NoSQL数据库,它文档中的数据是以类似于Json的Bson格式进行存储的,我们拿Json去理解,Json中的数据,都是key-value,key一般都是string类型的,而value就多种多样了,记住value中可以再存储一个文档。

概念解析

不管我们学习什么数据库都应该学习其中的基础概念,在MongoDB中基本的概念是文档、集合、数据库,下面我们挨个介绍,下表格将帮助我们更容易理解MongoDB中的一些概念。

SQL术语/概念MongoDB术语/概念解释/说明
databasedatabase数据库
tablecollection数据库表/集合
rowdocument数据记录行/文档
columnfield字段/域
indexindex索引
table joins表连接,MongoDB不支持
primary keyobjectId主键

通过下图实例,我们也可以更直观的了解MongoDB中的一些概念:

image.png

数据库

一个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…

选择对应的版本

image.png

下载完安装包后,解压tgz(下面演示的是64位的linux上的安装)。

tar -xf mongodb-linux-x86_64-rhel80-4.0.28.tgz
mv mongodb-linux-x86_64-rhel80-4.0.28 mongodb

image.png

解压之后的bin目录下的文件如下图所示

1.png

然后必须创建mongodb默认的数据文件夹,默认路径:/data/db。当然也可以通过配置文件指定目录。

mkdir -p /data/db

接着可以通过后台进程方式启动mongodb

./mongod &

image.png

但是上述启动方式只能本地连接MongoDB,想要远程也可以连接MongoDB,需要以配置文件方式启动。首先创建MongoDB配置文件mongodb.cfg,如下图所示

image.png

配置文件的内容如下:

#数据库文件位置
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以及chunkserver的映射关系。

MongoDB的客户端直接与路由节点相连,从配置节点上查询配置信息,根据配置信息再接着到实际的存储数据节点查询和存储数据。

MongoDB的副本集与分片混合部署的方式如下图所示

image.png

  • 相同的副本集中的节点存储的数据是一样的,副本集中的节点分为主节点、从节点,副本集这种主从架构是为了高性能、高可用以及数据备份。
  • 不同的副本集中的节点存储的数据是不一样的,这样设计主要是为了解决高扩展问题,理论上是可以无限扩展的(即无限增加副本集,当然也需要在配置节点中配置)。
  • 每一个副本集可以看成一个shard(分片),多个副本集共同组成一个逻辑上的大数据节点。通过shard上面进行逻辑分块chunk(块),每个块都有自己存储的数据范围,所以说客户端请求数据的时候,首先会去读取Config Server中的映射信息,找到对应的chunk(块),最终定位到具体的副本集进行数据读写。

可以根据key的范围,映射到对应的chunk上,映射信息大致如下表所示

key rangechunk
[0,10)chunk1
[10,20)chunk2
[20,30)chunk3
[30,40)chunk4
[40,50)chunk5

chunkshard的映射信息大致如下表所示

chunkshard
chunk1shard1
chunk2shard1
chunk3shard2
chunk4shard2
chunk5shard2

image.png

副本集集群

对于副本集集群来说,分为主从两种角色,主节点和从节点中的数据是一样的,写数据的过程只能写在主节点中,由主节点以异步的方式同步到从节点中,主节点和从节点中都能读数据,但是只有主节点中才能写数据

写数据大致示意图如下图所示

image.png

读数据则只需要从任意节点中读取,具体到哪个节点读取时可以指定的,大致示意图如下图所示

image.png

主从搭建

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()

副本集集群

上面的主从方式有个明显的缺点:如果主节点挂了之后,从节点无法自动升级为主节点。但是副本集集群是可以的,分为仲裁副本集集群无仲裁副本集集群,当主节点宕机了之后,

  • 如果采用仲裁副本集集群,那么仲裁节点会选举从节点作为新的主节点,但是一旦仲裁节点挂了,副本集集群就不会具有当主机宕机了选举从机当选主节点的能力了。

  • 如果采用无仲裁副本集集群,那么系统也会自动进行选主,切换选中的从节点为新的主节点。

image.png

仲裁副本集集群

节点一配置

# 数据库文件位置
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

配置主备和仲裁

以上三个节点的配置中一定要注意创建dbpathlogpath,分别启动三个节点,然后需要登录到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宕机

image.png

image.png

此时使用./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

无仲裁的副本集集群会自动进行选主,当然主节点宕机之后也会选取从节点作为新的主节点。

副本集与分片混合部署

我们这里为了简单演示,通过伪分布式方式,部署图如下:

image.png

数据服务器配置

副本集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});