​ 2)oplog读取的位置最新

​ 3)如果从节点设置了优先级,满足上述要求的优先级最高的节点

针对从节点,还有一些常见选项:

​ 投票权:从节点默认具有投票权,可以关闭,单纯作为复制节点,参数为v;

​ 优先级:优先级越高的节点在选举时同等条件下优先成为主节点,优先级为0的节点无法成为主节点,参数为;

​ 隐藏:单纯作为数据备份,无法被应用访问,隐藏节点的优先级必须为0,参数为;

​ 延迟:在复制数据时,复制指定延迟时间之前的数据,与主节点保持时间差,常用来作为数据备份与恢复,参数为27017端口,单位为秒。

# 动态修改从节点的信息
conf=rs.conf()
conf.members[2].secondaryDelaySecs = 5
conf.members[2].priority = 0
rs.reconfig(conf)

1.2、复制集的写策略

复制集的写策略是通过来进行配置的,他代表的是多少个节点确认完成才算是吸入成功。当数据写入主节点的时候,主节点会向从节点发送确认消息,如果从节点返回确认成功,才算一个从节点完成了确认,接下来主节点会再向从节点发送写入消息。如下图:

端口27017是什么_27017端口_端口27001

的配置方式为::{w:n},你的赋值规则如下:

​ 0:不关心写入结果,当应用发送写入命令以后,直接返回成功;

​ 1:当应用发送写入命令以后,只要写入主节点,则返回成功;

​ n:大于1小于总结点的一个数值,代表写入主节点以后,需要n-1个节点确认成功,才返回成功;

​ :需要保证大多数节点完成确认,即超过总结点数的一半,才返回写入成功,这种方式为推荐方式;

​ all:全部节点完成确认,才会返回成功;

在写操作这里还有一个特殊的日志文件日志文件,它类似于关系型数据库中的事务日志。它的作用为数据库由于故障宕机以后的快速恢复。设置参数为j,值为true时代表数据要写入日志文件才算成功;false代表写操作记录到内存即可。开启该配置以后,肯定会影响一部分写性能,但是它有利于数据库的快速恢复,建议生产环境进行开启。参考下图:

端口27017是什么_端口27001_27017端口

1.3、复制集的读策略

在复制集中只要节点不是隐藏节点或者仅投票节点,这些节点是都可以向外部提供读取权限的,那么我们在读取的时候,应该从哪个节点进行读取呢?这需要我们根据具体的业务情况进行策略配置,配置参数为。它有一下几种值可供选择:

​ :只从主节点进行读取,这是的默认值;

​ :优先从主节点读取,如果当前主节点不可用,则选择从节点读取;

​ :只选择从节点进行读取;

​ :优先选择从节点读取,如果从节点不可用,则从主节点读取;

​ :选择最近的节点,这里的最近指的是地理位置。

这些策略分别适用的场景为:/:对数据实时性要求较高的业务场景,因为从节点有一个数据复制的过程,会造成数据延迟;

/:适用查询历史数据的业务场景,其中也适用于报表生成这类需要耗费大量资源的业务场景;:适用于地域性较广的应用,进行就近读取,减少传输时间。

的配置方式有三种,如下:

#在配置文件中的mongdb连接串后添加对应参数mongodb://host1:27107,host2:27107/?replicaSet=rs&readPreference=secondary#在java代码中MongoCollection.withReadPreference(ReadPreference readPref)#在xshell控制台中db.collection.find().readPref( "secondary")   

上面的这种方式,只能指定一类节点,主从节点。但是在实际生产中可能需要根据硬件配置,业务场景来区分要读取哪些节点。这是我们可以通过给不同的节点打上不同的tag来区分,方式如下:

# 给mongodb打tag
conf = rs.conf()
conf.members[1].tags = {"type" : "a" }
rs.reconfig(conf)
#应用程序的mongodb配置文件
mongodb://host1:27107,host2:27107/?replicaSet=rs&type=a

上面介绍了我们要从哪个节点读取数据的方式。但是节点上的那些数据是可以读取的呢?这就需要用到另一个参数。这个参数的配置值类似于数据库中的隔离级别。它有一下的策略配置,他们的隔离级别由上到下越来越强:

​ :读取所有可用的数据,可以直接查询到写入复制集的数据;

​ local:读取所有可用且属于当前分片的数据,可以直接查询到该分片的数据;

​ :读取在大多数节点上提交的数据,使用该策略可以有效的避免脏读的问题。 采用mvcc机制来保证数据的读取。通过维护多个快照来链接不同的版本,每个被大多数节点确认过的版本都将是一个快照,快照持续到不再使用的时候才会进行删除。因为该策略是需要写入操作需要大多数节点完成确认,才能够被读取,当本次写入没有被大多数节点确认,主节点就发生回滚,那么本次写入对其它应用是不可见的;

​ :可线性化读取文档;

​ :读取最近快照中的数据,即每次读取都是从同一个快照中进行读取,那么也就避不会出现脏读,不可重复读以及幻读。下面看一个可重复的例子:

# 开启事务,并且将readConcern设置为快照
var session = db.getMongo().startSession();
session.startTransaction({readConcern: {level: "snapshot"},writeConcern: {w: "majority"}});
var coll = session.getDatabase('test').getCollection("test_1");、
# 读取id1的数据,获得的文档为id:1
coll.findOne({id: 1});
# 不通过事务对id1的数据进行更新 
db.tx.updateOne({id: 1}, {$set: {name: "b"}});
# 事务外读取id1的数据,获得的文档为id:1,name:b
db.tx.findOne({id: 1});
# 事务内再次读取id1的数据,获取的文档为id:1,它与第一次读时用的是同一个快照,所以两次读取是一样的
coll.findOne({id: 1}); 
session.abortTransaction();

2、分片集群

分片集群可以理解为是有多个复制集组成一个可以横向扩展的集群,每个复制集中存储不同范围的数据。分片集群主要是为了解决当下数据量日益增大,当一个节点存储大量数据,在进行数据恢复时需要的时间更大的情况以及解决全球化网站数据的存储以及访问问题。我们先来看一下分片集群的架构图,如下:

27017端口_端口27001_端口27017是什么

​ 路由节点:提供分片集群的统一入口,一个节点即可满足要求,但是为了高可用,还是部署两台,因为它只是起到一个路由转发的功能,不需要进行存储大量数据,使用低配的服务器即可。

​ 配置节点:配置节点的架构就是一个普通的复制集,提供集群元数据的存储,分片数据分布的映射。我们一般采用hash的方式作为分片依据,如果使用范围作为分片标准,会导致数据分布不均匀。

​ 数据节点:数据节点以复制集作为存储单位,每个数据节点的数据是不重复的。一个数据节点就是1片,最大能达到1024分片。当数据量越来越大时,我们可以通过添加数据节点的方式进行横向扩容。每个分片的大小最好不要超过3T,尽可能保证2T每片。

分片数量可以由一下三个维度来进行计算,然后取其最大值:

1)总存储量/单台服务器容量;

2)工作集大小/单台服务器硬盘 * 0.6;

3)并发总数/单服务器并发数 * 0.7

下面介绍一下分片集群中的一些概念:

​ shard key:片键,文档中的一个字段;

​ doc:文档,包含shard key的一行数据;

​ chunk:块,包含多个文档;

​ shard:分片,包含多个chunk;

​ :集群,包含多个;

分片集群的一个好处就是可以进行动态扩容,当我们进行动态扩容时,由于分片变多,原有数据的存储位置就需要发生变更,此时就会发生chunk的移动,这就是的自动均衡机制。

四、的数据备份【扩展】

的数据备份方式有两种:延迟节点备份以及全量备份+oplog。接下来我们做一个简单的介绍,一般这些工作是由运维完成。

1、 延迟节点备份

在上面的介绍中,我们知道从节点可以配置延迟同步时间,我们可以利用这一机制进行数据备份,能够让主节点恢复到指定时间之前的状态。这种情况适用于误操作时进行数据恢复。

端口27017是什么_端口27001_27017端口

此时我们可以利用节点2以及oplog,将主节点的数据恢复到安全范围内的任意一个节点的数据状态。

2、 全量备份+oplog

全量备份的方式有以下三种:

​ 复制文件:复制数据库文件,在复制文件的时候需要先关闭或者锁定数据库,该复制过程要在从节点上完成,最好是设置为的节点,这样对整个应用不会产生影响;

​ 文件快照:支持使用文件快照直接获取数据库某一时刻的镜像,快照过程不用统计,但是必须要要与在一起;

​ :最灵活的一种备份方式,但是消耗时间长。在t1时间点进行备份,会在t3时间点备份完成,索引备份的文件不是某一个时间点的数据,而是一个时间段的数据。

端口27017是什么_端口27001_27017端口

当我们要进行数据恢复时,可以找到离恢复时间点最近的一次全量备份,然后再加上这段时间范围之内的oplog,即可完成数据恢复。如果全量备份的频率较高,并且对数据的完整性要求不高的话,可以只恢复到最近的一次全量备份。

五、整合【重要】1、环境搭建1)创建项目–demo

端口27017是什么_27017端口_端口27001

2)导入依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.5.RELEASE</version>
</parent>
<dependencies>
    
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-mongodb</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</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>
</dependencies>

3)创建用户

use admin
db.createUser({user:"wuchangyong", pwd:"123456", roles:[{role:"readWriteAnyDatabase", db:"admin"}]});

4)yml配置

spring:
  application:
    name: springboot-mongodb-demo
  data:
    mongodb:
      authentication-database: admin    # 登录用户所在的数据库
      host: 127.0.0.1                   # 数据库的ip地址
      port: 27017                       # MongoDB端口号
      username: wuchangyong             # 用户名
      password: "123456"                # 用户密码   必须加上双引号
      database: test                    # 指定使用的数据库,若不存在该库会自动创建
logging:
  level:
    org.springframework.data.mongodb.core: debug   #控制台输出日志记录

5)启动类

package cn.itsource.mongodb_demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

2、代码操作1)实体类

/**
 * SpringDataMongoDB会自动以当前domain实体类名称作为数据集合名称,相当于表名称
 *   若添加了@Document("employee")注解,也可指定其他名称
 *   @Document 表示一个文档,实则是转化为json格式的字符串 与SpringDataElasticsearch的文档实体类一致
 */
@NoArgsConstructor
@AllArgsConstructor
@Accessors(chain = true)   //链式操作设置对象属性值
@Data
@Document("employee")
public class Employee {
    @Id// 必须指定id列
    private Long id;
    private String name;
    private Integer age;
    private String phone;
    private Date birthDay;
}

2)dao层接口

这里的dao层接口与、rch都是一致的写法。

/**
 * 这种写法不需要实现类,很显然和我们之前讲的都一样采用了动态代理。
 * 两个泛型:实体类和主键字段类型
 */
public interface EmployeeRepository extends MongoRepository<Employee, Long>{
}

3、基本CRUD1)测试新增文档

@SpringBootTest
@RunWith(SpringRunner.class)
public class DemoApplicationTests {
    @Autowired
    private EmployeeRepository employeeRepository;
    /**
     * 测试新增文档
     * @throws Exception
     */
    @Test
    public void testSave() throws Exception{
        Employee employee = new Employee()
                .setId(1L)
                .setName("张三丰")
                .setAge(22)
                .setBirthDay(new Date())
                .setPhone("18996157300");
        employeeRepository.save(employee);
    }
}

2)测试修改文档

/**
 * 测试修改文档
 *  save方法传入的实体对象
 *  其id在mongodb中存在就是修改,不存在就是新增
 * @throws Exception
 */
@Test
public void testSave2() throws Exception{
    Employee employee = new Employee()
        .setId(1L)
        .setName("张三丰123")
        .setAge(33)
        .setBirthDay(new Date())
        .setPhone("18996157301");
    employeeRepository.save(employee);
}

3)通过id获取文档

/**
 * 通过文档id查询
 * @throws Exception
 */
@Test
public void testFindById() throws Exception{
    System.out.println(employeeRepository.findById(1L).get());
}

4)通过id删除文档

/**
 * 通过文档id删除
 * @throws Exception
 */
@Test
public void testDeleteById() throws Exception{
    employeeRepository.deleteById(1L);
}

4、高级查询+分页+排序1)where is

/**
 * 条件查询
 *  where...is...
 * @throws Exception
 */
@Test
public void testQueryByWhereIs() throws Exception{
    Query query = new Query();
    // where...is... 相当于 where ? = ?
    query.addCriteria(Criteria.where("age").is(12));
    Employee employee = mongoTemplate.findOne(query, Employee.class);
    System.out.println(employee);
}

2)where in

/**
 * 条件查询
 *  where...in...
 * @throws Exception
 */
@Test
public void testQueryByWhereIn() throws Exception{
    List ages = Arrays.asList(11,12,22,33);
    Query query = new Query();
    // where...in... 相当于 where ? in (?)
    query.addCriteria(Criteria.where("age").in(ages));
    List employees = mongoTemplate.find(query, Employee.class);
    System.out.println(employees);
}

3)字符模糊匹配

/**
* 字符串模糊查询
*  where...regex...
* @throws Exception
*/
@Test
public void testQueryByLike() throws Exception{
    Query query = new Query();
    // where...regex... 相当于 where ? like ?
    query.addCriteria(Criteria.where("name").regex("张"));
    List employees = mongoTemplate.find(query, Employee.class);
    System.out.println(employees);
}

4)范围查询

/**
 * 范围查询
 *  where...gte...lte...
 *  gte表示大于等于  lte表示小于等于   gt表示大于   lt表示小于
 * @throws Exception
 */
@Test
public void testQueryByRange() throws Exception{
    Query query = new Query();
    // where...gte... lte 相当于 where ? >= ? and ? <= ?
    query.addCriteria(Criteria.where("age").gte(12).lte(33));
    List employees = mongoTemplate.find(query, Employee.class);
    System.out.println(employees);
}

5)不返回指定字段

/**
 * 指定字段不返回
 * @throws Exception
 */
@Test
public void testQueryExculdeFields() throws Exception{
    Query query = new Query();
    //exclude表示排除
    query.fields().exclude("age");
    List employees = mongoTemplate.find(query, Employee.class);
    System.out.println(employees);
}

6)分页查询+排序

/**
 * 分页查询+排序
 * @throws Exception
 */
@Test
public void testQueryPageAndSort() throws Exception{
    Query query = new Query();
    //分页:当前页码从0开始
    query.with(PageRequest.of(0, 2));
    //排序:Sort.Direction.ASC表示升序,Sort.Direction.DESC表示降序
    query.with(Sort.by(Sort.Direction.DESC, "age"));
    List employees = mongoTemplate.find(query, Employee.class);
    System.out.println(employees);
}

会员全站资源免费获取,点击查看会员权益

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注