mongo整理

一、mongo集群

1.简介

MongoDB 有三种集群部署模式,分别为主从复制(Master-Slaver)、副本集(Replica Set)和分片(Sharding)模式。

  • Master-Slaver 是一种主从副本的模式,目前已经不推荐使用。

  • Replica Set 模式取代了 Master-Slaver 模式,是一种互为主从的关系。Replica Set 将数据复制多份保存,不同服务器保存同一份数据,在出现故障时自动切换,实现故障转移,在实际生产中非常实用。

  • Sharding 模式适合处理大量数据,它将数据分开存储,不同服务器保存不同的数据,所有服务器数据的总和即为整个数据集。

2.使用场景

1.适合json存储格式的非关系数据,例如游戏场景中用户装备,积分;物流场景的订单信息(在运送状态下不常更新,适合用内嵌文档形式查询所有订单信息);物联网场景中采集信息等;数据传输过程中由于失败需要暂存的中间存储介质等;特点是数据灵活,数据量大,不需要经常变动的数据。

2.mongo的优势:高性能读写,数据在磁盘,热数据和索引会加载到内存中。拓展方便,读写分离等

3.劣势:不支持事务,需要内存较多,资料和生态相比不够完善,学习成本较大。

3.主从复制

主从复制是 MongoDB 中最简单的数据库同步备份的集群技术,其基本的设置方式是建立一个主节点(Primary)和一个或多个从节点(Secondary),如下图所示。

 

这种方式比单节点的可用性好很多,可用于备份、故障恢复、读扩展等。集群中的主从节点均运行 MongoDB 实例,完成数据的存储、查询与修改操作。

主从复制模式的集群中只能有一个主节点,主节点提供所有的增、删、查、改服务,从节点不提供任何服务,但是可以通过设置使从节点提供查询服务,这样可以减少主节点的压力。

另外,每个从节点要知道主节点的地址,主节点记录在其上的所有操作,从节点定期轮询主节点获取这些操作,然后对自己的数据副本执行这些操作,从而保证从节点的数据与主节点一致。

在主从复制的集群中,当主节点出现故障时,只能人工介入,指定新的主节点,从节点不会自动升级为主节点。同时,在这段时间内,该集群架构只能处于只读状态。

总结:主从模式只能作数据备份容灾,不能做高可用读写分离,选举(需要人工参与)等。

4.副本集

副本集的集群架构如下图所示。

 

此集群拥有一个主节点和多个从节点,这一点与主从复制模式类似,且主从节点所负责的工作也类似,但是副本集与主从复制的区别在于:当集群中主节点发生故障时,副本集可以自动投票,选举出新的主节点,并引导其余的从节点连接新的主节点,而且这个过程对应用是透明的。

可以说,MongoDB 的副本集是自带故障转移功能的主从复制。

MongoDB 副本集使用的是 N 个 mongod 节点构建的具备自动容错功能、自动恢复功能的高可用方案。在副本集中,任何节点都可作为主节点,但为了维持数据一致性,只能有一个主节点。

主节点负责数据的写入和更新,并在更新数据的同时,将操作信息写入名为 oplog 的日志文件当中。主节点还负责指定其他节点为从节点,并设置从节点数据的可读性,从而让从节点来分担集群读取数据的压力。

另外,从节点会定时轮询读取 oplog 日志,根据日志内容同步更新自身的数据,保持与主节点一致。

在一些场景中,用户还可以使用副本集来扩展读性能,客户端有能力发送读写操作给不同的服务器,也可以在不同的数据中心获取不同的副本来扩展分布式应用的能力。

在副本集中还有一个额外的仲裁节点(不需要使用专用的硬件设备),负责在主节点发生故障时,参与选举新节点作为主节点。

副本集中的各节点会通过心跳信息来检测各自的健康状况,当主节点出现故障时,多个从节点会触发一次新的选举操作,并选举其中一个作为新的主节点。为了保证选举票数不同,副本集的节点数保持为奇数(补充:有竞选资格的节点数/2 + 1,有效防脑裂)

总结:副本集可以实现读写分离,备份容灾,自动节点选举等,不能实现分布式海量数据存储和查询

5.分片

副本集可以解决主节点发生故障导致数据丢失或不可用的问题,但遇到需要存储海量数据的情况时,副本集机制就束手无策了。副本集中的一台机器可能不足以存储数据,或者说集群不足以提供可接受的读写吞吐量。这就需要用到 MongoDB 的分片(Sharding)技术,这也是 MongoDB 的另外一种集群部署模式。

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

MongoDB 支持自动分片,可以使数据库架构对应用程序不可见,简化系统管理。对应用程序而言,就如同始终在使用一个单机的 MongoDB 服务器一样。

MongoDB 的分片机制允许创建一个包含许多台机器的集群,将数据子集分散在集群中,每个分片维护着一个数据集合的子集。与副本集相比,使用集群架构可以使应用程序具有更强大的数据处理能力。

MongoDB 分片的集群模式如下图所示。

 

 

构建一个 MongoDB 的分片集群,需要三个重要的组件,分别是分片服务器(Shard Server)、配置服务器(Config Server)和路由服务器(Route Server)。

Shard Server

每个 Shard Server 都是一个 mongod 数据库实例,用于存储实际的数据块。整个数据库集合分成多个块存储在不同的 Shard Server 中。

在实际生产中,一个 Shard Server 可由几台机器组成一个副本集来承担,防止因主节点单点故障导致整个系统崩溃。

Config Server

这是独立的一个 mongod 进程,保存集群和分片的元数据,在集群启动最开始时建立,保存各个分片包含数据的信息。

Route Server

这是独立的一个 mongos 进程,Route Server 在集群中可作为路由使用,客户端由此接入,让整个集群看起来像是一个单一的数据库,提供客户端应用程序和分片集群之间的接口。

Route Server 本身不保存数据,启动时从 Config Server 加载集群信息到缓存中,并将客户端的请求路由给每个 Shard Server,在各 Shard Server 返回结果后进行聚合并返回客户端。

以上介绍了 MongoDB 的三种集群模式,副本集已经替代了主从复制,通过备份保证集群的可靠性,分片机制为集群提供了可扩展性,以满足海量数据的存储和分析的需求。

在实际生产环境中,副本集和分片是结合起来使用的,可满足实际应用场景中高可用性和高可扩展性的需求。

总结:可以实现海量数据操作

6.读策略

mongo的读策略在客户端实现,具体有客户端的driver实现,以下为几种策略模式;

primary模式:默认模式,所有的读操作都由复制集的主节点处理;

primaryPreferred模式:一般情况下,所有的读操作由主节点处理,当主节点不可用的时候,读操作由备份节点处理;

secondary模式:所有的读操作由复制集的备份节点处理;

secondaryPreferred模式:一般情况下,所有的读操作由备份节点处理,当所有的备份阶段宕机后,读操作由主节点处理;

nearest模式:选择复制集中的读延迟最少的节点处理读操作,主节点以及备份节点都有可能处理读操作;

应用场景:

primary模式拥有最好的数据一致性,能保证所有的读操作均获取最新的数据,这种模式下主节点负载较大,在高并发场景中性能会有影响。

primaryPreferred模块拥有最好的可用性,主节点处理读请求时也能保证数据数据一致性;

nearest模式拥有最小的延迟,会从复制集中选择最低网络延迟的节点处理读请求,这种方式不能保证数据一致性,也不能保证减少IO以及CPU的负载;

secondary模式主要用于减少主节点的负载,但是不能保证数据一致性,读操作获取的数据可能是旧数据(备份节点与主节点存在数据同步延迟。oplog的方式进行同步,类似于mysql的binlog),但是能降低主节点的复杂,集群整体的读写速度;适合于读密集的应用;

为什么不用secondaryPreferred模式?

 从模式的字面意思看,secondaryPreferred模式比secondary模式更能保证服务的可用性,但是为什么不选选择secondaryPreferred?原因是在某些场景下,所有的备份节点都宕机后,所有的读请求也将转移到主节点,假如主节点不能同时处理这些读请求,那么读请求将会和写请求竞争资源,导致主节点负载加大,影响读写性能,严重情况会造成主节点宕机。

对于读密集型应用,线上使用复制集集群方式,能保证服务的可用性,由于数据的及时性要求不是特别高(前台页面展示内容),能够接受短时间内的数据延迟,因此选择 secondary 模式是非常合适的。

默认情况:

 

通过设置读偏好可以实现

 

二、springboot集成

1.pom.xml

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
   <groupId>org.mongodb</groupId>
   <artifactId>mongo-java-driver</artifactId>
   <version>3.12.10</version>
</dependency>

2.application.yml

方式1:

spring:
  data:
    mongodb:
      uri: mongodb://uat_pw_ec_favorite:FzqXZiucLd@10.115.90.155:27020,10.115.90.156:27020,10.115.90.183:27020/ec_favorite_uat?readPreference=primary(参考读策略)

方式2:

xxxx:
  database: ec_favorite_uat
  hosts:
    - 10.115.90.156
    - 10.115.90.183
    - 10.115.90.155
  ports:
    - 27020
    - 27020
    - 27020
  username: uat_pw_ec_favorite
  password: FzqXZiucLd
  authenticationDatabase: ec_favorite_uat
  maxConnections: 100
  minConnections: 20
  maxQueue: 10
  replicaSet: uat_replset27020

3.mongotemplate

方式1:直接注入使用

方式2:

配置类:

package com.gome.stage.collection.config.mongoConfigs;
​
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
​
import java.util.List;
​
/**
 * mongo连接参数配置类
 * @author liangkunjie
 * @version 1.0
 * @date 2021/12/17 13:47
 */
@data
@Component
@ConfigurationProperties(prefix = "xxxx")//和application.yml对应
public class MongoDBProperties{
    private String database;
​
    private List<String> hosts;
​
    private List<Integer> ports;
​
    private String replicaSet;
    private String username;
    private String password;
    private String authenticationDatabase;
    private Integer minConnections;
    private Integer maxConnections;
    private Integer maxQueue;
 
}

 mongo配置:

package com.gome.stage.collection.config.mongoConfigs;
​
import com.mongodb.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDbFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.SimpleMongoDbFactory;
​
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
​
/**
 * mongotemplate配置类
 * @author liangkunjie
 * @version 1.0
 * @date 2021/12/17 13:49
 */
@Configuration
public class MongoConfig {
    private static final Logger log = LoggerFactory.getLogger(MongoConfig.class);
​
    private final
    MongoDBProperties mongoSettingsProperties;
​
    public MongoConfig(MongoDBProperties mongoSettingsProperties) {
        this.mongoSettingsProperties = mongoSettingsProperties;
    }
​
​
    @Bean
    MongoDbFactory mongoDbFactory() {
        //客户端配置(连接数、副本集群验证)
        MongoClientOptions.Builder builder = new MongoClientOptions.Builder();
        builder.connectionsPerHost(mongoSettingsProperties.getMaxConnections());
        builder.minConnectionsPerHost(mongoSettingsProperties.getMinConnections());
        builder.threadsAllowedToBlockForConnectionMultiplier(mongoSettingsProperties.getMaxQueue());
        builder.readPreference(ReadPreference.secondaryPreferred());//参考读策略
        if (mongoSettingsProperties.getReplicaSet() != null) {
            builder.requiredReplicaSetName(mongoSettingsProperties.getReplicaSet());
        }
        MongoClientOptions mongoClientOptions = builder.build();
​
        // MongoDB地址列表
        List<ServerAddress> serverAddresses = new ArrayList<>();
        for (String host : mongoSettingsProperties.getHosts()) {
            int index = mongoSettingsProperties.getHosts().indexOf(host);
            Integer port = mongoSettingsProperties.getPorts().get(index);
​
            ServerAddress serverAddress = new ServerAddress(host, port);
            serverAddresses.add(serverAddress);
        }
        log.info("MongoDB Server Addresses:[{}]", serverAddresses.toString());
        MongoCredential mongoCredentia = null;
        // 连接认证
        if (mongoSettingsProperties.getUsername() != null) {
            mongoCredentia = MongoCredential.createScramSha1Credential(
                    mongoSettingsProperties.getUsername(),
                    mongoSettingsProperties.getAuthenticationDatabase() != null ? mongoSettingsProperties.getAuthenticationDatabase() : mongoSettingsProperties.getDatabase(),
                    mongoSettingsProperties.getPassword().toCharArray());
        }
        if (Objects.isNull(mongoCredentia)) {
            throw new RuntimeException("mongoCredentia is null");
        }
        log.info("mongoDB CredentialList:[{}]", mongoCredentia.toString());
​
        //创建客户端和Factory
        MongoClient mongoClient = new MongoClient(serverAddresses, mongoCredentia, mongoClientOptions);
        return new SimpleMongoDbFactory(mongoClient, mongoSettingsProperties.getDatabase());
    }
​
    @Bean(name = "mongoTemplate")
    public MongoTemplate mongoTemplate(MongoDbFactory mongoDbFactory) {
        MongoTemplate mongoTemplate = new MongoTemplate(mongoDbFactory);
        return mongoTemplate;
​
    }
}
package com.gome.stage.collection.config.mongoConfigs;
​
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.convert.DefaultMongoTypeMapper;
import org.springframework.data.mongodb.core.convert.MappingMongoConverter;
import org.springframework.data.mongodb.core.convert.MongoConverter;
/**
 * mongo保存数据时默认会有_class字段,本类作用是去掉该字段,实际场景本类可加可不加 
 * @author liangkunjie
 * @version 1.0
 * @date 2021/12/17 13:51
 */
@Configuration
public class MongoClassConfig implements ApplicationListener<ContextRefreshedEvent> {
​
​
    private final
    MongoTemplate mongoTemplate;
​
    public MongoClassConfig(MongoTemplate mongoTemplate) {
        this.mongoTemplate = mongoTemplate;
    }
​
    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
​
        MongoConverter mongoConverter = mongoTemplate.getConverter();
​
        if (mongoConverter.getTypeMapper().isTypeKey("_class")) {
            ((MappingMongoConverter) mongoConverter).setTypeMapper(new DefaultMongoTypeMapper(null));
        }
    }
}

方式2和方式1的区别是可以自定义一些参数

三、常用查询

1.mongotemplate

方法具体解释
getCollectionNamesSet<String> getCollectionNames()获取所有集合名(表名)
getCollectionMongoCollection<Document> getCollection(String collectionName)通过表名获取信息
collectionExists<T> boolean collectionExists(Class<T> entry)判断表是否存在通过class
collectionExistsboolean collectionExists(String collectionName)判断表是否存在通过表名
dropCollection<T> void dropCollection(Class<T> entry)删除表通过class
dropCollectionvoid dropCollection(String collectionName)删除表通过表名
indexOpsIndexOperations indexOps(String collectionName)获取索引信息通过表名
indexOpsIndexOperations indexOps(Class<?> entry)获取索引信息通过class
scriptOpsScriptOperations scriptOps()通过直接发送脚本或调用存储的脚本在服务器上执行 JavaScript 函数
bulkOpsBulkOperations bulkOps(BulkMode var1, String collectionName)批量操作通过表名
bulkOpsBulkOperations bulkOps(BulkMode var1, Class<?> entry)批量操作通过class
bulkOpsBulkOperations bulkOps(BulkMode var1, @Nullable Class<?> entry, String collectionName)批量操作通过class或表名
findAll<T> List<T> findAll(Class<T> entry)查所有数据通过class
findAll<T> List<T> findAll(Class<T> entry, String collectionName)查所有数据通过class和表名
find<T> T findOne(Query query, Class<T> entry)通过条件查询数据
find<T> T findOne(Query query, Class<T> entry, String collectionName)通过条件查询数据
findOne<T> T findOne(Query query, Class<T> entry)通过条件查询数据
findOne<T> T findOne(Query query, Class<T> entry, String collectionName)通过条件查询数据
findAndModify<T> T findAndModify(Query var1, Update var2, Class<T> var3)通过条件查询数据并修改(原子性操作)
....
.....
existsboolean exists(Query query, String collectionName)通过条件查询数据是否存在
existsboolean exists(Query query, Class<?> entry)通过条件查询数据是否存在
existsboolean exists(Query query, @Nullable Class<?> entry, String collectionName)通过条件查询数据是否存在
countlong count(Query query, Class<?> var2)通过条件查询数据条数
countlong count(Query query, String collectionName)通过条件查询数据条数
countlong count(Query query, @Nullable Class<?> entry, String collectionName)通过条件查询数据条数
group不常用,后续补充分组
group不常用,后续补充分组
aggregate不常用,后续补充聚合函数/管道
aggregate不常用,后续补充聚合函数/管道
aggregate不常用,后续补充聚合函数/管道
aggregate不常用,后续补充聚合函数/管道
aggregateStream不常用,后续补充聚合函数/管道Stream流
aggregateStream不常用,后续补充聚合函数/管道Stream流
aggregateStream不常用,后续补充聚合函数/管道Stream流
aggregateStream不常用,后续补充聚合函数/管道Stream流
mapReduce不常用,后续补充分治
mapReduce不常用,后续补充分治
mapReduce不常用,后续补充分治
mapReduce不常用,后续补充分治
geoNear不常用,后续补充获取坐标
geoNear不常用,后续补充获取坐标

常用用法:

find

Query query = new Query();
Criteria criteria = where("字段").is(值);
query.addCriteria(criteria);
List<Object> list = mongoTemplate.find(query, Object.class);

count

Query query = new Query();
Criteria criteria = where("字段").is(值);
query.addCriteria(criteria);
List<Object> list = mongoTemplate.count(query, Object.class);

bulkops

批量插入

List<Object> insertDataList;
BulkOperations operations = mongoTemplate.bulkOps(BulkOperations.BulkMode.UNORDERED, collectionName);
operations.insert(insertDataList);
BulkWriteResult result = operations.execute();

批量修改

List<Object> updateDataList;
BulkOperations operations = mongoTemplate.bulkOps(BulkOperations.BulkMode.UNORDERED, collectionName);
updateDateList.forEach(date -> {
   Query queryUpdate = new Query();
   queryUpdate.addCriteria(where("_id").is(value));
   Update update = new Update();
   update.set(field1, value1).set(field2, value2);
   operations.updateOne(queryUpdate, update);
});
BulkWriteResult result = operations.execute();

2.query(分页和高级查询条件)

Query
Query
Query
query
addCriteria
fields
skip
limit
withHint
withHint
with
with
getRestrictedTypes
restrict
getQueryObject
getFieldsObject
getSortObject
isSorted
getSkip
getLimit
getHint
maxTimeMsec
maxTime
maxTime
maxScan
comment
useSnapshot
cursorBatchSize
noCursorTimeout
exhaust
slaveOk
partialResults
getMeta
setMeta
collation
getCollation
getCriteria
of
toString
equals
querySettingsEquals
hashCode
isRestrictedTypeKey
RESTRICTED_TYPES_KEY
restrictedTypes
criteria
fieldSpec
sort
skip
limit
hint
meta
collation

着重说下withhint和sort,项目中用到

hint表示指定索引,如不指定可能走不到最优索引(最优索引设计参考四、索引

spring-data-mongodb:1.10.14版本中用法为

Query query=new Query();
query.withHint("index1_1_index2_-1");

spring-data-mongodb:2.2.4版本中用法为

Query query=new Query();
Document doc=new Document();
doc.append("index1",1.0);
doc.append("index2",-1.0);
query.withHint(doc);

sort表示排序

spring-data-mongodb:1.10.14版本中用法为

Query query=new Query();
query.with(new Sort(Direction.DESC, "filed"));
spring-data-mongodb:2.2.4版本中用法为

Query query=new Query();
query.with(Sort.by(Direction.DESC, "filed"));
 

3.Criteria条件

Criteria
Criteria
Criteria
where
byExample
byExample
matchingDocumentStructure
and
is
lastOperatorWasNot
ne
lt
lte
gt
gte
in
in
nin
nin
mod
all
all
size
exists
type
type
not
not
regex
regex
regex
regex
toPattern
withinSphere
within
near
nearSphere
intersects
maxDistance
minDistance
elemMatch
alike
andDocumentStructureMatches
bits
orOperator
norOperator
andOperator
registerCriteriaChainElement
getKey
getCriteriaObject
getSingleCriteriaObject
createCriteriaList
setValue
createNearCriteriaForCommand
equals
simpleCriteriaEquals
isEqual
hashCode
requiresGeoJsonFormat
regexFlags
regexFlag
NOT_SET
FLAG_LOOKUP
key
criteriaChain
criteria
isValue

四、索引

.

1.简单理解

索引数据会存储内存中,并且会以顺序存储,结构类似下面:

 

student中有name,age,score等字段,以age建立索引如下

[1]代表年龄的值,0x代表磁盘地址
[1]->0xA001
[2]->0xB001
[3]->0xC001
[4]->0xD001

2.组合索引

以student为例子结构如下,组合索引为age_1_core_1(1代表升序,-1代表逆序)

[1]代表年龄的值,0x代表磁盘地址
[1,90]->0xA001
[2,95]->0xB001
[3,80]->0xC001
[4,60]->0xD001

(1).如果查询条件为age>2,sort age ASC:mongo会直接找到>2的数据返回,该查询最优

(2).如果查询条件为age>2,sort score ASC:mongo会直接找到>2,并将数据放入内存中排序,需要消耗一部分内存;

(3).如果查询条件为score<90,sort age ASC:mongo会检索整个索引数据;

(4).如果查询条件为score<90,sort score ASC:mongo会检索整个索引数据,并将数据放入内存中排序;

总结:根据实际场景去设计索引

3.索引优化

(1)单个集合(表)不超过64个索引,索引长度不超过128字符,一个组合索引不超过32个字段

(2)集合中查询频率较高的字段,并且该字段分组后,每组数据量级不太大

4.常见问题

(1)排序问题:首先,应尽量避免在内存中排序,如果排序的数据超过了mongo默认排序内存33M会报错Sort operation used more than the maximum 33554432 bytes of RAM

    解决方案:

       1.排序字段没加索引加索引。

       2.上述某些查询由于索引规则导致没有命中排序儿需要在内存中排序的(最大100M),可以调大排序内存db.adminCommand({setParameter: 1, internalQueryExecMaxBlockingSortBytes: 104857600}) 或持久化到配置中

setParameter:
internalQueryExecMaxBlockingSortBytes:104857600

        3.如果超过100M的大排序,可以用临时文件,{allowDiskUse: true}

(2)磁盘满了会导致服务挂掉或者写入不进去问题,看具体场景

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值