MongoDB-存储


MongoDB

  • 数据的存储
    • WiredTiger
      • 文档级并发?
      • 快照和检查点?
      • 日志
      • 压缩
      • 内存的使用
    • 内存存储引擎In-memory
      • 内存存储引擎
      • 并发性?
      • 内存使用
      • 持久性
      • 事务
      • 部署架构
        • 复制集
        • 分片集群
    • Journaling
      • Journaling and the WiredTiger Storage Engine
        • Journal Files
      • Journaling and the In-Memory Storage Engine
    • GridFS
      • chunk集合
      • files集合
      • GridFS操作
      • 使用python
  • 数据备份
  • 数据恢复

数据的存储 存储引擎是MongoDB中负责管理数据的的主要组件,负责管理数据在内存和磁盘上的存储方式。MongoDB支持多个存储引擎,因为不同的引擎可以更好地执行特定工作负载,允许用户选择最合适的存储引擎。
journal是一个日志,可以在硬件关机时帮助数据库恢复。 有几个可配置选项允许日志在性能和可靠性之间取得平衡,适用于特定用例。
GridFS是一种多功能存储系统,适用于处理大型文件,例如超过16 MB文档大小限制的文件。
  • WiredTiger存储引擎(默认)
    从MongoDB 3.2开始WiredTiger就是默认存储引擎。它非常适合大多数工作负载,建议用于新部署。 WiredTiger提供文档级并发模型,检查点和压缩等功能。在MongoDB Enterprise中,WiredTiger还支持静态加密
  • In-Memory内存存储引擎
    内存存储引擎可在MongoDB Enterprise中使用。它不是将文档存储在磁盘上,而是将它们保留在内存中,以实现更可预测的数据延迟
  • MMAPv1存储引擎(从MongoDB 4.0开始不推荐使用)
    MMAPv1是MongoDB 3.0及更早版本的默认存储引擎。
WiredTiger 自3.2版本后WiredTiger成为了默认的存储引擎,如果不用 --storageEngine 或者 storage.engine指定使用那种引擎,则MongoDB会自动选择创建文件时使用的存储引擎。
文档级并发?
WiredTiger使用文档级并发控制进行写操作。 因此,多个客户端可以同时修改集合的不同文档。
对于大多数读写操作,WiredTiger使用乐观并发控制。 WiredTiger仅在全局,数据库和集合级别使用锁。 当存储引擎检测到两个操作之间的冲突时,会发生写入冲突,导致MongoDB透明地重试该操作。
一些全局操作(通常是涉及多个数据库的短期操作)仍然需要全局“实例范围”锁定。 其他一些操作(例如删除集合)仍需要独占数据库锁。
快照和检查点?
WiredTiger使用MultiVersion并发控制(MVCC)。在操作开始时,WiredTiger为事务提供数据的时间点快照。快照显示内存中数据的一致视图。
写入磁盘时,WiredTiger会以相同的方式将快照中的所有数据写入磁盘,并覆盖所有数据文件。当前持久化的数据充当数据文件中的检查点。检查点确保数据文件一致,而且保留最后一个检查点,即检查点可以作为恢复点
版本3.6中更改:MongoDB配置WiredTiger以60秒的间隔创建检查点(即将快照数据写入磁盘)。
在写入新检查点期间,先前的检查点仍然有效。因此,即使MongoDB在写入新检查点时终止或遇到错误,在重新启动时,MongoDB也可以从最后一个有效检查点恢复。
当WiredTiger的元数据表以原子方式更新并引用新检查点时,新检查点变为可访问且永久的。一旦可以访问新检查点,WiredTiger就会从旧检查点释放页面。
使用WiredTiger,即使没有日志记录,MongoDB也可以从最后一个检查点恢复; 但是,要恢复在最后一个检查点之后所做的更改,请使用日记功能运行。
从MongoDB 4.0开始,已经不能为使用WiredTiger存储引擎的副本集成员指定--nojournal选项或storage.journal.enabled:false
日志
WiredTiger将预写事务日志与检查点结合使用以确保数据持久性。
WiredTiger日志会在检查点之间保留所有数据修改。如果MongoDB在2个检查点之间退出,它将使用日志来重复自上一个检查点以来修改的所有数据
使用snappy压缩库压缩WiredTiger日志。要指定备用压缩算法或不指定压缩,请使用storage.wiredTiger.engineConfig.journalCompressor设置。
注意
WiredTiger的最小日志记录大小为128字节。如果日志记录是128字节或更小,WiredTiger不会压缩该记录。
可以通过将storage.journal.enabled设置为false来禁用独立实例的日记功能,这可以减少维护日志的开销。对于独立实例,不使用日志意味着,当MongoDB意外退出时,将丢失最后一个检查点之前的所有数据修改。
压缩
通过WiredTiger,MongoDB支持对所有集合和索引进行压缩。压缩可以最大限度地减少对内存使用,代价时增加了CPU的使用。
默认情况下,WiredTiger对所有集合使用块压缩snappy压缩库,对所有索引使用前缀压缩
对于集合,还可以使用zlib进行块压缩。要指定备用压缩算法或不指定压缩,请使用storage.wiredTiger.collectionConfig.blockCompressor设置。
默认情况下,WiredTiger对所有集合使用Snappy块压缩,对所有索引使用前缀压缩。压缩默认值可在全局级别配置,也可在集合和索引创建期间基于每个集合和每个索引进行设置。
WiredTiger日志也默认进行压缩。
内存的使用
使用WiredTiger,MongoDB同时使用WiredTiger内部缓存和文件系统缓存。
从3.4开始,WiredTiger内部缓存默认使用较大的一个:
  • 50%(RAM - 1 GB),或
  • 256 MB。
例如,在总共4GB RAM的系统上,WiredTiger缓存将使用1.5GB的RAM(0.5 *(4 GB - 1 GB)= 1.5 GB)。相反,一个总共1.25 GB RAM的系统将为WiredTiger缓存分配256 MB,因为这超过总RAM减去1 GB的一半(0.5 *(1.25 GB - 1 GB)= 128 MB <256 MB) 。
WiredTiger内部缓存中的数据与在磁盘中的格式有不同的表示形式:
  • 文件系统缓存中的数据与在磁盘的数据格式相同,包括对数据文件进行任何压缩的好处。操作系统使用文件系统缓存来减少磁盘I/O.
  • 在WiredTiger内部缓存中加载的索引具有与在磁盘格式不同的数据表示,但仍可利用索引前缀压缩来减少RAM使用。索引前缀压缩从索引字段中删除公共前缀
  • WiredTiger内部缓存中的集合数据是未压缩的,并使用与磁盘格式不同的表示形式。块压缩可以节省大量的磁盘存储空间,但数据必须解压缩才能由服务器操作。
通过文件系统缓存,MongoDB自动使用WiredTiger缓存或其他进程未使用的所有可用内存。要调整WiredTiger内部缓存的大小,请参阅storage.wiredTiger.engineConfig.cacheSizeGB和–wiredTigerCacheSizeGB。避免将WiredTiger内部缓存大小增加到其默认值以上。
内存存储引擎In-memory 从MongoDB企业版3.2.6开始,内存存储引擎在64位的版本中正式发布。与其它元数据和诊断数据不同内存存储引擎不维护任何磁盘中的数据,包括配置数据,索引,用户认证信息等。
通过避免磁盘I/O,内存存储引擎提供了数据库操作更大的可预测延迟。
内存存储引擎
为了选择内存存储引擎,指定:
  • inMemory for the --storageEngine option, or the storage.engine setting if using a configuration file.
    –dbpath, or storage.dbPath if using a configuration file. Although the in-memory storage engine does not write data to the filesystem, it maintains in the --dbpath small metadata files and diagnostic data as well temporary files for building large indexes.
例如,从命令行:
mongod --storageEngine inMemory --dbpath
或者,如果使用 YAML configuration file format:
storage: engine: inMemory dbPath:

WARNING
在进程结束之后,内存存储引擎并不会持久化数据。
并发性?
内存存储引擎对写操作使用 document-level 并发控制。因此,多个客户端可以同时修改同一个集合的不同文档。
内存使用
内存存储引擎要求所有数据(如果 mongod 实例是复制集的一个部分,包括索引,日志等)必须能够容纳于 --inMemorySizeGB 命令行选项 或者 YAML configuration file 中 storage.inMemory.engineConfig.inMemorySizeGB 配置中指定的大小中。
默认地,内存存储引擎使用物理内存减去1GB后的50%内存。
如果一个写操作将会造成数据超出特定的内存大小,MongoDB返回以下错误:"WT_CACHE_FULL: operation would overflow cache"
在 YAML configuration file format 中设置:setting:storage.inMemory.engineConfig.inMemorySizeGB 来指定一个新的大小:
storage: engine: inMemory dbPath: inMemory: engineConfig: inMemorySizeGB:

或者使用命令行选项 --inMemorySizeGB:
mongod --storageEngine inMemory --dbpath --inMemorySizeGB

持久性
内存存储引擎是非持久化的,因此不会将数据写到一个持久化的存储中。也就是说,非持久化的数据包括应用数据以及系统数据,例如用户、权限、索引、复制集配置、分片集群配置等。因此, journal 的概念或者是等待数据变成 durable 并不适用于内存存储引擎。
事务
多文档事务不适用于具有使用内存存储引擎的成员的部署
部署架构
除了单机运行,使用内存存储引擎的 mongod 实例可以作为一个复制集或者分片集群的一个部分运行。
复制集 可以将使用内存存储引擎的 mongod 实例作为复制集的一个部分进行部署。例如,作为一个三成员复制集的一个部分,您可以有:
  • 两个运行着内存存储引擎的 mongod 实例。
  • 一个运行着 WiredTiger 存储引擎的 mongod 实例。将WiredTiger成员作为一个隐藏的成员(例如 hidden: true and priority: 0 )。
通过使用这种部署模型,只有运行着内存存储引擎的 mongod 实例可能变成主节点。客户端只会连接到内存存储引擎的 mongod 实例。即使两台运行着内存存储引擎的 mongod 实例同时崩溃重启,它们也可以从运行着WiredTiger的成员中进行同步。运行着WiredTiger的隐藏 mongod 实例将数据持久化到磁盘,包括用户数据、索引以及复制集配置。
内存存储引擎要求所有数据(如果 mongod 实例是复制集的一个部分,包括索引,日志等)必须能够容纳于 --inMemorySizeGB 命令行选项 或者 YAML configuration file 中 storage.inMemory.engineConfig.inMemorySizeGB 配置中指定的大小中。
分片集群 可以将使用内存存储引擎的 mongod 实例部署为分片集群的一个部分。例如,在分片集群中,可以配置一个分片,由以下复制集组成:
  • 两个运行着内存存储引擎的 mongod 实例
  • 一个运行着 WiredTiger 存储引擎的 mongod 实例。将WiredTiger成员作为一个隐藏的成员(例如 hidden: true and priority: 0 )。
向该分片中添加 tag inmem 。例如,如果该分片的名字为 shardC ,连接到 mongos ,然后运行 sh.addShardTag() 。
例如,
sh.addShardTag("shardC", "inmem")

对于其他分片,增加一个单独的标签 persisted 。
sh.addShardTag("shardA", "persisted") sh.addShardTag("shardB", "persisted")

对应该位于 inmem 分片的每个分片集合, assign to the entire chunk range 标签 inmem :
sh.addTagRange("test.analytics", { shardKey: MinKey }, { shardKey: MaxKey }, "inmem")

对应该位于 persisted 分片的每个分片集合, 将标记inmem分配给整个块范围:
sh.addTagRange("salesdb.orders", { shardKey: MinKey }, { shardKey: MaxKey }, "persisted")

对于 inmen 分片,创建一个数据库或将其移动数据库。
Journaling 为了在发生故障时提供持久性,MongoDB使用写入日志记录到磁盘日志文件。
Journaling and the WiredTiger Storage Engine
WiredTiger会使用检查点来恢复数据,如果MongoDB在检查点之间意外退出,则需要使用日记功能来恢复在上一个检查点之后发生的信息。
在4.0版本后,使用WiredTiger引擎后就无法使用 --nojournal 或者storage.journal.enabled: false
使用Journaling进行恢复数据的过程:
  1. 查看数据文件以查找最后一个检查点的标识符;
  2. 在日志文件中搜索与上一个检查点的标识符匹配的记录;
  3. 执行自上一个检查点以来在日志文件中应用操作
Journal Files 对于日志文件,MongoDB在dbPath目录下创建一个名为journal的子目录。 WiredTiger日志文件的名称格式为WiredTigerLog.其中sequence是从0000000001开始的零填充数字。
MongoDB-存储
文章图片

日志文件包含每个写操作的记录,每条记录都有唯一的标识符。
MongoDB将WiredTiger配置为对日记数据使用snappy压缩。
WiredTiger的最小日志记录大小为128字节。 如果日志记录是128字节或更小,WiredTiger不会压缩该记录。
MongoDB的WiredTiger日志文件的最大大小限制大约为100 MB。 一旦文件超过该限制,WiredTiger就会创建一个新的日志文件。
WiredTiger会自动删除旧的日志文件,仅维护从上一个检查点恢复所需的文件。
WiredTiger将预先分配日志文件。
Journaling and the In-Memory Storage Engine
由于其数据保存在内存中,因此没有单独的日志。 写入关注j:true的写操作立即被确认。
如果副本集的任何投票成员使用内存存储引擎,则必须将writeConcernMajorityJournalDefault设置为false。
将writeConcernMajorityJournalDefault设置为false时,在确认写入之前MongoDB不会等待w: "majority"被写入磁盘日志。 这样,在给定副本集中的大多数节点的瞬时丢失(例如,崩溃和重启)的情况下,大多数写操作可能会回滚。
GridFS GridFS是用于存储和检索超过BSON文档大小限制(16 MB)的文件,如:图片、音频、视频等的规范。GridFS 也是文件存储的一种方式,但是它是存储在MonoDB的集合中。
GridFS 会将大文件对象分割成多个小的chunk(文件片段),一般为256k/个,每个chunk将作为MongoDB的一个文档(document)被存储在chunks集合中。
GridFS 用两个集合来存储一个文件:fs.filesfs.chunks
每个文件的实际内容被存在chunks(二进制数据)中,和文件有关的meta数据(filename,content_type,还有用户自定义的属性)将会被存在files集合中。
chunk集合
chunk集合中的每个文档代表GridFS中表示的文件的不同块。 此集合中的文档具有以下形式:
{ "_id" : , "files_id" : , "n" : , "data" : }

  • chunks._id
    The unique ObjectId of the chunk.
  • chunks.files_id
    父文件的id,对于在files集合中的_id
  • chunks.n
    块的序列号,从0开始
  • chunks.data
    The chunk’s payload as a BSON Binary type.
files集合
files集合的每个文档代表GridFS中的一个文件:
{ "_id" : , "length" : , "chunkSize" : , "uploadDate" : , "md5" : , "filename" : , "contentType" : , "aliases" : , "metadata" : , }

  • files._id
    The unique identifier for this document. The _id is of the data type you chose for the original document. The default type for MongoDB documents is BSON ObjectId.
  • files.length
    文件长度,字节
  • files.chunkSize
    指定每个chunk块的大小(字节),除了最后一块,默认255KB
  • files.uploadDate
    The date the document was first stored by GridFS. This value has the Date type.
  • files.md5
    An MD5 hash of the complete file returned by the filemd5 command. This value has the String type.
  • files.filename
    Optional. A human-readable name for the GridFS file.
  • files.contentType
    Optional. A valid MIME type for the GridFS file.
  • files.aliases
    Optional. An array of alias strings.
  • files.metadata
    可选的。 元数据字段可以是任何数据类型,并且可以包含您要存储的任何其他信息。 如果要向文件集合中的文档添加其他任意字段,请将它们添加到元数据字段中的对象。
GridFS操作
三个基本命令:put(存储) get(取得) list(列表)
先将文件压缩, 使用-d指定数据库
ulysses@ulysses:~$ tar -czvf TCP_IP.tar.gz Documents/图解TCP_IP_第5版.pdf Documents/图解TCP_IP_第5版.pdf ulysses@ulysses:~$ mongofiles -d first_demo put TCP_IP.tar.gz 2018-12-06T11:05:08.087+0800 connected to: localhost 2018-12-06T11:05:08.532+0800 added file: TCP_IP.tar.gz

使用list和search查看文件
ulysses@ulysses:~$ mongofiles -d first_demo list 2018-12-06T11:40:40.536+0800 connected to: localhost TCP_IP.tar.gz 32202865 ulysses@ulysses:~$ mongofiles -d first_demo search TCP 2018-12-06T11:40:50.291+0800 connected to: localhost TCP_IP.tar.gz 32202865

进入数据库后查看集合,多了fs.chunks和fs.files:
> use first_demo; switched to db first_demo > show tables fs.chunks fs.files myresult myresult_1 orders profile sites

查看fs.files:
> db.fs.files.find() { "_id" : ObjectId("5c0891e46a39c24a825b3d28"), "chunkSize" : 261120, "uploadDate" : ISODate("2018-12-06T03:05:08.536Z"), "length" : 32202865, "md5" : "ebafc54d2da56a5b3745fa96cb4240f3", "filename" : "TCP_IP.tar.gz" }

使用files的 _id 获取区块(chunk)的数据
db.fs.chunks.find({files_id:ObjectId("5c0891e46a39c24a825b3d28")})

尝试从MongoDB中获取文件
ulysses@ulysses:~$ mongofiles -d first_demo get TCP_IP.tar.gz 2018-12-06T11:38:34.518+0800 connected to: localhost 2018-12-06T11:38:34.761+0800 finished writing to TCP_IP.tar.gz

删除大文件
ulysses@ulysses:~$ mongofiles -d first_demo delete TCP_IP.tar.gz 2018-12-06T11:42:38.809+0800 connected to: localhost 2018-12-06T11:42:38.896+0800 successfully deleted all instances of 'TCP_IP.tar.gz' from GridFSulysses@ulysses:~$ mongofiles -d first_demo list 2018-12-06T11:42:43.716+0800 connected to: localhost... > show tables fs.chunks fs.files myresult myresult_1 orders profile sites > db.fs.files.find() > db.fs.chunks.find()

使用python
from pymongo import MongoClient from gridfs import GridFS import base64 db = MongoClient().first_demo fs = GridFS(db) print(fs.list()) if not fs.exists({"filename": 'timg.jpg'}): with open('/home/ulysses/Pictures/timg.jpg', 'rb') as f: kw = {'filename': 'timg.jpg'} fs.put(f, **kw) else: cursor = fs.find({"filename": 'timg.jpg'}, no_cursor_timeout=True) for grid_out in cursor: data = https://www.it610.com/article/grid_out.read() print(data)

执行结果
(venv) ulysses@ulysses:~/PycharmProjects/test_base$ python -m demo.mongo_demo.gridfs ['timg.jpg'] b'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\x01\x01\x00H\x00H\x00\x00\xff\xdb\x00C\x00\x0c\x08\t\n\t\x07\x0c\n\t\n\r\x0c\x0c\x0e\x11\x1d\x13\x11\x10\x10\x11#\x19\x1b\x15\x1d*%,+)%((.4B8.1?2((:N:?DGJKJ-7QWQHVBIJG\xff\xdb\x00C\x01\x0c\r\r\x11\x0f\x11"\x13\x13"G0(0GGGGG

删除:
else: cursor = fs.find({"filename": 'timg.jpg'}, no_cursor_timeout=True) for grid_out in cursor: data = https://www.it610.com/article/grid_out.read() print(data) fs.delete(grid_out._id)

数据备份 在Mongodb中我们使用mongodump命令来备份MongoDB数据。该命令可以导出所有数据到指定目录中。
mongodump命令可以通过参数指定导出的数据量级转存的服务器。
mongodump -h dbhost -d dbname -o dbdirectory
  • -h:
    MongDB所在服务器地址,例如:127.0.0.1,当然也可以指定端口号:127.0.0.1:27017
  • -d:
    需要备份的数据库实例,例如:test
  • -o:
    备份的数据存放位置,例如:c:\data\dump,当然该目录需要提前建立,在备份完成后,系统自动在dump目录下建立一个test目录,这个目录里面存放该数据库实例的备份数据。
实例:
ulysses@ulysses:~$ mongodump -d first_demo -o /home/mongodb/backup 2018-12-05T10:51:31.391+0800 writing first_demo.sites to 2018-12-05T10:51:31.394+0800 writing first_demo.profile to 2018-12-05T10:51:31.394+0800 writing first_demo.orders to 2018-12-05T10:51:31.395+0800 writing first_demo.myresult to 2018-12-05T10:51:31.448+0800 done dumping first_demo.profile (8 documents) 2018-12-05T10:51:31.448+0800 writing first_demo.myresult_1 to 2018-12-05T10:51:31.448+0800 done dumping first_demo.orders (4 documents) 2018-12-05T10:51:31.458+0800 done dumping first_demo.sites (19 documents) 2018-12-05T10:51:31.473+0800 done dumping first_demo.myresult_1 (2 documents) 2018-12-05T10:51:31.473+0800 done dumping first_demo.myresult (2 documents)

MongoDB-存储
文章图片

若不指定要备份的数据库,则会备份整个服务器数据;也可以使用mongodump --collection COLLECTION --db DB_NAME命令指定要备份的集合
数据恢复 mongodb使用 mongorestore 命令来恢复备份的数据。语法:
mongorestore -h <:port> -d dbname
  • –host <:port>, -h <:port>:
    MongoDB所在服务器地址,默认为: localhost:27017
  • –db , -d :
    需要恢复的数据库实例,例如:test,当然这个名称也可以和备份时候的不一样,比如test2
  • –drop:
    恢复的时候,先删除当前数据,然后恢复备份的数据。就是说,恢复后,备份后添加修改的数据都会被删除,慎用哦!
  • path, --dir::
    mongorestore 最后的一个参数,设置备份数据所在位置,
    不能同时指定 path和 --dir 选项,–dir也可以设置备份目录。
【MongoDB-存储】实例:
ulysses@ulysses:~$ mongorestore -d first_demo /home/mongodb/backup/first_demo 2018-12-05T11:00:45.779+0800 the --db and --collection args should only be used when restoring from a BSON file. Other uses are deprecated and will not exist in the future; use --nsInclude instead 2018-12-05T11:00:45.779+0800 building a list of collections to restore from /home/mongodb/backup/first_demo dir 2018-12-05T11:00:45.780+0800 reading metadata for first_demo.sites from /home/mongodb/backup/first_demo/sites.metadata.json 2018-12-05T11:00:45.780+0800 reading metadata for first_demo.profile from /home/mongodb/backup/first_demo/profile.metadata.json ...

    推荐阅读