MongoDB 文档数据库

文章目录

一、MongoDB 基本介绍

1、简介

MongoDB是一个开源、高性能、无模式的文档型数据库,当初的设计就是用于简化开发和方便扩展,是NoSQL数据库产品中的一种。是最像关系型数据库(MySQL)的非关系型数据库

它支持的数据结构非常松散,是一种类似于 JSON 的 格式叫BSON,所以它既可以存储比较复杂的数据类型,又相当的灵活。

MongoDB中的记录是一个文档,它是一个由字段和值对(field:value)组成的数据结构。MongoDB文档类似于JSON对象,即一个文档认为就是一个对象。字段的数据类型是字符型,它的值除了使用基本的一些类型外,还可以包括其他文档、普通数组和文档数组。

MongoDB的3大核心优势
灵活模式:通过json文档来实现灵活模式。
高可用性:通过复制集来保证高可用。
可扩展性:通过Sharding cluster来保证可扩展性。

2、应用场景

传统的关系型数据库(如MySQL),在数据操作的“三高”需求以及应对Web2.0的网站需求面前,显得力不从心。

三高”需求:

  • High performance - 对数据库高并发读写的需求。
  • Huge Storage - 对海量数据的高效率存储和访问的需求。
  • High Scalability && High Availability- 对数据库的高可扩展性和高可用性的需求。

而MongoDB可应对“三高”需求。具体的应用场景如:

  • 社交场景,使用 MongoDB 存储存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实现附近的人、地点等功能。
  • 游戏场景,使用 MongoDB 存储游戏用户信息,用户的装备、积分等直接以内嵌文档的形式存储,方便查询、高效率存储和访问。
  • 物流场景,使用 MongoDB 存储订单信息,订单状态在运送过程中会不断更新,以 MongoDB 内嵌数组的形式来存储,一次查询就能将订单所有的变更读取出来。
  • 物联网场景,使用 MongoDB 存储所有接入的智能设备信息,以及设备汇报的日志信息,并对这些信息进行多维度的分析。
  • 视频直播,使用 MongoDB 存储用户信息、点赞互动信息等。

这些应用场景中,数据操作方面的共同特点是:

  • 数据量大。
  • 写入操作频繁(读写都很频繁)。
  • 价值较低的数据,对事务性要求不高。

3、MongoDB的特点

  • 高性能

    MongoDB提供高性能的数据持久性。特别是,
    对嵌入式数据模型的支持减少了数据库系统上的I/O活动。
    索引支持更快的查询,并且可以包含来自嵌入式文档和数组的键。
    Gridfs解决文件存储的需求。
    mmapv1、WiredTiger、mongorocks(rocksdb)、in-memory 等多引擎支持满足各种场景需求。

  • 高可用性

    MongoDB的复制工具称为副本集(replica set),它可提供自动故障转移和数据冗余。

  • 高扩展性

    MongoDB提供了水平可扩展性作为其核心功能的一部分。分片将数据分布在一组集群的机器上。(海量数据存储,服务能力水平扩展)

  • 丰富的查询支持

    MongoDB支持丰富的查询语言,支持读和写操作(CRUD),比如数据聚合、文本搜索和地理空间查询等。

  • 其他特点:如无模式(动态模式)、灵活的文档模型等

4、MongoDB的优势

  • 读写效率高 – Data locality
    由于文档模型把相关数据集中在一块,在普通机械盘上读数据的时候不用花太多时间去定位磁头,因此在IO性能上有先天独厚的优势;
  • 可扩展能力强 – 无关联易分库
    关系型数据库很难做分布式的原因就是多节点海量数据关联有巨大的性能问题。如果不考虑关联,数据分区分库,水平扩展就比较简单;
  • 动态模式 – 灵活应对不同数据模型
    文档模型支持可变的数据模式,不要求每个文档都具有完全相同的结构。对很多异构数据场景支持非常好;
  • 模型自然 – 最接近于对象模型
    文档模型最接近于我们熟悉的对象模型。从内存到存储,无需经过ORM的双向转换,性能上和理解上都很自然易懂。

5、体系架构

在这里插入图片描述

SQL 术语/概念MongoDB术语/概念解释/说明
databasedatabase数据库
tablecollection数据库表/集合
rowdocument数据记录行/文档
columnfield数据字段/域
indexindex索引
table joins表连接,MongoDB不支持
嵌入文档MongoDB通过嵌入式文档来替代多表连接
primary keyprimary key主键,MongoDB自动将_id字段设置为主键

二、MongoDB 索引

1、索引操作

索引就是将文档按照某个(或某些)字段顺序组织起来,以便能根据该字段高效的查询。

索引的基本作用:搜索+排序。

# 创建索引
db.collection.createIndex(keys, options)
db.comment.createIndex({userid:1})
db.comment.createIndex({userid:1,nickname:-1})
# 获取索引
db.collection.getIndexes()
db.collection.totalIndexSize()
db.collection.getIndexSpecs()
# 修改索引
db.collection.reIndex({userid:-1})
# 删除索引
db.collection.dropIndex(index)
db.comment.dropIndex({userid:1})
db.collection.dropIndexes()
  • 索引分类

单字段索引、复合索引、多键索引、Hash索引、地理位图索引、TTL索引、全文索引等

db.person.createIndex( {age: 1} ) 
db.person.createIndex( {age: 1, name: 1} ) 
#多键索引是建立在数组(array)上的索引。多key索引是自动创建的。
db.person.createIndex( {habbit: 1} )  
#哈希索引(Hashed Index)是指按照某个字段的hash值来建立索引,目前主要用于MongoDB Sharded Cluster的Hash分片,hash索引只能满足字段完全匹配的查询,不能满足范围查询等。
db.person.createIndex( {a: "hashed"} ) 
db.places.createIndex( { loc : "2dsphere" } )
#TTL索引,在集合中的文档超过expireAfterSeconds关键字定义的时间后,会被自动删除。【删除过期文档的后台任务每60秒运行一次】
db.events.createIndex({"createdAt":1},{expireAfterSeconds: 3600})
#text索引可以包括任何值为字符串或者字符串元素数组的字段。为了执行文本检索查询,必须在集合上有一个text索引。
db.stores.createIndex( { name: "text", description: "text" } )

2、索引结构

MongoDB索引使用B树数据结构(确切的说是B-Tree,MySQL是B+Tree)。

当往某各个集合插入多个文档后,每个文档在经过底层的存储引擎持久化后,会有一个位置信息,通过这个位置信息,就能从存储引擎里读出该文档。

WiredTiger: WiredTiger在存储文档时生成的一个key【即位置信息:pos】,通过这个key能访问到对应的文档。

假设现在有个查询db.person.find( {age: 18} ),查询所有年龄为18岁的人,这时需要遍历所有的文档(全表扫描),根据位置信息读出文档,对比age字段是否为18。当然如果只有4个文档,全表扫描的开销并不大,但如果集合文档数量到百万、甚至千万上亿的时候,对集合进行全表扫描开销是非常大的,一个查询耗费数十秒甚至几分钟都有可能。

建立索引后,MongoDB会额外存储一份按age字段升序排序的索引数据,引结构类似如下:索引通常采用类似b- tree的结构持久化存储,以保证从索引里快速(O(logN)的时间复杂度)找出某个age值对应的位置信息,然后根据位置信息就能读取出对应的文档。

在这里插入图片描述
B-Tree != Binary Tree && B-Tree == Self-Balancing Tree

MongoDB默认会为插入的文档生成_id字段(如果应用本身没有指定该字段),_id是文档唯一的标识,为了保证能根据文档id快递查询文档,MongoDB默认会为集合创建_id字段的索引。

3、索引特性

  • 索引存储在内存(RAM)中,应该确保该索引的大小不超过内存的限制。
  • 如果索引的大小大于内存的限制,MongoDB会删除一些索引,这将导致性能下降。
  • 每个collection限制64个索引。
  • 索引名的长度不能超过125个字符。
  • 一个复合索引最多可以有31个字段。
  • 所有索引字段是一个数组,不能使用索引覆盖查询。
  • 正则表达式及非操作符,如in,not等;算术运算符,如mod,等;where子句。
  • 索引越多写性能越差,例如:一张频繁修改的collection ,其索引达到20-30索引性能严重瓶颈。
  • 建立索引是一个IO密集型操作,特别是当你的集合很大的时候。包括MySQL在内的所有支持辅助索引的数据库系统都有这种情况。如果你需要在一个大集合上建立索引,可以考虑在后台建立它。
  • 索引是有代价的,如果很少对集合进行读取,可以不使用索引。

4、索引语句优化

  • Profiling分析器

使用Profiling 查看慢查询。

db.setProfilingLevel(n,slowms=1000ms)

#n=0 关闭Profiling。
#n=1 记录执行时间大于slowms的操作
#n=2 记录所有操作
#分析结果记录在system.profile集合。
  • 查询优化器

分析查询性能(Analyze Query Performance)通常使用执行计划(解释计划、Explain Plan)来查看查询的情况,如查询耗费的时间、是否基于索引查询等。

MongoDB查询优化器:当有多个索引可用时,MongoDB会同时尝试每一个索引,最先拿到100条(或者所有)结果者为胜者。短暂的cache 上次的优化结果直到:– 1000 个更新以后,– reIndex或restart。

#告诉数据库使用什么索引
db.recipes.find( {calories: { $lt : 1000 } }).hint ( { _id : 1 } )
#告诉数据库不要使用什么索引
db.recipes.find({ calories: { $lt : 1000 } }).hint( { $natural : 1 } )

db.collection.find(query,options).explain(options)
# eg:未对userid创建索引的情况
db.comment.find({userid:"1003"}).explain()
{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "articledb.comment",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "userid" : {
                "$eq" : "1003"
           }
       },
        "winningPlan" : {
            "stage" : "COLLSCAN",   ## 基于集合扫描
            "filter" : {
                "userid" : {
                    "$eq" : "1003"
               }
           },
            "direction" : "forward"
       },
        "rejectedPlans" : [ ]
   },
    "serverInfo" : {
        "host" : "9ef3740277ad",
        "port" : 27017,
        "version" : "4.0.10",
        "gitVersion" : "c389e7f69f637f7a1ac3cc9fae843b635f20b766"
   },
    "ok" : 1
}
# eg:对userid创建索引的情况
db.comment.find({userid:"1013"}).explain()
{
    "queryPlanner" : {
        "plannerVersion" : 1,
        "namespace" : "articledb.comment",
        "indexFilterSet" : false,
        "parsedQuery" : {
            "userid" : {
                "$eq" : "1013"
           }
       },
        "winningPlan" : {
            "stage" : "FETCH",
            "inputStage" : {
                "stage" : "IXSCAN",    ## 索引扫描
                "keyPattern" : {
                    "userid" : 1
               },
                "indexName" : "userid_1",
                "isMultiKey" : false,
                "multiKeyPaths" : {
                    "userid" : [ ]
               },
                "isUnique" : false,
                "isSparse" : false,
                "isPartial" : false,
                "indexVersion" : 2,
                "direction" : "forward",
                "indexBounds" : {
                    "userid" : [
                        "[\"1013\", \"1013\"]"
                   ]
               }
           }
       },
        "rejectedPlans" : [ ]
   },
    "serverInfo" : {
        "host" : "9ef3740277ad",
        "port" : 27017,
        "version" : "4.0.10",
        "gitVersion" : "c389e7f69f637f7a1ac3cc9fae843b635f20b766"
   },
    "ok" : 1
}

三、MongoDB 存储引擎

1、默认WiredTiger

存储引擎是MongoDB的核心组件,负责管理数据如何存储在硬盘和内存上。从MongoDB 3.2 版本开始,MongoDB 支持多数据存储引擎,MongoDB支持的存储引擎有:WiredTiger,MMAPv1和In-Memory。

从mongodb3.2开始默认的存储引擎是WiredTiger,3.3版本之前的默认存储引擎是MMAPv1,mongodb4.x版本不再支持MMAPv1存储引擎。WiredTiger是各种操作应用的理想选择,因此是MongoDB的默认存储引擎

MongoDB不仅能将数据持久化存储到硬盘文件中,而且还能将数据只保存到内存中;In-Memory存储引擎用于将数据只存储在内存中,只将少量的元数据和诊断日志(Diagnostic)存储到硬盘文件中,由于不需要Disk的IO操作,就能获取索取的数据,In-Memory存储引擎大幅度降低了数据查询的延迟(Latency)。

查看存储引擎【搜索engine】:
db.serverStatus()
db.serverStatus().connections

查看WiredTiger内部缓存到底占用了多少内存:
db.runCommand({serverStatus:1}).wiredTiger.cache[“bytes currently in the cache”]

2、Why WiredTiger

WiredTiger存储引擎的主要优势:

  • 最大化可用缓存: WiredTiger最大限度地利用可用内存作为缓存来减少I / O瓶颈。使用了两个缓存:WiredTiger缓存文件系统缓存。WiredTiger缓存存储未压缩的数据并提供类似内存的性能。操作系统的文件系统缓存存储压缩数据。当在WiredTiger缓存中找不到数据时,WiredTiger将在文件系统缓存中查找数据。

    mongodb从3.4版本开始默认使用内存为下面两个中的最大一个:

    • 50% of (RAM - 1 GB)
    • 256MB
  • 高吞吐量: WiredTiger使用“写入时复制” - 当文档更新时,WiredTiger将制作文档的新副本并确定最新版本以返回给读者。此方法允许多个客户端同时修改集合中的不同文档,从而实现更高的并发性和吞吐量。当应用程序使用具有多个内核的主机(越多越好)并且多个线程正在写入不同的文档时,实现最佳写入性能。

  • 减少存储空间并改善磁盘IOP: WiredTiger使用压缩算法来减少磁盘上存储的数据量。不仅存储减少了,而且随着从磁盘读取或写入更少的位,IOP性能也会提高。某些类型的文件比其他文件压缩得更好。文本文件是高度可压缩的,而二进制数据可能不是可压缩的,因为它可能已经被编码和压缩。使用压缩时,WiredTiger会产生额外的CPU周期,但用户可以配置压缩方案以优化CPU开销与压缩比。Snappy是默认的压缩引擎,在高压缩率和低CPU开销之间提供了良好的平衡。Zlib将实现更高的压缩比,但会产生额外的CPU周期。

  • 压缩(索引和journals日志):索引可以在内存和磁盘上压缩。WiredTiger利用前缀压缩来压缩索引,节省RAM使用以及释放存储IOP。默认情况下,使用Snappy压缩压缩journals日志。

  • 多核可扩展性:随着CPU制造商缩小到更小的平版印刷,功耗变得越来越成问题,处理器趋势已转向多核架构,以维持摩尔定律的节奏。WiredTiger在设计时考虑了现代的多核架构,并提供跨多核系统的可扩展性。危险指针,无锁算法和快速锁存等编程技术可最大限度地减少线程之间的争用。线程可以执行操作而不会相互阻塞 - 从而减少线程争用,更好的并发性和更高的吞吐量。

  • 文档级并发:WiredTiger使用文档级并发控制进行写操作。因此,多个客户端可以同时修改集合的不同文档。对于大多数读写操作,WiredTiger使用乐观并发控制[乐观锁]。WiredTiger仅在全局,数据库和集合级别使用意向锁。当存储引擎检测到两个操作之间的冲突时,会发生写入冲突,导致MongoDB透明地重试该操作。

在这里插入图片描述

在移动到WiredTiger缓存之前,在文件系统缓存中找到的数据首先经过解压缩过程。

WiredTiger缓存在保存尽可能多的工作集时表现最佳。但是,为需要它的其他进程(如操作系统,包括文件系统缓存)保留内存也很重要。这也包括MongoDB本身,因此整体上消耗的内存比WiredTiger主动使用的内存要多。

WiredTiger允许用户为其读取指定隔离级别。读取操作可以返回大部分副本集已接受或提交到磁盘的数据视图。这样可以保证应用程序只读取在发生故障时仍然存在的数据,并且在将新的副本集成员提升为主要成员时不会回滚。

3、checkpoint

在Checkpoint操作开始时,WiredTiger提供指定时间点(point-in-time)的数据库快照(Snapshot),该Snapshot呈现的是内存中数据的一致性视图。当向Disk写入数据时,WiredTiger将Snapshot中的所有数据以一致性方式写入到数据文件(Disk Files)中。一旦Checkpoint创建成功,WiredTiger保证数据文件和内存数据是一致性的,因此,Checkpoint担当的是还原点(Recovery Point),Checkpoint操作能够缩短MongoDB从Journal日志文件还原数据的时间。

当WiredTiger创建Checkpoint时,MongoDB将数据刷新到数据文件(Disk Files)中,在默认情况下,WiredTiger创建Checkpoint的时间间隔是60s,或产生2GB的Journal文件。在WiredTiger创建新的Checkpoint期间,上一个Checkpoint仍然是有效的,这意味着,即使MongoDB在创建新的Checkpoint期间遭遇到错误而异常终止运行,只要重启,MongoDB就能从上一个有效的Checkpoint开始还原数据。

当MongoDB以原子方式更新WiredTiger的元数据表,使其引用新的Checkpoint时,表明新的Checkpoint创建成功,MongoDB将老的Checkpoint占用的Disk空间释放。使用WiredTiger 存储引擎,如果没有记录数据更新的日志,MongoDB只能还原到上一个Checkpoint;如果要还原在上一个Checkpoint之后执行的修改操作,必须使用Jounal日志文件。

  • journal日志[预写日志]

WiredTiger使用预写日志的机制,在数据更新时,先将数据更新写入到日志文件,然后在创建Checkpoint操作开始时,将日志文件中记录的操作,刷新到数据文件,就是说:**通过预写日志和Checkpoint,将数据更新持久化到数据文件中,实现数据的一致性。**WiredTiger 日志文件会持久化记录从上一次Checkpoint操作之后发生的所有数据更新,在MongoDB系统崩溃时,通过日志文件能够还原从上次Checkpoint操作之后发生的数据更新。

  • 压缩

WiredTiger支持对所有集合和索引进行压缩。压缩可以以额外的CPU为代价最大限度地减少磁盘的使用。

默认情况下WiredTiger存储引擎使用snappy方式压缩所有集合,前缀压缩方式压缩索引。

对于集合的压缩还可以使用zlib方式压缩。

storage:
   joural:
      enabled: true    #启用journal日志,false为关闭
   engine:  wiredTiger  #指定存储引擎 
   wiredTiger:
      cacheSizeGB: 8    #来指定mongodb使用内存的多少-8G
      engineConfig:     #存储引擎的配置
         journalCompressor:  snappy        #指定journal日志的压缩方式
      indexConfig:                         #索引配置 
         prefixCompression:  true          #前缀压缩开启    
      collectionConfig:
         blockCompressor: snappy           #指定集合的压缩方式 

4、Disk空间回收

当从MongoDB中删除文档(Documents)或集合(Collections)后,MongoDB不会将Disk空间释放给OS,MongoDB在数据文件(Data Files)中维护Empty Records的列表。当重新插入数据后,MongoDB从Empty Records列表中分配存储空间给新的Document,因此,不需要重新开辟空间。为了更新有效的重用Disk空间,必须重新整理数据碎片。

WiredTiger使用compact 命令,移除集合(Collection)中数据和索引的碎片,并将unused的空间释放,调用语法:

db.runCommand ( { compact: '<collection>' } )
eg:
db.runCommand({compact:'comment',force:true})
#在执行compact命令时,MongoDB会对当前的database加锁,阻塞其他操作。在compact命令执行完成之后,mongod会重建集合的所有索引

5、db.serverStatus()和db.stats()

监控实例的运行状态(内存使用、锁、用户连接等信息)。

# 一些重要信息
db.serverStatus().connections
db.serverStatus().locks
db.serverStatus().activeClients
db.serverStatus().opcounters
db.serverStatus().opcountersRepl
db.serverStatus().storageEngine
db.serverStatus().mem

db.serverStatus()
{
        "host" : "mdb-6229df1e0f921362cbb1b28f-0",
        "version" : "4.0.21",
        "process" : "mongod",
        "pid" : NumberLong(394490),
        "uptime" : 96,
        "uptimeMillis" : NumberLong(95864),
        "uptimeEstimate" : NumberLong(95),
        "localTime" : ISODate("2022-03-14T08:09:44.443Z"),
        "asserts" : {
                "regular" : 0,
                "warning" : 0,
                "msg" : 0,
                "user" : 65,
                "rollovers" : 0
        },
        "connections" : {
                "current" : 21,
                "available" : 979,
                "totalCreated" : 299,
                "active" : 3
        },
...

db.stats()
{
        "db" : "test",
        "collections" : 1,
        "views" : 0,
        "objects" : 100,
        "avgObjSize" : 33.22,
        "dataSize" : 3322,
        "storageSize" : 32768,
        "numExtents" : 0,
        "indexes" : 1,
        "indexSize" : 32768,
        "fsUsedSize" : 643317760,
        "fsTotalSize" : 32196526080,
        "ok" : 1,
        "operationTime" : Timestamp(1647246974, 1),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1647246974, 1),
                "signature" : {
                        "hash" : BinData(0,"pqfevp7IwKB0jYF1a8R3cPNQwJg="),
                        "keyId" : NumberLong("7073430430610882562")
                }
        }
}

四、MongoDB 复制集

1、复制集简介

MongoDB中的副本集(Replica Set)是一组维护相同数据集的mongod服务。 副本集可提供冗余和高可用性,是所有生产部署的基础。

通俗的讲就是用多台机器进行同一数据的异步同步,从而使多台机器拥有同一数据的多个副本,并且当主库宕掉时在不需要用户干预的情况下自动切换其他备份服务器做主库。而且还可以利用副本服务器做只读服务器,实现读写分离,提高负载。

副本集作用

  • 1)高可用
    防止设备(服务器、网络)故障。
    提供自动failover 功能。
  • 2)灾难恢复
    当发生故障时,可以从其他节点恢复。
  • 3)功能隔离
    用于分析、报表,数据挖掘,系统任务等。
    用于备份。
  • 4)运维升级&数据维护
    滚动升级从节点,最后升级主节点。

2、复制集四种架构及角色

在这里插入图片描述

  • 1)主节点(Primary)

MongoDB复制集由一组mongod实例(进程)组成,包含一个Primary节点和多个Secondary节点。

Primary节点接收所有来自客户端的写操作,并会将所有数据集的变动记录到oplog
复制集为所有来自主节点的读提供了严格的一致性校验 。

  • 2)副本节点(Secondary)

MongoDB复制集由一组mongod实例(进程)组成,包含一个Primary节点和多个Secondary节点。
一旦主节点不可用,复制集就会将一个从节点选举成为新的主节点。

  • 3)仲裁节点(Arbiter)

仲裁节点并不包含数据集,它仅仅是在选举过程中参与投票。

如果你的副本+主节点的个数是偶数,建议加一个仲裁者,形成奇数,容易满足大多数的投票。
如果你的副本+主节点的个数是奇数,可以不加仲裁者。

  • 4)Hidden节点

Hidden节点不能被选为主(Priority为0),并且对Driver不可见。因Hidden节点不会接受Driver的请求,可使用Hidden节点做一些数据备份、离线计算的任务,不会影响复制集的服务。

  • 5)Delayed节点

Delayed节点必须是Hidden节点。因Delayed节点的数据比Primary落后一段时间,当错误或者无效的数据写入Primary时,可通过Delayed节点的数据来恢复到之前的时间点。

3、Oplog

  • 作用:Primary与Secondary之间通过oplog来同步数据。

Primary上的写操作完成后,会向特殊的local.oplog.rs特殊集合写入一条oplog,Secondary不断的从Primary取新的oplog并应用,从而实现Replication的功能。同时由于其记录了Primary上的写操作,故还能将其用作数据恢复。可以简单的将其视作MySQL中的binlog。

  • 大小:Oplog是一个capped collection【通俗意思就是它是固定大小,循环使用的】。MongoDB默认将其大小设置为可用disk空间的5%。
  • Oplog格式样例:
{
	"ts": Timestamp(1646912240, 1),  ##操作时间
	"t": NumberLong(4),              ##数据类型
	"h": NumberLong("478351560757205978"),   ##全局唯一操作符
	"v": 2,     ##oplog版本
	"op": "n",  ##操作类型:i-插入,u-更新,d-删除,c-执行命令(如create/dropDatabase),n-空操作 
	"ns": "articleadb.test",  ##操作的集合      
	"wall": ISODate("2022-03-10T11:37:20.281Z"),
	"o": {             ##操作内容   
		"_id" : ObjectId("622f15dc7284015c7a121e2f"), "x" : 14548 }
	}
}

4、复制集数据同步流程

MongoDB复制集里的Secondary会从Primary上同步数据,以保持副本集所有节点的数据保持一致,数据同步主要包含2个过程:initial sync、replication。

先通过init sync同步全量数据,再通过replication不断重放Primary上的oplog同步增量数据。

  • 同步流程:

step1、T1时间,从Primary同步所有数据库的数据(local除外),通过 istDatabases + listCollections + cloneCollection命令组合完成, 假设T2时间完成所有操作。
step2、从Primary应用[T1-T2]时间段内的所有oplog,可能部分操作已经包含在步骤1,但由于oplog的幂等性,可重复应用。
step3、根据Primary各集合的index设置,在Secondary上为相应集合创建index。(每个集合_id的index已在步骤1中完成)。

  • 同步场景分析

a、副本集初始化

初始化选出Primary后,此时Secondary上无有效数据,oplog是空的,会先进行initial sync,然后不断的应用新的oplog 。

b、新加入成员

因新成员上无有效数据,会先进行initial sync,然后不断的应用新的oplog。

c、有数据的节点加入

如果该节点最新的oplog时间戳,比所有节点最旧的oplog时间戳还要小,该节点将找不到同步源,会一直处于RECOVERING而不能服务;反之,如果能找到同步源,则直接进入replication阶段,不断的应用新的oplog。

注意:因oplog太旧而处于RECOVERING的节点目前无法自动恢复,需人工介入处理(故设置合理的oplog大小非常重要),最简单的方式是发送resync命令,让该节点重新进行initial sync

官方给出两种方式:第一种是停掉数据库,直接删除本地数据,然后启动mongo数据库,启动之后存在一个同步的过程,会非常耗时。另一种方式是停掉数据库,直接拷贝主节点上的数据,然后再启动mongo,这样就不存在数据同步的过程了,但是拷贝主节点上的数据,也有一个问题,就是数据时刻在变化,拷贝过程中难免会漏掉一些数据。

通过 db.printReplicationInfo()可以查看oplog的一些信息:

db.printReplicationInfo();
configured oplog size:   1518.617431640625MB
log length start to end: 349348secs (97.04hrs)
oplog first event time:  Thu Mar 10 2022 19:22:29 GMT+0800 (CST)
oplog last event time:   Mon Mar 14 2022 20:24:57 GMT+0800 (CST)
now:                     Mon Mar 14 2022 20:24:56 GMT+0800 (CST)
  • 异常处理

当Primary宕机时,如果有数据未同步到Secondary,当Primary重新加入时,如果新的Primary上已经发生了写操作,则旧Primary需要回滚部分操作,以保证数据集与新的Primary一致。
旧Primary将回滚的数据写到单独的rollback目录下,数据库管理员可根据需要使用mongorestore进行恢复。

5、复制集选举

复制集通过replSetInitiate命令(或mongo shell的rs.initiate())进行初始化,初始化后各个成员间开始发送心跳消息,并发起Priamry选举操作,获得大多数成员投票支持的节点,会成为Primary,其余节点成为Secondary。

当复制集内存活成员数量不足大多数时,整个复制集将无法选举出Primary,复制集将无法提供写服务,处于只读状态。

  • 复制集选举场景

    复制集的初始化。
    复制集被reconfig。
    Secondary节点检测到Primary宕机时,会触发新Primary的选举。
    当有Primary节点主动stepDown(主动降级为Secondary)时,也会触发新的Primary选举。
    复制集成员心跳检测结果发生变化,比如某个节点挂了或者新增节点。

  • 节点间心跳

    Primary的选举受节点间心跳、优先级、最新的oplog时间等多种因素影响。

    复制集成员间默认每2s会发送一次心跳信息,如果10s未收到某个节点的心跳,则认为该节点已宕机;如果宕机的节点为 Primary,Secondary(前提是可被选为Primary)会发起新的Primary选举。

    每个节点都倾向于投票给优先级最高的节点。
    优先级默认为1,取值为0-1000。
    优先级为0的节点不会主动发起Primary选举。

    当Primary发现有优先级更高Secondary,并且该Secondary的数据落后在10s内,则Primary会主动降级,让优先级更高的 Secondary有成为Primary的机会。(reconfig)

  • Optime

    拥有最新optime(最近一条oplog的时间戳)的节点才能被选为主。

  • 网络分区

    只有更大多数投票节点间保持网络连通,才有机会被选Primary;如果Primary与大多数的节点断开连接,Primary会主动降级为Secondary。当发生网络分区时,可能在短时间内出现多个 Primary【类似脑裂】。

  • Vote属性

    复制集成员最多50个,但是参与Primary选举投票的成员最多7个,其他成员的votes属性必须设置为0,即不参与投票。

6、写——Writen Concern机制

写关注机制用来指定MongoDB对写操作的回执行为。可在connection level或者写操作level指定。

  • {w:0}——关闭Writen Concern。

  • {w:1}——MongoDB在写完内存后发送确认。系统崩溃时可能会丢失最多100ms数据。

  • {w:1,j: true}——刷盘Journal之后再发送写回执。Group Commit 30ms间隔,所以单个请求可能会等最多30ms才返回。

  • {w:n}——等待数据复制到n个节点再发送回执。

  • {w:majority}——等待数据复制到大多数节点再发送回执。

在这里插入图片描述

7、读——复制集读选项

默认情况下,复制集的所有读请求都发到Primary,Driver可通过设置Read Preference来将读请求路由到其他的节点。

  • primary:默认规则,所有读请求发到Primary。
  • primaryPreferred:Primary优先,如果Primary不可达,请求Secondary。
  • secondary:所有的读请求都发到secondary。
  • secondaryPreferred:Secondary优先,当所有Secondary不可达时,请求Primary。
  • nearest:读请求发送到最近的可达节点上(通过ping探测得出最近的节点)。

五、MongoDB 分片集

1、分片目的

分片(sharding)是一种跨多台机器分布数据的方法, MongoDB使用分片来支持具有非常大的数据集和高吞吐量操作的部署。

具有大型数据集或高吞吐量应用程序的数据库系统可以会挑战单个服务器的容量。例如,高查询率会耗尽服务器的CPU容量。工作集大小大于系统的RAM会抢掉磁盘驱动器的I/O容量。

MongoDB支持通过分片进行水平扩展来解决系统增长。

MongoDB的3大核心优势:
• 灵活模式:通过json文档来实现灵活模式。
• 高可用性:通过复制集来保证高可用。
• 可扩展性:通过Sharding cluster来保证可扩展性

当MongoDB复制集遇到下面的业务场景时,需要考虑使用Sharding cluster:
• 存储容量需求超出单机磁盘容量。
• 活跃的数据集超出单机内存容量,导致很多请求都要从磁盘读取数 据,影响性能。
• 写IOPS超出单个MongoDB节点的写服务能力。

2、分片集群架构

Sharding Cluster由Mongos、Config server 和 Shard 3个组件构成。

  • Mongos路由:客户端请求的入口。
  • Config server配置中心:存储Sharding Cluster的所有元数据。[主要是Chunk信息]
  • Shard分片:存储应用数据记录。

在这里插入图片描述

如何确定 shard、mongos 数量?

shard、mongos的数量归根结底是由应用需求决定。

  • 如果你使用 sharding 只是解决海量数据存储的问题,访问并不多,那么很简单,假设你单个shard能存储 M, 需要的存储总量是N。
    n u m b e r O f S h a r d s = N / M / 0.75 numberOfShards = N / M / 0.75 numberOfShards=N/M/0.75

    n u m b e r O f M o n g o s = 2 + numberOfMongos = 2+ numberOfMongos=2+

  • 如果你使用sharding是解决高并发写入(或读取)数据的问题,总的数据量其实很小。假设单个shard最大qps为M,单个mongos最大qps为Ms,需要总的qps为N。
    n u m b e r O f S h a r d s = N / M / 0.75 numberOfShards = N / M / 0.75 numberOfShards=N/M/0.75

    n u m b e r O f M o n g o s = N / M s / 0.75 numberOfMongos = N / Ms/0.75 numberOfMongos=N/Ms/0.75

3、数据分布策略

  • 策略一:范围分片

集合根据x字段来分片,x的取值范围为[minKey, maxKey],将整个取值范围划分为多个chunk,每个chunk的数据都存储在同一个Shard上,每个Shard可以存储很多个chunk,chunk存储在哪个shard的信息会存储在Config server种,mongos也会根据各个shard上的chunk的数量来自动做负载均衡。

范围分片的优点:
范围分片能很好的满足范围查询的需求,比如想查询x的值在[-30, 10]之间的所有文档,这时mongos直接能将请求路由到ChunkX,就能查询出所有符合条件的文档。

范围分片的缺点:
如果shard key有明显递增(或者递减)趋势,则新插入的文档多会分布到同一个chunk,无法扩展写的能力。

  • chunk分裂

对于一个刚配置为Sharding的collection ,最开始只有一个chunk,范围是从-∞到 +∞。随着数据的增长,其中的数据大小超过了配置的chunk size,默认是64M,则这个chunk就会分裂成两个。数据的增长会让chunk分裂得越来越多。

  • 策略二:hash分片

Hash分片是根据用户的shard key计算hash值(64bit整型),根据hash值按照范围分片的策略将文档分布到不同的chunk。

Hash分片与范围分片互补,能将文档随机的分散到各个chunk,充分的扩展写能力,弥补了范围分片的不足,但不能高效的服务范围查询,所有的范围查询要分发到后端所有的Shard才能找出满足条件的文档。

  • 分片键

必须为分片集群定义分片键。

分片键基于一个或多个字段。

分片键决定了文档在集群中的位置。

一旦集合已经分片,就不可以直接修改分片键。

分片键必须有索引。

分片键大小限制512bytes。

MongoDB不接受已进行collection级分片的collection上插入无分片键的文档(也不支持空值插入)。

  • 如何选择一个优秀的片键?

所有的插入、更新以及删除将会均匀分发到集群中的所有分片中。

所有的查询将会在集群中的所有分片中平均地分发。

所有的操作将会只面向相关的分片:更新或者删除操作将不会发送到一个没有存储被修改数据的分片上。

相似地,一个查询将不会被送到没有存储被查询数据的分片上。

  • 分片键选择建议

1)递增的shard key(范围片键)
数据文件挪动小。(优势)
因为数据文件递增,所以会把insert的写IO永久放在最后一片上,造成最后一片的写热点。
同时,随着最后一片的数据量增大,将不断的发生迁移至之前的片上。

2)随机的shard key(Hash片键)
数据分布均匀,insert的写IO均匀分布在多个片上。(优势)
大量的随机IO,磁盘不堪重荷。

3)混合型shard key(范围+Hash)
大方向随机递增。
小范围随机分布。

4、Chunk迁移与数据平衡

  • 基本概念

Chunk:数据块。

MongoDB基于分片键范围将数据分区到数据块中。

数据的均衡通过数据块的迁移来达到,MongoDB会尝试在不同的片之间保持数据均衡。

初始1个chunk。缺省chunk 大小:64MB。chunksize设置通常100-200M。小的chunksize:数据均衡是迁移速度快,数据分布更均匀,但是数据分裂频繁,路由节点消耗更多资源。大的chunksize:数据分裂少,但是数据块移动集中消耗IO资源。

MongoDB自动拆分 & 迁移chunks。

Banlancer:后台任务。该任务不断对针对下面3种场景来判断是否需要迁移chunk,如果需要,则发送moveChunk命令到源shard上开始迁移,整个迁移过程比较复杂。同事也提供了moveChunk 命令,让用户能主动的触发数据迁移。

  • 需要chunk迁移的场景

1)场景1:
当多个shard上chunk数量分布不均时,MongoDB会自动的在shard间迁移chunk,尽可能让各个shard上chunk数量均匀分布。
2)场景2:
用户调用removeShard命令后,被移除shard上的chunk就需要被迁移到其他的shard上,等该shard上没有数据后,安全下线。
3)场景3:
MongoDB sharding支持shard tag功能,可以对shard及shard key range打标签,系统会自动将对应range的数据迁移到拥有相同tag的shard上。

  • Banlancer工作步骤

Step1:获取集合对应的chunk分布信息。
Step2:检查是否需要chunk分裂。
Step3:迁移draining shard上的chunk。
Step4:迁移tag不匹配的chunk。
Step5:负载均衡迁移。
Step6:执行迁移。

Balancer是基于各个shard持有的chunk数量来做负载均衡迁移,如果一个集合在2个shard里的chunk数量相差超过一定阈值,则会触发迁移。 (通过对比持有chunk 最多和最少的shard)。eg:当集合的chunk数量小于20时,如果2个shard 间chunk数量相差大于或等于2时,就会构建迁移任务,将某个chunk从持有chunk最多的shard迁移到持有chunk最少的 shard。

  • moveChunk命令

在这里插入图片描述

5、mongos路由策略

在Shard Cluster里,用户可以将集合的数据以chunk为单位分散存储到多个shard 上。

当写入新文档时,mongos从config server上获取集合的路由表本地,mongos从config server上获取到路由表后,会缓存在本地内存,避免每次写入/查询都去config server上取表。而在Sharded Cluster里,mongos会自动在shard之间迁移chunk以均衡负载(用户也可以发送moveChunk命令来手动迁移),那么一旦 chunk 发生迁移后,mongos 本地缓存的路由表就会失效,从而请求被路由到错误的 shard。

MongoDB 的做法是给路由表增加版本信息,mongos在写入时,会带上自身缓存的路由表版本,当请求到达shard后,shard 发现mongos的路由表版本比自己的低,则说明路由表已经发生过更新,这时mongos会重新到config server上取最新的路由表,然后按新的路由表来写入。

六、MongoDB 安装部署

0、常用CRUD操作

# 常用命令
#选择切换数据库:
use articledb
#插入数据:
db.comment.insert({bson数据})
db.comment.insert({bson数据1},{bson数据2},{bson数据3})

#查询所有数据:
db.comment.find();
#条件查询数据:
db.comment.find({条件})
#查询符合条件的第一条记录:
db.comment.findOne({条件})
#查询符合条件的前几条记录:
db.comment.find({条件}).limit(条数)
#查询符合条件的跳过的记录:
db.comment.find({条件}).skip(条数)

#修改数据:
db.comment.update({条件},{修改后的数据})
db.comment.update({条件},{$set:{要修改部分的字段:数据})
#修改数据并自增某字段值:
db.comment.update({条件},{$inc:{自增的字段:步进值}})

#删除数据:
db.comment.remove({条件})
#集合创建:
db.createCollection("mycollection")
#集合删除:
db.mycollection.drop()

#统计查询:
db.comment.count({条件})
#模糊查询:
db.comment.find({字段名:/正则表达式/})
#条件比较运算:
db.comment.find({字段名:{$gt:}})
#包含查询:
db.comment.find({字段名:{$in:[值1,值2]}})
db.comment.find({字段名:{$nin:[值1,值2]}})
#条件连接查询:
db.comment.find({$and:[{条件1},{条件2}]})或db.comment.find({$or:[{条件1},{条件2}]})

1、单节点

# 单节点 后台启动
mongod -f /etc/mongod.conf

# 关闭服务的方法
#方法一
kill -2 Pid

#如果一旦是因为数据损坏,则需要进行如下操作
rm -f /mongodb/single/data/db/*.lock
mongod --repair --dbpath=/var/lib/mongo

#方法二:标准的关闭方法(数据不容易出错,但是比较麻烦)
#客户端登录服务,注意,这里通过localhost登录,如果需要远程登录,必须先登录认证才行。
mongo --port 27017
##切换到admin库
use admin
#关闭服务
db.shutdownServer()
#必须是在 admin库下执行该关闭服务命令。
#如果没有开启认证,必须是从 localhost登陆的,才能执行关闭服务命令。
#非 localhost的、通过远程登录的,必须有登录且必须登录用户有对admin操作权限才可以。

2、副本集:一主一从一仲裁

#第一步 准备主节点
mkdir -p /mongodb/replica_sets/myrs_27017/log
mkdir -p /mongodb/replica_sets/myrs_27017/data/db
vim /mongodb/replica_sets/myrs_27017/mongod.conf
#第二步 准备副本节点
mkdir -p /mongodb/replica_sets/myrs_27018/log
mkdir -p /mongodb/replica_sets/myrs_27018/data/db
vim /mongodb/replica_sets/myrs_27018/mongod.conf
#第三步 准备仲裁节点
mkdir -p /mongodb/replica_sets/myrs_27019/log
mkdir -p /mongodb/replica_sets/myrs_27019/data/db
vim /mongodb/replica_sets/myrs_27019/mongod.conf
#第四步 启动节点
# 启动主节点、启动副本节点、启动仲裁节点
mongod -f /mongodb/replica_sets/myrs_27017/mongod.conf
mongod -f /mongodb/replica_sets/myrs_27018/mongod.conf
mongod -f /mongodb/replica_sets/myrs_27019/mongod.conf

# 连接主节点
mongo --host 192.168.168.101 --port 27017
# 第五步 初始化副本集和主节点
rs.initiate()

rs.conf()

rs.status()

rs.add("192.168.168.101:27018")

# 第五步 添加仲裁节点
rs.addArb("192.168.168.101:27019")
# 如果加入有问题(卡住,很长时间后报错)  先执行下面命令,参考:https://docs.mongodb.com/manual/reference/command/setDefaultRWConcern/
db.adminCommand({
  "setDefaultRWConcern" : 1,
  "defaultWriteConcern" : {
    "w" : 2
  }
})

rs.status

#从库 设置为奴隶节点,允许在从成员上运行读的操作
rs.secondaryOk()

## 副本集的关闭
#关闭副本集中的服务,建议依次关闭仲裁节点→副本节点→主节点。
#客户端登录服务,注意,这里通过localhost登录,如果需要远程登录,必须先登录认证才行。

#先关闭仲裁节点
mongo --port 27018
#告知副本集说本机要下线
rs.stepDown()
#切换到admin库
use admin
#关闭服务
db.shutdownServer()

#再关闭副本节点
mongo --port 27018
#告知副本集说本机要下线
rs.stepDown()
#切换到admin库
use admin
#关闭服务
db.shutdownServer()

#最后关闭主节点
mongo --port 27017
#告知副本集说本机要下线
rs.stepDown()
#切换到admin库
use admin
#关闭服务
db.shutdownServer()
# 主节点
systemLog:
  #MongoDB发送所有日志输出的目标指定为文件
  destination: file
  #mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
  path: "/mongodb/replica_sets/myrs_27017/log/mongod.log"
  #当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾。
  logAppend: true
storage:
  #mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod。
  dbPath: "/mongodb/replica_sets/myrs_27017/data/db"
  journal:
#启用或禁用持久性日志以确保数据文件保持有效和可恢复。
    enabled: true
processManagement:
  #启用在后台运行mongos或mongod进程的守护进程模式。
  fork: true
  #指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
  pidFilePath: "/mongodb/replica_sets/myrs_27017/log/mongod.pid"
net:
  #服务实例绑定所有IP,有副作用,副本集初始化的时候,节点名字会自动设置为本地域名,而不是ip
  #bindIpAll: true
  #服务实例绑定的IP
  bindIp: localhost,192.168.168.101
  #bindIp
  #绑定的端口
  port: 27017
replication:
  #副本集的名称
  replSetName: myrs

# 副本节点
systemLog:
  #MongoDB发送所有日志输出的目标指定为文件
  destination: file
  #mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
  path: "/mongodb/replica_sets/myrs_27018/log/mongod.log"
  #当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾。
  logAppend: true
storage:
  #mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod。
  dbPath: "/mongodb/replica_sets/myrs_27018/data/db"
  journal:
    #启用或禁用持久性日志以确保数据文件保持有效和可恢复。
    enabled: true
processManagement:
  #启用在后台运行mongos或mongod进程的守护进程模式。
  fork: true
  #指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
  pidFilePath: "/mongodb/replica_sets/myrs_27018/log/mongod.pid"
net:
  #服务实例绑定所有IP,有副作用,副本集初始化的时候,节点名字会自动设置为本地域名,而不是ip
  #bindIpAll: true
  #服务实例绑定的IP
  bindIp: localhost,192.168.168.101
  #bindIp
  #绑定的端口
  port: 27018
replication:
  #副本集的名称
  replSetName: myrs
  
# 仲裁节点  官方文档建议:不要在同时承载副本集的主成员或辅助成员的系统上运行仲裁服务器
systemLog:
  #MongoDB发送所有日志输出的目标指定为文件
  destination: file
  #mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
  path: "/mongodb/replica_sets/myrs_27019/log/mongod.log"
  #当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾。
  logAppend: true
storage:
  #mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod。
  dbPath: "/mongodb/replica_sets/myrs_27019/data/db"
  journal:
    #启用或禁用持久性日志以确保数据文件保持有效和可恢复。
    enabled: true
processManagement:
  #启用在后台运行mongos或mongod进程的守护进程模式。
  fork: true
  #指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
  pidFilePath: "/mongodb/replica_sets/myrs_27019/log/mongod.pid"
net:
  #服务实例绑定所有IP,有副作用,副本集初始化的时候,节点名字会自动设置为本地域名,而不是ip
  #bindIpAll: true
  #服务实例绑定的IP
  bindIp: localhost,192.168.168.101
  #bindIp
  #绑定的端口
  port: 27019
replication:
  #副本集的名称
  replSetName: myrs

3、分片集群

在这里插入图片描述

a.第一套副本集:一主一副本一仲裁
mkdir -p /mongodb/sharded_cluster/myshardrs01_27018/log
mkdir -p /mongodb/sharded_cluster/myshardrs01_27018/data/db
mkdir -p /mongodb/sharded_cluster/myshardrs01_27118/log
mkdir -p /mongodb/sharded_cluster/myshardrs01_27118/data/db
mkdir -p /mongodb/sharded_cluster/myshardrs01_27218/log
mkdir -p /mongodb/sharded_cluster/myshardrs01_27218/data/db

vim /mongodb/sharded_cluster/myshardrs01_27018/mongod.conf

#myshardrs01_27018
systemLog:
  #MongoDB发送所有日志输出的目标指定为文件
  destination: file
  #mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
  path: "/mongodb/sharded_cluster/myshardrs01_27018/log/mongod.log"
  #当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾。
  logAppend: true
storage:
  #mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod。
  dbPath: "/mongodb/sharded_cluster/myshardrs01_27018/data/db"
  journal:
    #启用或禁用持久性日志以确保数据文件保持有效和可恢复。
    enabled: true
processManagement:
  #启用在后台运行mongos或mongod进程的守护进程模式。
  fork: true
  #指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
  pidFilePath: "/mongodb/sharded_cluster/myshardrs01_27018/log/mongod.pid"
net:
  #服务实例绑定所有IP,有副作用,副本集初始化的时候,节点名字会自动设置为本地域名,而不是ip
  #bindIpAll: true
  #服务实例绑定的IP
  bindIp: localhost,192.168.168.101
  #bindIp
  #绑定的端口
  port: 27018
replication:
  #副本集的名称
  replSetName: myshardrs01
sharding:
  #分片角色
  clusterRole: shardsvr

vim /mongodb/sharded_cluster/myshardrs01_27118/mongod.conf

#myshardrs01_27118
systemLog:
  #MongoDB发送所有日志输出的目标指定为文件
  destination: file
#mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
  path: "/mongodb/sharded_cluster/myshardrs01_27118/log/mongod.log"
  #当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾。
  logAppend: true
storage:
  #mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod。
  dbPath: "/mongodb/sharded_cluster/myshardrs01_27118/data/db"
  journal:
    #启用或禁用持久性日志以确保数据文件保持有效和可恢复。
    enabled: true
processManagement:
  #启用在后台运行mongos或mongod进程的守护进程模式。
  fork: true
  #指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
  pidFilePath: "/mongodb/sharded_cluster/myshardrs01_27118/log/mongod.pid"
net:
  #服务实例绑定所有IP
  #bindIpAll: true
  #服务实例绑定的IP
  bindIp: localhost,192.168.168.101
  #绑定的端口
  port: 27118
replication:
  replSetName: myshardrs01
sharding:
  clusterRole: shardsvr

vim /mongodb/sharded_cluster/myshardrs01_27218/mongod.conf

#myshardrs01_27218
systemLog:
  #MongoDB发送所有日志输出的目标指定为文件
  destination: file
  #mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
  path: "/mongodb/sharded_cluster/myshardrs01_27218/log/mongod.log"
  #当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾。
  logAppend: true
storage:
  #mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod。
  dbPath: "/mongodb/sharded_cluster/myshardrs01_27218/data/db"
  journal:
    #启用或禁用持久性日志以确保数据文件保持有效和可恢复。
    enabled: true
processManagement:
  #启用在后台运行mongos或mongod进程的守护进程模式。
  fork: true
  #指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
  pidFilePath: "/mongodb/sharded_cluster/myshardrs01_27218/log/mongod.pid"
net:
  #服务实例绑定的IP
  bindIp: localhost,192.168.168.101
  #绑定的端口
  port: 27218
replication:
  replSetName: myshardrs01
sharding:
  clusterRole: shardsvr
启动第一套副本集
mongod -f /mongodb/sharded_cluster/myshardrs01_27018/mongod.conf
mongod -f /mongodb/sharded_cluster/myshardrs01_27118/mongod.conf
mongod -f /mongodb/sharded_cluster/myshardrs01_27218/mongod.conf

ps -ef |grep mongod

mongo --host 192.168.168.101 --port 27018
rs.initiate()
rs.conf()
rs.add("192.168.168.101:27118")
rs.addArb("192.168.168.101:27218")
rs.conf()
b.第二套副本集:一主一副本一仲裁
mkdir -p /mongodb/sharded_cluster/myshardrs02_27318/log
mkdir -p /mongodb/sharded_cluster/myshardrs02_27318/data/db
mkdir -p /mongodb/sharded_cluster/myshardrs02_27418/log
mkdir -p /mongodb/sharded_cluster/myshardrs02_27418/data/db
mkdir -p /mongodb/sharded_cluster/myshardrs02_27518/log
mkdir -p /mongodb/sharded_cluster/myshardrs02_27518/data/db

vim /mongodb/sharded_cluster/myshardrs02_27318/mongod.conf

#myshardrs02_27318
systemLog:
  #MongoDB发送所有日志输出的目标指定为文件
  destination: file
  #mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
  path: "/mongodb/sharded_cluster/myshardrs02_27318/log/mongod.log"
  #当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾。
  logAppend: true
storage:
  #mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod。
  dbPath: "/mongodb/sharded_cluster/myshardrs02_27318/data/db"
  journal:
    #启用或禁用持久性日志以确保数据文件保持有效和可恢复。
    enabled: true
processManagement:
  #启用在后台运行mongos或mongod进程的守护进程模式。
  fork: true
  #指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
  pidFilePath: "/mongodb/sharded_cluster/myshardrs02_27318/log/mongod.pid"
net:
  #服务实例绑定的IP
  bindIp: localhost,192.168.168.101
  #绑定的端口
  port: 27318
replication:
  replSetName: myshardrs02
sharding:
  clusterRole: shardsvr
  
vim /mongodb/sharded_cluster/myshardrs02_27418/mongod.conf

#myshardrs02_27418
systemLog:
  #MongoDB发送所有日志输出的目标指定为文件
  destination: file
  #mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
  path: "/mongodb/sharded_cluster/myshardrs02_27418/log/mongod.log"
  #当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾。
  logAppend: true
storage:
  #mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod。
  dbPath: "/mongodb/sharded_cluster/myshardrs02_27418/data/db"
  journal:
    #启用或禁用持久性日志以确保数据文件保持有效和可恢复。
    enabled: true
processManagement:
  #启用在后台运行mongos或mongod进程的守护进程模式。
  fork: true
  #指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
  pidFilePath: "/mongodb/sharded_cluster/myshardrs02_27418/log/mongod.pid"
net:
  #服务实例绑定所有IP
  #bindIpAll: true
  #服务实例绑定的IP
  bindIp: localhost,192.168.168.101
  #绑定的端口
  port: 27418
replication:
  replSetName: myshardrs02
sharding:
  clusterRole: shardsvr

vim /mongodb/sharded_cluster/myshardrs02_27518/mongod.conf

#myshardrs02_27518
systemLog:
  #MongoDB发送所有日志输出的目标指定为文件
  destination: file
  #mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
  path: "/mongodb/sharded_cluster/myshardrs02_27518/log/mongod.log"
  #当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾。
  logAppend: true
storage:
  #mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod。
  dbPath: "/mongodb/sharded_cluster/myshardrs02_27518/data/db"
  journal:
    #启用或禁用持久性日志以确保数据文件保持有效和可恢复。
    enabled: true
processManagement:
  #启用在后台运行mongos或mongod进程的守护进程模式。
  fork: true
  #指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
  pidFilePath: "/mongodb/sharded_cluster/myshardrs02_27518/log/mongod.pid"
net:
  #服务实例绑定所有IP
  #bindIpAll: true
  #服务实例绑定的IP
  bindIp: localhost,192.168.168.101
  #绑定的端口
  port: 27518
replication:
  replSetName: myshardrs02
sharding:
  clusterRole: shardsvr
启动第二套副本集
mongod -f /mongodb/sharded_cluster/myshardrs02_27318/mongod.conf
mongod -f /mongodb/sharded_cluster/myshardrs02_27418/mongod.conf
mongod -f /mongodb/sharded_cluster/myshardrs02_27518/mongod.conf

ps -ef |grep mongod

mongo --host 192.168.168.101 --port 27318
rs.initiate()
rs.conf()
rs.add("192.168.168.101:27418")
rs.addArb("192.168.168.101:27518")
rs.conf()
rs.status()
c.第三套副本集:配置节点副本集
mkdir -p /mongodb/sharded_cluster/myconfigrs_27019/log
mkdir -p /mongodb/sharded_cluster/myconfigrs_27019/data/db
mkdir -p /mongodb/sharded_cluster/myconfigrs_27119/log
mkdir -p /mongodb/sharded_cluster/myconfigrs_27119/data/db
mkdir -p /mongodb/sharded_cluster/myconfigrs_27219/log
mkdir -p /mongodb/sharded_cluster/myconfigrs_27219/data/db

vim /mongodb/sharded_cluster/myconfigrs_27019/mongod.conf

# myconfigrs_27019
systemLog:
  #MongoDB发送所有日志输出的目标指定为文件
  destination: file
  #mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
  path: "/mongodb/sharded_cluster/myconfigrs_27019/log/mongod.log"
  #当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾。
  logAppend: true
storage:
  #mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod。
  dbPath: "/mongodb/sharded_cluster/myconfigrs_27019/data/db"
  journal:
    #启用或禁用持久性日志以确保数据文件保持有效和可恢复。
    enabled: true
processManagement:
  #启用在后台运行mongos或mongod进程的守护进程模式。
  fork: true
  #指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
  pidFilePath: "/mongodb/sharded_cluster/myconfigrs_27019/log/mongod.pid"
net:
  #服务实例绑定所有IP
  #bindIpAll: true
  #服务实例绑定的IP
  bindIp: localhost,192.168.168.101
  #绑定的端口
  port: 27019
replication:
  replSetName: myconfigrs
sharding:
  clusterRole: configsvr

vim /mongodb/sharded_cluster/myconfigrs_27119/mongod.conf

#myconfigrs_27119
systemLog:
  #MongoDB发送所有日志输出的目标指定为文件
  destination: file
  #mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
  path: "/mongodb/sharded_cluster/myconfigrs_27119/log/mongod.log"
  #当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾。
  logAppend: true
storage:
  #mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod。
  dbPath: "/mongodb/sharded_cluster/myconfigrs_27119/data/db"
  journal:
    #启用或禁用持久性日志以确保数据文件保持有效和可恢复。
    enabled: true
processManagement:
  #启用在后台运行mongos或mongod进程的守护进程模式。
  fork: true
  #指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
  pidFilePath: "/mongodb/sharded_cluster/myconfigrs_27119/log/mongod.pid"
net:
  #服务实例绑定所有IP
  #bindIpAll: true
  #服务实例绑定的IP
  bindIp: localhost,192.168.168.101
  #绑定的端口
  port: 27119
replication:
  replSetName: myconfigrs
sharding:
  clusterRole: configsvr
 
vim /mongodb/sharded_cluster/myconfigrs_27219/mongod.conf
 
# myconfigrs_27219
systemLog:
  #MongoDB发送所有日志输出的目标指定为文件
  destination: file
  #mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
  path: "/mongodb/sharded_cluster/myconfigrs_27219/log/mongod.log"
  #当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾。
  logAppend: true
storage:
  #mongod实例存储其数据的目录。storage.dbPath设置仅适用于mongod。
  dbPath: "/mongodb/sharded_cluster/myconfigrs_27219/data/db"
  journal:
    #启用或禁用持久性日志以确保数据文件保持有效和可恢复。
    enabled: true
processManagement:
  #启用在后台运行mongos或mongod进程的守护进程模式。
  fork: true
  #指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
  pidFilePath: "/mongodb/sharded_cluster/myconfigrs_27219/log/mongod.pid"
net:
  #服务实例绑定所有IP
  #bindIpAll: true
  #服务实例绑定的IP
  bindIp: localhost,192.168.168.101
  #绑定的端口
  port: 27219
replication:
  replSetName: myconfigrs
sharding:
  clusterRole: configsvr
启动第三套副本集
mongod -f /mongodb/sharded_cluster/myconfigrs_27019/mongod.conf
mongod -f /mongodb/sharded_cluster/myconfigrs_27119/mongod.conf
mongod -f /mongodb/sharded_cluster/myconfigrs_27219/mongod.conf

ps -ef |grep mongod

mongo --host 192.168.168.101 --port 27019
rs.initiate()
rs.conf()
rs.add("192.168.168.101:27119")
rs.add("192.168.168.101:27219")
rs.conf()
rs.status()
d.第一个路由节点
mkdir -p /mongodb/sharded_cluster/mymongos_27017/log

vim /mongodb/sharded_cluster/mymongos_27017/mongos.conf

# mongos.conf
systemLog:
  #MongoDB发送所有日志输出的目标指定为文件
  destination: file
  #mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
  path: "/mongodb/sharded_cluster/mymongos_27017/log/mongod.log"
  #当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾。
  logAppend: true
processManagement:
  #启用在后台运行mongos或mongod进程的守护进程模式。
  fork: true
  #指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
  pidFilePath: /mongodb/sharded_cluster/mymongos_27017/log/mongod.pid"
net:
  #服务实例绑定所有IP,有副作用,副本集初始化的时候,节点名字会自动设置为本地域名,而不是ip
  #bindIpAll: true
  #服务实例绑定的IP
  bindIp: localhost,192.168.168.101
  #bindIp
  #绑定的端口
  port: 27017
sharding:
  #指定配置节点副本集
  configDB: myconfigrs/192.168.168.101:27019,192.168.168.101:27119,192.168.168.101:27219
  
mongos -f /mongodb/sharded_cluster/mymongos_27017/mongos.conf

mongo --host 192.168.168.101 --port 27017

# 添加第一套分片
sh.addShard("myshardrs01/192.168.168.101:27018,192.168.168.101:27118,192.168.168.101:27218")

sh.status()

# 添加第二套分片
sh.addShard("myshardrs02/192.168.168.101:27318,192.168.168.101:27418,192.168.168.101:27518")

sh.status()

# 如何移除分片
use admin
db.runCommand( { removeShard: "myshardrs02" } )

#开启分片功能
sh.enableSharding("articledb")

#集合分片【基于范围的分片方式与基于哈希的分片方式性能对比】
#分片规则一:哈希策略
sh.shardCollection("articledb.comment",{"nickname":"hashed"})
#分片规则二:范围策略
sh.shardCollection("articledb.author",{"age":1})

#显示集群的详细信息
db.printShardingStatus()
#查看均衡器是否工作【需要重新均衡时系统会自动启动】
sh.isBalancerRunning()
#查看当前 Balancer状态
sh.getBalancerState()
分片测试
# 测试一(哈希规则):登录mongs后,向comment循环插入1000条数据做测试
use articledb
# js的语法,因为mongo的shell是一个JavaScript的shell
for(var i=1;i<=1000;i++){db.comment.insert({_id:i+"",nickname:"BoBo"+i})}

db.comment.count()

# 登录第一个分片集
mongo --host 192.168.168.101 --port 27018
# 计算有多少个记录
use articledb
db.comment.count()

# 登录第二个分片集
mongo --host 192.168.168.101 --port 27318
# 计算有多少个记录
use articledb
db.comment.count()
#结论:可以看到, 1000条数据近似均匀的分布到了2个shard上。是根据片键的哈希值分配的。这种分配方式非常易于水平扩展:一旦数据存储需要更大空间,可以直接再增加分片即可,同时提升了性能。

#测试二(范围规则):登录mongs后,向comment循环插入1000条数据做测试
use articledb
#js的语法【下面这条命令执行时间比较长】
for(var i=1;i<=20000;i++){db.author.save({"name":"BoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBoBo"+i,"age":NumberInt(i%120)})}
db.author.count()

# 登录第一个分片集
mongo --host 192.168.168.101 --port 27018
# 计算有多少个记录
use articledb
db.author.count()

# 登录第二个分片集
mongo --host 192.168.168.101 --port 27318
# 计算有多少个记录
use articledb
db.author.count()
#如果查看状态发现没有分片,则可能是由于以下原因造成:
#1)系统繁忙,正在分片中。
#2)数据块(chunk)没有填满,默认的数据块尺寸(chunksize)是64M,填满后才会考虑向其他片的数据块填充数据,因此,为了测试,可以将其改小,这里改为1M,操作如下:
use config
db.settings.save( { _id:"chunksize", value: 1 } )
#要先改小,再设置分片。为了测试,可以先删除集合,重新建立集合的分片策略,再插入数据测试即可。
db.settings.save( { _id:"chunksize", value: 64 } )
e.第二个路由节点
mkdir -p /mongodb/sharded_cluster/mymongos_27117/log

vi /mongodb/sharded_cluster/mymongos_27117/mongos.conf

systemLog:
  #MongoDB发送所有日志输出的目标指定为文件
  destination: file
  #mongod或mongos应向其发送所有诊断日志记录信息的日志文件的路径
  path: "/mongodb/sharded_cluster/mymongos_27117/log/mongod.log"
  #当mongos或mongod实例重新启动时,mongos或mongod会将新条目附加到现有日志文件的末尾。
  logAppend: true
processManagement:
  #启用在后台运行mongos或mongod进程的守护进程模式。
  fork: true
  #指定用于保存mongos或mongod进程的进程ID的文件位置,其中mongos或mongod将写入其PID
  pidFilePath: /mongodb/sharded_cluster/mymongos_27117/log/mongod.pid"
net:
  #服务实例绑定所有IP,有副作用,副本集初始化的时候,节点名字会自动设置为本地域名,而不是ip
  #bindIpAll: true
  #服务实例绑定的IP
  bindIp: localhost,192.168.168.101
  #bindIp
  #绑定的端口
  port: 27117
sharding:
  #指定配置节点副本集
  configDB: myconfigrs/192.168.168.101:27019,192.168.168.101:27119,192.168.168.101:27219

mongos -f /mongodb/sharded_cluster/mymongos_27117/mongos.conf

mongo --host 192.168.168.101 --port 27117
#第二个路由无需配置,因为分片配置都保存到了配置服务器
重置分片集群

如果在搭建分片的时候有操作失败或配置有问题,需要重新来过的,可以进行如下操作:

#第一步:查询出所有的测试服务节点的进程
ps -ef |grep mongo

kill -2 进程编号

#第二步:清除所有节点【十一个节点】的数据
rm -rf /mongodb/sharded_cluster/myshardrs01_27018/data/db/*
rm -rf /mongodb/sharded_cluster/myshardrs01_27118/data/db/*
rm -rf /mongodb/sharded_cluster/myshardrs01_27218/data/db/*
rm -rf /mongodb/sharded_cluster/myshardrs02_27318/data/db/*
rm -rf /mongodb/sharded_cluster/myshardrs02_27418/data/db/*
rm -rf /mongodb/sharded_cluster/myshardrs02_27518/data/db/*
rm -rf /mongodb/sharded_cluster/myconfigrs_27019/data/db/*
rm -rf /mongodb/sharded_cluster/myconfigrs_27119/data/db/*
rm -rf /mongodb/sharded_cluster/myconfigrs_27219/data/db/*
# rm -rf /mongodb/sharded_cluster/mymongos_27017/data/db/*.*
# rm -rf /mongodb/sharded_cluster/mymongos_27117/data/db/*.*

#第三步:查看或修改有问题的配置
#第四步:依次启动所有节点,不包括路由节点
mongod -f /mongodb/sharded_cluster/myshardrs01_27018/mongod.conf
mongod -f /mongodb/sharded_cluster/myshardrs01_27118/mongod.conf
mongod -f /mongodb/sharded_cluster/myshardrs01_27218/mongod.conf

#启动第一套副本集
mongo --host 192.168.168.101 --port 27018
rs.initiate()
rs.conf()
rs.add("192.168.168.101:27118")
rs.addArb("192.168.168.101:27218")
rs.conf()

mongod -f /mongodb/sharded_cluster/myshardrs02_27318/mongod.conf
mongod -f /mongodb/sharded_cluster/myshardrs02_27418/mongod.conf
mongod -f /mongodb/sharded_cluster/myshardrs02_27518/mongod.conf

#启动第二套副本集
mongo --host 192.168.168.101 --port 27318
rs.initiate()
rs.conf()
rs.add("192.168.168.101:27418")
rs.addArb("192.168.168.101:27518")
rs.conf()
rs.status()

mongod -f /mongodb/sharded_cluster/myconfigrs_27019/mongod.conf
mongod -f /mongodb/sharded_cluster/myconfigrs_27119/mongod.conf
mongod -f /mongodb/sharded_cluster/myconfigrs_27219/mongod.conf

#启动第三套副本集
mongo --host 192.168.168.101 --port 27019
rs.initiate()
rs.conf()
rs.add("192.168.168.101:27119")
rs.add("192.168.168.101:27219")
rs.conf()
rs.status()

#第五步:对两个数据分片副本集和一个配置副本集进行初始化和相关配置
#第六步:检查路由mongos的配置,并启动mongos
mongos -f /mongodb/sharded_cluster/mymongos_27017/mongos.conf

#启动第一个路由
mongo --host 192.168.168.101 --port 27017
# 添加第一套分片
sh.addShard("myshardrs01/192.168.168.101:27018,192.168.168.101:27118,192.168.168.101:27218")
sh.status()
# 添加第二套分片
sh.addShard("myshardrs02/192.168.168.101:27318,192.168.168.101:27418,192.168.168.101:27518")
sh.status()

#启动第二个路由【自动同步分片】
mongos -f /mongodb/sharded_cluster/mymongos_27117/mongos.conf
#第七步:mongo登录mongos,在其上进行相关操作。

七、MongoDB 数据安全

1、为什么需要安全

默认情况下,MongoDB实例启动运行时是没有启用用户访问权限控制的,也就是说,在实例本机服务器上都可以随意连接到实例进行各种操作,MongoDB不会对连接客户端进行用户验证,这是非常危险的。

因此官网建议:

  • 使用新的端口,默认的27017端口如果一旦知道了ip就能连接上,不安全。
  • 置mongodb的网络环境,最好将mongodb部署到公司服务器内网,这样外网是访问不到的。公司内部访问使用vpn等。
  • 开启安全认证。认证要同时设置服务器之间的内部认证方式,同时要设置客户端连接到集群的账号密码认证方式。

为了强制开启用户访问控制(用户验证),则需要在MongoDB实例启动时使用选项 --auth 或在指定启动配置文件中添加选项 auth=true。

2、基本概念

认证(Authentication):确认这个人是谁——认证管理:通过访问控制和授权管理,防止非授权访问。

授权(Authorization):确认这个该干什么——安全加固:通过操作系统和网络加固,保护数据库安全。

审计(Authentication):记录系统发生了什么——操作审计:通过操作记录,提供完整操作审计与分析。

加密(Authorization):只有授权用户才能理解——数据加密:通过数据存储和传输加密,保证数据安全。

RBAC:MongoDB使用的是基于角色的访问控制(Role-Based Access Control,RBAC)来管理用户对实例的访问。通过对用户授予一个或多个角色来控制用户访问数据库资源的权限和数据库操作的权限,在对用户分配角色之前,用户无法访问实例。

  • 传输加密——启用TLS/SSL加密传输保证网络传输安全。
# 前提:mongodb.pem文件和客户端文件client.pem文件通过openssl工具生成;mongodb需要开启ssl模式
mongo --ssl --sslCAFile ./mongodb.pem --sslPEMKeyFile ./client.pem --sslPEMKeyPassword yourpassword
  • 存储加密
mongod --enableEncryption --encryptionKeyFile mongodb-keyfile
  • 操作审计
# 操作审计支持CRUD、数据schema (DDL)、部署架构、用户认证与授权等
mongod --dbpath data/db --auditDestination file --auditFormat JSON --auditPath data/db/auditLog.json
  • 安全加固
# 关闭http接口
net.http.enabled=False 
# 关闭运行json访问http端口
net.http.JSONenabled=False
# 管理Rest API接口
net.http.RESTInterfaceEnabled=False
# 监听的IP地址
net.bindIp=127.0.0.1,192.168.1.21
# 使用普通用户,而非root运行MongoDB
# 修改默认监听端口:27017

3、实操——单节点

# 用户权限的CRUD
use admin
db.createUser({user:"myroot",pwd:"123456",roles:["root"]})
db.createUser({user:"myadmin",pwd:"123456",roles:[{role:"userAdminAnyDatabase",db:"admin"}]})
db.system.users.find()
db.changeUserPassword("myroot", "12345678")
db.dropUser("myadmin")

## 服务端
#有两种方式开启权限认证启动服务:一种是参数方式,一种是配置文件方式。
#方式一、在启动时指定参数 -- auth 
mongod -f /etc/mongod.conf --auth

#方式一二、配置文件方式
vim /etc/mongod.conf
security:
  #开启授权认证
  authorization: enabled

## 客户端
#开启了认证的情况下的客户端登录
#有两种认证方式,一种是先登录,在mongo shell中认证;一种是登录时直接认证
#方式一、先登录,在mongo shell中认证
mongo --host 192.168.168.101 --port 27017
use admin
db.auth("myroot","123456")

#方式二、登录时直接认证
mongo --host 192.168.168.101 --port 27017 --authenticationDatabase admin -umyroot -p123456
mongo --host 192.168.168.101 --port 27017 --authenticationDatabase articledb -u bobo -p 123456

3、实操——副本集

注意两点:

(1)副本集和共享集群的各个节点成员之间使用内部身份验证,可以使用密钥文件或x.509证书。密钥文件比较简单,本文使用密钥文件,官方推荐如果是测试环境可以使用密钥文件,但是正式环境,官方推荐x.509证书。原理就是,集群中每一个实例彼此连接的时候都检验彼此使用的证书的内容是否相同。只有证书相同的实例彼此才可以访问。

(2)使用客户端连接到mongodb集群时,开启访问授权。对于集群外部的访问。如通过可视化客户端,或者通过代码连接的时候,需要开启授权。

#第一步:添加安全认证用户
#只需要在主节点上添加用户,副本集会自动同步。
use admin
db.createUser({user:"myroot",pwd:"123456",roles:["root"]})

#第二步:创建副本集认证的key文件
#生成一个key文件到当前文件夹中。
openssl rand -base64 90 -out ./mongo.keyfile
chmod 400 ./mongo.keyfile
ll mongo.keyfile
#所有副本集节点都必须要用同一份keyfile,一般是在一台机器上生成,然后拷贝到其他机器上,且必须有读的权限,否则将来会报错: permissions on /mongodb/replica_sets/myrs_27017/mongo.keyfile are too open
cp mongo.keyfile /mongodb/replica_sets/myrs_27017
cp mongo.keyfile /mongodb/replica_sets/myrs_27018
cp mongo.keyfile /mongodb/replica_sets/myrs_27019

#第三步:分别编辑几个服务的mongod.conf文件,添加相关内容
vim /mongodb/replica_sets/myrs_2701x/mongod.conf
security:
  #KeyFile鉴权文件
  keyFile: /mongodb/replica_sets/myrs_2701X/mongo.keyfile
  #开启认证方式运行
  authorization: enabled
  
#第四步: 重新启动副本集群【先通过kill -2关闭服务】
mongod -f /mongodb/replica_sets/myrs_27017/mongod.conf
mongod -f /mongodb/replica_sets/myrs_27018/mongod.conf
mongod -f /mongodb/replica_sets/myrs_27019/mongod.conf

#第五步:在主节点上添加普通账号
#先用管理员账号登录
#切换到admin库
use admin
#管理员账号认证
db.auth("myroot","123456")
#切换到要认证的库
use articledb
#添加普通用户
db.createUser({user: "bobo", pwd: "123456", roles: ["readWrite"]})

#重新连接,使用普通用户 bobo重新登录,查看数据。也要使用rs.status()命令查看副本集是否健康。

3、实操——分片集群

分片集群环境下的安全认证和副本集环境下基本上一样。
但分片集群的服务器环境和架构较为复杂,建议在搭建分片集群的时候,直接加入安全认证和服务器间的鉴权,如果之前有数据,可先将之前的数据备份出来,再还原回去。

#第一步:关闭分片服务器副本集中的服务,建议依次关闭仲裁节点、副本节点、主节点。

#第二步:创建副本集认证的key文件
#生成一个key文件到当前文件夹中。
openssl rand -base64 90 -out ./mongo.keyfile
chmod 400 ./mongo.keyfile
ll mongo.keyfile
#将该文件分别拷贝到多个目录
echo '/mongodb/sharded_cluster/myshardrs01_27018/mongo.keyfile /mongodb/sharded_cluster/myshardrs01_27118/mongo.keyfile /mongodb/sharded_cluster/myshardrs01_27218/mongo.keyfile /mongodb/sharded_cluster/myshardrs02_27318/mongo.keyfile /mongodb/sharded_cluster/myshardrs02_27418/mongo.keyfile /mongodb/sharded_cluster/myshardrs02_27518/mongo.keyfile /mongodb/sharded_cluster/myconfigrs_27019/mongo.keyfile /mongodb/sharded_cluster/myconfigrs_27119/mongo.keyfile /mongodb/sharded_cluster/myconfigrs_27219/mongo.keyfile /mongodb/sharded_cluster/mymongos_27017/mongo.keyfile /mongodb/sharded_cluster/mymongos_27117/mongo.keyfile' | xargs -n 1 cp /root/mongo.keyfile

#第三步:分别编辑几个服务【共十一个】的mongod.conf文件,添加相关内容
vim /mongodb/sharded_cluster/myshardrs01_2721X/mongod.conf
security:
  #KeyFile鉴权文件
  keyFile: /mongodb/sharded_cluster/myshardrs01_2721X/mongod.conf
  #开启认证方式运行
  #authorization: enabled
#mongos 比mongod少了authorization:enabled的配置。原因是,副本集加分片的安全认证需要配置两方面的,副本集各个节点之间使用内部身份验证,用于内部各个mongo实例的通信,只有相同keyfile才能相互访问。所以都要开启 keyFile:/mongodb/sharded_cluster/mymongos_27117/mongo.keyfile 。然而对于所有的mongod,才是真正的保存数据的分片。mongos只做路由,不保存数据。所以所有的mongod开启访问数据的授权authorization:enabled。这样用户只有账号密码正确才能访问到数据。

#第四步: 重新启动分片集群
#这里有个非常特别的情况,就是启动顺序。先启动配置节点,再启动分片节点,最后启动路由节点。如果先启动分片节点,会卡住,提示:about to fork child process, waiting until server is ready for connections。这也许是个 bug,原因未知。
mongos -f /mongodb/sharded_cluster/myconfigrs_27019/mongod.conf
mongos -f /mongodb/sharded_cluster/myconfigrs_27119/mongod.conf
mongos -f /mongodb/sharded_cluster/myconfigrs_27219/mongod.conf

mongos -f /mongodb/sharded_cluster/myshardrs01_27018/mongod.conf
mongos -f /mongodb/sharded_cluster/myshardrs01_27118/mongod.conf
mongos -f /mongodb/sharded_cluster/myshardrs01_27218/mongod.conf

mongos -f /mongodb/sharded_cluster/myshardrs02_27318/mongod.conf
mongos -f /mongodb/sharded_cluster/myshardrs02_27418/mongod.conf
mongos -f /mongodb/sharded_cluster/myshardrs02_27518/mongod.conf

mongos -f /mongodb/sharded_cluster/mymongos_27017/mongos.conf
mongos -f /mongodb/sharded_cluster/mymongos_27117/mongos.conf

#第五步:在分片路由节点上创建帐号和认证
#创建一个管理员账号
mongo --port 27017
use admin
db.createUser({user:"myroot",pwd:"123456",roles:["root"]})
#创建一个普通账号
use admin
db.auth("myroot","123456")
use articledb
db.createUser({user: "bobo", pwd: "123456", roles: [{ role: "readWrite",db: "articledb" }]})
db.auth("bobo","123456")
#通过mongos添加的账号信息,只会保存到配置节点的服务中,具体的数据节点不保存账号信息,因此,分片中的账号信息不涉及到同步问题。

#分别使用管理员账号和普通账号登录测试:退出连接,重新连接服务,使用普通权限帐号访问数据

八、MongoDB 性能优化

1、操作系统优化

内核和文件系统
关闭数据库文件的access-time
内核网络参数调整
IO调度策略
提高默认文件描述符和进程/线程数限制
Dirty Ratio
Swappiness
HugePages
NUMA
readahead设置
NTP时间服务器

2、监控工具优化

  • db.serverStatus()

监控实例的运行状态(内存使用、锁、用户连接等信息)。

通过持续观察这个状态,可以看到系统运行的趋势。

# 一些重要信息
db.serverStatus()
db.serverStatus().connections
db.serverStatus().locks
db.serverStatus().activeClients
db.serverStatus().opcounters
db.serverStatus().opcountersRepl
db.serverStatus().storageEngine
db.serverStatus().mem

  • db.stats()

查看当前数据库的统计信息。

db.stats()
{
        "db" : "test",
        "collections" : 1,
        "views" : 0,
        "objects" : 100,
        "avgObjSize" : 33.22,
        "dataSize" : 3322,
        "storageSize" : 32768,
        "numExtents" : 0,
        "indexes" : 1,
        "indexSize" : 32768,
        "fsUsedSize" : 643317760,
        "fsTotalSize" : 32196526080,
        "ok" : 1,
        "operationTime" : Timestamp(1647246974, 1),
        "$clusterTime" : {
                "clusterTime" : Timestamp(1647246974, 1),
                "signature" : {
                        "hash" : BinData(0,"pqfevp7IwKB0jYF1a8R3cPNQwJg="),
                        "keyId" : NumberLong("7073430430610882562")
                }
        }
}
  • db.collection.status()

查看集合的当前状态。所有参考均以字节为单位。

  • db.currentOp()

查看数据库当前执行什么操作。用于查看长时间运行进程。通过(执行时长、操作、锁、等待锁时长)等条件过滤。
如果发现一个操作太长,把数据库卡死的话,可以用这个命令杀死他 :

db.killOp(608605) 
  • mongostat

实时数据库状态,读写、加锁、索引命中、缺页中断、读写等待队列等情况。
每秒刷新一次状态值,并能提供良好的可读性,通过这些参数可以观察到MongoDB系统整体性能情况。

mongostat --host 10.214.11.59:32409,10.214.11.59:31822,10.214.11.59:31245  -u6229df1e0f921362cbb1b28f -pGotapdatad8! --authenticationDatabase admin

在这里插入图片描述

mongostat重要指标
command:语句操作,在压力测试过程需要注意是否均匀执行。
used% :内存使用比例,设置CacheSizeGB 只是大概进行限制 。一般占用到80%使用,超过之后有一个进程使用最近最少使用算法进行内存刷新。达到95%的时候,所有的进程会进行驱逐内存的操作。
dirty%:数据进入内存,但是还没有进入磁盘的比例 。特别是重写的系统需要注意,如果特别高的时候可能是磁盘IO不足。
res:实际占用内存。
conn:当前连接数。
qr|qw:客户端查询排队长度(读|写)。最好为0,如果有堆积,数据库处理慢。

qr|qw:客户端查询排队长度(读|写)。最好为0,如果有堆积,数据库处理慢。

  • mongotop

mongotop用来跟踪MongoDB的实例, 提供每个集合的统计数据。
默认情况下,mongotop每一秒刷新一次。

mongotop --host 10.214.11.59:32409,10.214.11.59:31822,10.214.11.59:31245  -u6229df1e0f921362cbb1b28f -pGotapdatad8! --authenticationDatabase admin

在这里插入图片描述

mongotop重要指标:
ns:数据库命名空间,后者结合了数据库名称和集合。
total:mongod在这个命令空间上花费的总时间。
read:在这个命令空间上mongod执行读操作花费的时间。
write:在这个命名空间上mongod进行写操作花费的时间。

3、数据库优化

  • 参数调整

1、连接数

MongoDB实例接受的默认最大并发连接数:1000000

net:
  maxIncomingConnections:1000

注:并发连接数不能大于操作系统最大文件句柄数。如果大于操作系统最大文件句柄数,则设置无效。

2、缓存大小的设置

默认情况下,将使用较大的以下两者之一:50% of (RAM - 1 GB)、256MB

  • 语句优化

优化查询语句(慢查询日志–分析执行计划)

详见 二、MongoDB索引

  • 索引优化

详见 二、MongoDB索引

1、Selectivity——过滤性

2、复合索引——索引顺序,由过滤性决定优先级

3、使用索引排序查询结果
在MongoDB中,排序操作可以通过根据索引中的排序检索文档来获取排序顺序。如果查询计划器无法从索引获取排序顺序,则会将结果排序到内存中。使用索引的排序操作通常比不使用索引的操作具有更好的性能。另外,不使用索引的排序操作会在使用32MB内存时中止。

九、MongoDB 备份与恢复

1、MongoDB冗余和备份

冗余≠备份。

冗余的目的:7*24h可用。故障发生无需干预。系统升级维护对用户透明。

备份的目的:灾难恢复。人工误操作。程序Bug损坏数据。

MongoDB的备份方式有三种:

  • mongodump/mongorestore

    逻辑备份。将集合导出成BSON文件,同时生成一份JSON格式的索引元数据信息。可指定导出的数据库(database)和集合(collection)。不包含索引(恢复时重建)。

    参数选择:
    –oplog 记录开始进行dump到dump结束之间所有的oplog
    –query 选择性导出。

  • 文件备份或磁盘快照

    物理备份。前提是必须开启journal日志。 为保证数据一致性,要求日志和数据文件在一个物理盘上。如果journal日志和数据文件在一个物理磁盘上,可以不加Lock。

    步骤:

    1. db.fsyncLock()
    2. 复制/快照
    3. db.fsyncUnlock()
  • Ops Manager自动备份

    通过数据同步重建一个镜像数据库。每6小时形成一次快照。oplog被保存48小时。

备份方法适合场景使用限制
mongodump适合少量数据3.0之前单进程不备份索引数据,恢复时重建;不适合大数据量,不支持增量;不适合分片环境
文件系统备份使用LVM;备份和恢复速度快;适用复制集环境需要确保数据无写入;不适合分片环境;不支持恢复到特定时间点
Ops Manager (企业版)适用于大数量 适用于复制集环境和分片环境 支持自动增量备份对少量数据的简单环境而言,部署复 杂

以上三种备份方式都适用于MongoDB副本集备份。

2、分片集备份

MongoDB分片集群备份实质是对分片集群中副本集的备份。

在分片集群下,不可能在一个时间点上得到一个完整集群状态的快照。当集群越来越大时,从备份恢复整个架构的几率越来越小的。 因此,对于分片集群的备份,只需独自备份config server和复制集。

在对分片集群进行备份与恢复操作前,要关闭balancer。备份时,直接连接分片集群的mongod而不是通过mongos。

对于比较小型的分片集群,可以直接通过mongodump连接到mongos进行备份,备份的文件将包含config服务器的元数据信息和实际数据。

  • 备份分片流程:
1)关闭balancer。
注意:连接到mongos而不是config server实例
sh.setBalancerState(false) 或
sh.stopBalancer() 或
use config
db.settings.update( { _id: "balancer" }, { $set : { stopped: true } } , true );
2)备份集群元数据。使用mongodump备份任意一台config server。可以直接连接任意一台的config mongod实例,也可以通过mongos连接。
# mongodump --db config
3)备份shard集群内各个replica set。可并行执行。
4)启用balancer。注意:连接到mongos而不是config server实例。
sh.setBalancerState(true) 或
sh.startBalancer() 或
use config
db.settings.update( { _id: "balancer" }, { $set : { stopped: false } } , true );

3、MongoDB的恢复

MongoDB的恢复方式也有三种:

  • 文档级恢复:mongorestore
    –oplogReplay 基于时间点备份的恢复
    –oplogLimit 恢复到任意状态
  • 文件系统恢复
    所有数据库文件
    选择特定的数据库和集合恢复
    Replay Oplog
  • Ops Manager自动恢复
    副本集可以恢复到任意状态。
    分片集群可以恢复到15min之前的状态。

十、MongoDB 运维工具——Ops Manager

前面的章节中,多次提到了Ops Manager。

官网:https://www.mongodb.com/products/ops-manager

1、Ops Manager用途

  • Ops Manager自动化

在这里插入图片描述

  • Ops Manager监控与备份

在这里插入图片描述

  • Ops Manager日常管理

在这里插入图片描述

2、Ops Manager工作原理

Agents承担了大部分工作:自动化、监控、备份。
OPS Manager负责小部分但最重要的工作:人机交互、信息的收集和分发。

在这里插入图片描述

3、Ops Manager使用

作为独立后台使用——中小企业
集成到企业现有系统——大企业

https://docs.opsmanager.mongodb.com/current/api/

https://github.com/zhangyaoxing/ops-api-demo

OPS Manager集成到LDAP
MongoDB集成到LDAP

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

进击的程序猿~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值