掘金 后端 ( ) • 2024-03-22 11:05

theme: smartblue

MongoDB的使用(索引、聚合、整合应用、副本集、分片集群)

springboot整合mongodb的demo

https://gitee.com/w--kk/mongodb-test.git

索引 index

https://docs.mongodb.com/manual/indexes/

说明

索引通常能够极大的提高查询的效率,如果没有索引,MongoDB在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。这种扫描全集合的查询效率是非常低的,特别在处理大量的数据时,查询可以要花费几十秒甚至几分钟,这对网站的性能是非常致命的。

索引是特殊的数据结构,索引存储在一个易于遍历读取的数据集合中,索引是对数据库表中一列或多列的值进行排序的一种结构。

原理

image.png

从根本上说,MongoDB中的索引与其他数据库系统中的索引类似。MongoDB在集合层面上定义了索引,并支持对MongoDB集合中的任何字段或文档的子字段进行索引。

操作

0、创建索引
db.集合名称.createIndex(keys, options)
db.集合名称.createIndex({"title":1,"description":-1})
说明: 语法中 Key 值为你要创建的索引字段,1 为指定按升序创建索引,如果你想按降序来创建索引指定为 -1 即可。
createIndex() 接收可选参数,可选参数列表如下:

image.png

image.png

image.png

1、查看集合索引
db.集合名称.getIndexes()

image.png

expireAfterSeconds
image.png

2、查看集合索引大小
db.集合名称.totalIndexSize()
image.png

3、删除集合所有索引
db.集合名称.dropIndexes()

image.png

4、删除集合指定索引
db.集合名称.dropIndex("索引名称")

复合索引

说明: 一个索引的值是由多个 key 进行维护的索引的称之为复合索引
db.集合名称.createIndex({"title":1,"description":-1})

注意: mongoDB 中复合索引和传统关系型数据库一致都是左前缀原则

image.png

image.png

聚合 aggregate

说明

MongoDB 中聚合 (aggregate) 主要用于处理数据 (诸如统计平均值,求和等),并返回计算后的数据结果。有点类似 SQL 语句中的 count(*)

使用

{
   title: 'MongoDB Overview', 
   description: 'MongoDB is no sql database',
   by_user: 'runoob.com',
   url: 'http://www.runoob.com',
   tags: ['mongodb', 'database', 'NoSQL'],
   likes: 100
},
{
   title: 'NoSQL Overview', 
   description: 'No sql database is very fast',
   by_user: 'runoob.com',
   url: 'http://www.runoob.com',
   tags: ['mongodb', 'database', 'NoSQL'],
   likes: 10
},
{
   title: 'Neo4j Overview', 
   description: 'Neo4j is no sql database',
   by_user: 'Neo4j',
   url: 'http://www.neo4j.com',
   tags: ['neo4j', 'database', 'NoSQL'],
   likes: 750
}

image.png

现在我们通过以上集合计算每个作者所写的文章数,使用aggregate()计算结果如下:
db.集合名称.aggregate([{$group : {_id : "$by_user", num_tutorial : {$sum : 1}}}])

常见聚合表达式

image.png

$sum image.png

image.png

Mongodb连接navicat

传统启动方式

./mongod --help
image.png

image.png

./mongod --port=27017 --dbpath=../data --logpath=../logs/mongo.log --bind_ip=0.0.0.0 image.png

注意:如果连接失败检查下防火墙

docker启动方式

docker run -d --name mongo -p 27017:27017 mongo:5.0.5 --bind_ip=0.0.0.0

进入 mongo 容器: docker exec -it 95126bf045bd bash

整合应用

说明: 这里主要以 springboot 应用为基础应用进行整合开发。
Spring Data : Spring 数据框架 JPA 、Redis、Elasticsearch、AMQP、MongoDB

JdbcTemplate
RedisTemplate
ElasticTempalte
AmqpTemplate
MongoTemplate
SpringBoot Spring Data MongoDB

环境搭建

引入依赖

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

编写配置

# mongodb 没有开启任何安全协议
# mongodb(协议)://121.5.167.13(主机):27017(端口)/wkk(库名)
spring.data.mongodb.uri=mongodb://192.168.175.129:27017/wkk

# mongodb 存在密码
#spring.data.mongodb.host=192.168.175.129
#spring.data.mongodb.port=27017
#spring.data.mongodb.database=wkk
#spring.data.mongodb.username=root
#spring.data.mongodb.password=root

集合操作

  • 创建集合
@Test
public void testCreateCollection(){
  mongoTemplate.createCollection("users");//参数: 创建集合名称
}

注意:创建集合不能存在,存在报错

  • 删除集合
@Test
public void testDeleteCollection(){
  mongoTemplate.dropCollection("users");
}

相关注解

@Document

  • 修饰范围: 用在类上
  • 作用: 用来映射这个类的一个对象为 mongo 中一条文档数据
  • 属性:(value 、collection ) 用来指定操作的集合名称

image.png

@Id

  • 修饰范围: 用在成员变量、方法上
  • 作用: 用来将成员变量的值映射为文档的_id 的值

@Field

  • 修饰范围: 用在成员变量、方法上
  • 作用: 用来将成员变量以及值映射为文档中一个key、value对
  • 属性: ( name,value)用来指定在文档中 key 的名称,默认为成员变量名

@Transient

  • 修饰范围: 用在成员变量、方法上
  • 作用 : 用来指定改成员变量,不参与文档的序列化

文档操作

查询

  1. Criteria
    image.png

  2. 常见查询

@Test
public void testQuery(){
  //基于 id 查询
  template.findById("1",User.class);

  //查询所有
  template.findAll(User.class);
  template.find(new Query(),User.class);

  //等值查询
  template.find(Query.query(Criteria.where("name").is("编程不良人")), 
               User.class);

  // > gt  < lt  >= gte  <= lte
  template.find(Query.query(Criteria.where("age").lt(25)),
                User.class);
  template.find(Query.query(Criteria.where("age").gt(25)),
                User.class);
  template.find(Query.query(Criteria.where("age").lte(25)),
                User.class);
  template.find(Query.query(Criteria.where("age").gte(25)),
                User.class);

  //and
  template.find(Query.query(Criteria.where("name").is("编程不良人")
                            .and("age").is(23)),User.class);



  //or
  Criteria criteria = new Criteria()
    .orOperator(Criteria.where("name").is("编程不良人_1"),
     Criteria.where("name").is("编程不良人_2"));
  template.find(Query.query(criteria), User.class);

  //and or
  Criteria criteria1 = new Criteria()
    .and("age").is(23)
    .orOperator(
    Criteria.where("name").is("编程不良人_1"),
    Criteria.where("name").is("编程不良人_2"));
  template.find(Query.query(criteria1), User.class);

  //sort 排序
  Query query = new Query();
  query.with(Sort.by(Sort.Order.desc("age")));//desc 降序  asc 升序
  template.find(query, User.class);


  //skip limit 分页
  Query queryPage = new Query();
  queryPage.with(Sort.by(Sort.Order.desc("age")))//desc 降序  asc 升序
    .skip(0) //起始条数
    .limit(4); //每页显示记录数
  template.find(queryPage, User.class);


  //count 总条数
  template.count(new Query(), User.class);

  //distinct 去重
  //参数 1:查询条件 参数 2: 去重字段  参数 3: 操作集合  参数 4: 返回类型
  template.findDistinct(new Query(), "name", 
                        User.class, String.class);
  
  //使用 json 字符串方式查询 
        Query query = new BasicQuery(
          "{$or:[{name:'编程不良人'},{name:'徐凤年'}]}", 
          "{name:0}");

  template.find(query, User.class);
}

添加

@Test
public void testSaveOrUpdate(){
  User user = new User();
  user.setId("1");
  user.setAge(23);
  user.setName("编程不良人_1");
  user.setBir(new Date());
  User userDB = mongoTemplate.insert(user);//返回保存的对象 insert or save
  System.out.println(userDB);
}
  • insert: 插入重复数据时:insertDuplicateKeyException提示主键重复;save对已存在的数据进行更新。
  • save: 批处理操作时:insert可以一次性插入整个数据,效率较高;save需遍历整个数据,一次插入或更新,效率较低。

更新

@Test
public void  testUpdate(){
  //1.更新条件
  Query query = Query.query(Criteria.where("age").is(23));
  //2.更新内容
  Update update = new Update();
  update.set("name","编程小陈陈");

  //单条更新
  mongoTemplate.updateFirst(query, update, User.class);
  //多条更新
  mongoTemplate.updateMulti(query, update, User.class);
  //更新插入
  mongoTemplate.upsert(query,update,User.class);

  //返回值均为 updateResult
  //System.out.println("匹配条数:" + updateResult.getMatchedCount());
  //System.out.println("修改条数:" + updateResult.getModifiedCount());
  //System.out.println("插入id_:" + updateResult.getUpsertedId());
}

删除

@Test
public void testDelete(){
  //删除所有
  mongoTemplate.remove(new Query(),User.class);
  //条件删除
  mongoTemplate.remove(
    Query.query(Criteria.where("name").is("编程不良人")),
    User.class
  );
}

副本集 Replica Set

说明

https://docs.mongodb.com/manual/replication/

MongoDB 副本集(Replica Set)是有自动故障恢复功能的主从集群,有一个Primary节点和一个或多个Secondary节点组成。副本集没有固定的主节点, 当主节点发生故障时整个集群会选举一个主节点为系统提供服务以保证系统的高可用。

image.png

Automatic Failover

自动故障转移机制: 当主节点未与集合的其他成员通信超过配置的选举超时时间(默认为 10 秒)时,合格的辅助节点将调用选举以将自己提名为新的主节点。集群尝试完成新主节点的选举并恢复正常操作。

image.png

搭建副本集

  1. 创建数据目录

在安装目录中创建

  • mkdir -p ../repl/data1
  • mkdir -p ../repl/data2
  • mkdir -p ../repl/data3
  1. 搭建副本集

$ mongod --port 27017 --dbpath ../repl/data1 --bind_ip 0.0.0.0 --replSet myreplace/[121.5.167.13:27018,121.5.167.13:27019] image.png

$ mongod --port 27018 --dbpath ../repl/data2 --bind_ip 0.0.0.0 --replSet myreplace/[121.5.167.13:27019,121.5.167.13:27017] image.png

$ mongod --port 27019 --dbpath ../repl/data3 --bind_ip 0.0.0.0 --replSet myreplace/[121.5.167.13:27017,121.5.167.13:27018] image.png

注意: --replSet 副本集   myreplace 副本集名称/集群中其他节点的主机和端口

docker run -d --name mongo -p 27017:27017 mongo:5.0.5 --bind_ip=0.0.0.0

  1. 配置副本集,连接任意节点
  • use admin image.png
  • 初始化副本集
> var config = { 
		_id:"myreplace", 
		members:[
		{_id:0,host:"192.168.175.129:27017"},
		{_id:1,host:"192.168.175.129:27018"},
		{_id:2,host:"192.168.175.129:27019"}]
}
> rs.initiate(config);//初始化配置 

image.png

image.png

分页查询 db.users.find().skip(0).limit(100);
image.png

访问27018副本
报错,默认从节点没有读的权限
./mongo --port 27018 image.png

访问27019副本
报错,默认从节点没有读的权限
./mongo --port 27019 image.png

image.png

  • 设置客户端临时可以访问 (让从节点有读的权限)

rs.slaveOk();
rs.secondaryOk();

image.png

用客户端搭建副本集群

image.png

分片集群 Sharding Cluster

说明

https://docs.mongodb.com/manual/sharding/

副本集:自动故障转移  主从复制  集群
解决问题: 1. 数据冗余备份  2. 架构高可用
不能解决: 1. 单节点压力问题(硬件限制 并发访问压力)

分片(sharding)是指将数据拆分,将其分散存在不同机器的过程,有时也用分区(partitioning)来表示这个概念, 将数据分散在不同的机器上,不需要功能强大的大型计算机就能存储更多的数据,处理更大的负载。

分片目的是通过分片能够增加更多机器来应对不断的增加负载和数据,还不影响应用运行。

MongoDB支持自动分片,可以摆脱手动分片的管理困扰,集群自动切分数据做负载均衡。MongoDB分片的基本思想就是将集合拆分成多个块,这些快分散在若干个片里,每个片只负责总数据的一部分,应用程序不必知道哪些片对应哪些数据,甚至不需要知道数据拆分了,所以在分片之前会运行一个路由进程,mongos进程,这个路由器知道所有的数据存放位置,应用只需要直接与mongos交互即可。mongos自动将请求转到相应的片上获取数据,从应用角度看分不分片没有什么区别。

架构

image.png

  • Shard: 用于存储实际的数据块,实际生产环境中一个shard server角色可由几台机器组个一个replica set承担,防止主机单点故障
  • Config Server: mongod实例,存储了整个 ClusterMetadata。
  • Query Routers: 前端路由,客户端由此接入,且让整个集群看上去像单一数据库,前端应用可以透明使用,前端路由不存储任何数据
  • Shard Key: 片键,设置分片时需要在集合中选一个键, 用该键的值作为拆分数据的依据, 这个片键称之为(shard key),片键的选取很重要, 片键的选取决定了数据散列是否均匀。

image.png

搭建

集群规划

  • Shard Server 1:27017
  • Shard Repl  1:27018
  • Shard Server 2:27019
  • Shard Repl  2:27020
  • Shard Server 3:27021
  • Shard Repl  3:27022
  • Config Server :27023
  • Config Server :27024
  • Config Server :27025
  • Route Process :27026

进入安装的 bin 目录创建数据目录

  • mkdir -p ../cluster/shard/s0
  • mkdir -p ../cluster/shard/s0-repl
  • mkdir -p ../cluster/shard/s1
  • mkdir -p ../cluster/shard/s1-repl
  • mkdir -p ../cluster/shard/s2
  • mkdir -p ../cluster/shard/s2-repl
  • mkdir -p ../cluster/shard/config1
  • mkdir -p ../cluster/shard/config2
  • mkdir -p ../cluster/shard/config3

image.png

启动3个 shard服务

  • 启动 s0、r0
    shardsvr: shard代表分片(分片就是只存储集合的某一部分数据),svr代表server简写

./mongod --port 27017 --dbpath ../cluster/shard/s0 --bind_ip 0.0.0.0 --shardsvr --replSet r0/192.168.175.129:27018
副本:./mongod --port 27018 --dbpath ../cluster/shard/s0-repl --bind_ip 0.0.0.0 --shardsvr --replSet r0/192.168.175.129:27017
image.png -- 1.登录任意节点 (副本集初始化)
./mongo --port 27017
-- 2. use admin
-- 3. 执行

config = { _id:"r0", members:[
      {_id:0,host:"192.168.175.129:27017"},
      {_id:1,host:"192.168.175.129:27018"},
    	]
    }
    //没初始化前,配置是可以改的
   rs.initiate(config);//初始化

image.png

  • 启动 s1、r1
    shardsvr: shard代表分片(分片就是只存储集合的某一部分数据),svr代表server简写

./mongod --port 27019 --dbpath ../cluster/shard/s1 --bind_ip 0.0.0.0 --shardsvr --replSet r1/192.168.175.129:27020
副本:./mongod --port 27020 --dbpath ../cluster/shard/s1-repl --bind_ip 0.0.0.0 --shardsvr --replSet r1/192.168.175.129:27019
-- 1.登录任意节点
./mongo --port 27019
-- 2. use admin
-- 3. 执行

	config = { _id:"r1", members:[
      {_id:0,host:"192.168.175.129:27019"},
      {_id:1,host:"192.168.175.129:27020"},
    	]
    }
rs.initiate(config);//初始化

image.png

  • 启动 s2、r2
    shardsvr: shard代表分片(分片就是只存储集合的某一部分数据),svr代表server简写

./mongod --port 27021 --dbpath ../cluster/shard/s2 --bind_ip 0.0.0.0 --shardsvr --replSet r2/192.168.175.129:27022
副本:./mongod --port 27022 --dbpath ../cluster/shard/s2-repl --bind_ip 0.0.0.0 --shardsvr --replSet r2/192.168.175.129:27021
-- 1.登录任意节点
./mongo --port 27021
-- 2. use admin
-- 3. 执行

	config = { _id:"r2", members:[
      {_id:0,host:"192.168.175.129:27021"},
      {_id:1,host:"192.168.175.129:27022"},
    	]
    }
		rs.initiate(config);//初始化

image.png

启动3个 config服务

./mongod --port 27023 --dbpath ../cluster/shard/config1 --bind_ip 0.0.0.0 --replSet config/[192.168.175.129:27024,192.168.175.129:27025] --configsvr

./mongod --port 27024 --dbpath ../cluster/shard/config2 --bind_ip 0.0.0.0 --replSet config/[192.168.175.129:27023,192.168.175.129:27025] --configsvr

./mongod --port 27025 --dbpath ../cluster/shard/config3 --bind_ip 0.0.0.0 --replSet config/[192.168.175.129:27023,192.168.175.129:27024] --configsvr

初始化 config server 副本集

1.登录任意节点 congfig server
./mongo --port 27023
2.use admin
3.在admin中执行

 config = { 
      _id:"config", 
      configsvr: true,
      members:[
          {_id:0,host:"192.168.175.129:27023"},
          {_id:1,host:"192.168.175.129:27024"},
          {_id:2,host:"192.168.175.129:27025"}
        ]
  }
> rs.initiate(config); //初始化副本集配置 

image.png

启动 mongos 路由服务

./mongos --port 27026 --configdb config/192.168.175.129:27023,192.168.175.129:27024,192.168.175.129:27025 --bind_ip 0.0.0.0

image.png

登录 mongos 服务

1.登录 mongo --port 27026
2.use admin
3.添加分片信息

  • db.runCommand({ addshard:"r0/192.168.175.129:27017,192.168.175.129:27018", "allowLocal":true });
  • db.runCommand({ addshard:"r1/192.168.175.129:27019,192.168.175.129:27020", "allowLocal":true });
  • db.runCommand({ addshard:"r2/192.168.175.129:27021,192.168.175.129:27022", "allowLocal":true });

image.png

指定分片的数据库

db.runCommand({ enablesharding:"wkk" });

image.png
只有启动库的权限,mongos才会把这个库的集合切分成很多数据块

image.png

设置库的片键信息

光指定库不够,还需要指定库中的集合采用哪种分片

以下代表启用_id作为片键

db.runCommand({ shardcollection: "wkk.users", key: { _id:1}});
db.runCommand({ shardcollection: "wkk.emps", key: { _id: "hashed"}});

image.png

片键太有规律了,都分到r2上了
image.png

r0没有数据
image.png

r1没有数据
image.png

使用哈希散列更均匀
db.runCommand({ shardcollection: "wkk.emps", key: { _id: "hashed"}});

r0有emps库
image.png

r1有emps库
image.png

r2有emps库
image.png

给emps集合添加数据
image.png