持久性1、使用日志机制的成员级别持久性2、使用写关注的集群级别持久性2.1、writeConcern的w和wtimeout选项2.2、writeConcern的j日志选项3、使用读关注的集群级别持久性4、使用写关注的事务持久性5、MongoDB不能保证什么6、检查数据损坏1、使用日志机制的成员级别持久性为了在服务器发生故障时提供持久性MongoDB 使用了一种称为日志journal的预写式日志WAL机制。WAL 是数据库系统中一种常用的持久性技术其基本原理是在将对数据库所做的更改应用到数据库本身之前将对这些更改的一种表示写到持久介质如磁盘上。在许多数据库系统中WAL 也被用来提供原子性这一数据库属性。然而MongoDB 使用其他技术来确保原子写入。从 MongoDB 4.0 开始当应用程序对副本集执行写操作时MongoDB 会使用与 oplog相同的格式创建日志条目。MongoDB 使用了一种基于操作日志oplog的语句级别的复制机制。oplog 中的语句是对写操作影响的每个文档所做的实际更改的表示。因此oplog 语句很容易应用于副本集的其他成员而无须考虑版本、硬件或副本集成员之间的其他差异。此外每个 oplog 语句都是幂等的这意味着它可以被应用任意次数而对数据库的更改结果总是相同的。像大多数数据库一样MongoDB 同时维护了日志和数据库数据文件的内存视图。默认情况下它每 50 毫秒会将日志条目刷新到磁盘上每 60 秒会将数据库文件刷新到磁盘上。刷新数据文件的 60 秒间隔称为检查点checkpoint。日志用于为自上一个检查点以来写入的数据提供持久性。关于持久性的问题如果服务器突然停止了那么在其重新启动时可以使用日志重放在关闭前没有刷新到磁盘的所有写操作。对于日志文件MongoDB 在 dbPath 目录下创建了一个名为 journal 的子目录。WiredTigerMongoDB 的默认存储引擎日志文件的名称格式为 WiredTigerLog.sequence其中 sequence 是一个从 0 000 000001 开始的零填充数字。除了非常小的日志记录MongoDB 会对写入日志的数据进行压缩。日志文件的最大大小限制大约为 100MB。一旦日志文件超过这个限制MongoDB 就会创建一个新的日志文件并在其中写入新的记录。由于日志文件只需要在上次检查点之后恢复数据因此在新的检查点写入完成时MongoDB 会自动删除“旧的”日志文件也就是那些在最近检查点之前的写操作。如果服务器崩溃了或使用了 kill -9那么 mongod会在启动时重放其日志文件。可能发生的写操作丢失有一个最大范围默认情况下是最近 100 毫秒加上将日志刷 新到磁盘所花费的时间内所发生的写操作。如果应用程序需要较短的日志刷新间隔那么有两种方法。一种方法是对 mongod 命令使用--journalCommitInterval选项以更改间隔时间。该选项接受从 1 到 500 毫秒的值。另一种方法是在写关注中指定所有写操作都记录到磁盘。缩短日志刷盘的时间间隔将对性能产生负面影响因此在更改日志记录默认值之前需要确定对应用程序的影响。2、使用写关注的集群级别持久性通过写关注write concern可以指定应用程序在响应写请求时需要何种级别的确认。在副本集中网络分区、服务器故障或数据中心断电都可能会阻止写操作复制到每个成员甚至大多数成员。当副本集恢复到正常状态时可能会回滚那些未复制到大多数成员的写操作。在这些情况下客户端和数据库可能对已提交的数据有不同的看法。有些应用程序在某些情况下可以接受写操作的回滚。例如在某些社交应用程序中回滚少量的评论不会有什么问题。MongoDB 在集群级别上支持一系列耐久性保证使应用程序设计者能够选择最适合其场景的耐久性级别。2.1、writeConcern的w和wtimeout选项MongoDB 查询语言支持为所有插入和更新方法指定写关注。假设现在有一个电子商务应用程序我们希望确保所有的订单都是持久的。将订单写入数据库的代码如下所示try{db.products.insertOne({sku:H1100335456,item:Electric Toothbrush Head,quantity:3},{writeConcern:{w:majority,wtimeout:100}});}catch(e){print(e);}所有的插入和更新方法都接受第二个参数其格式是一个文档。在该文档中可以为 writeConcern 指定一个值。在前面示例中指定的写关注表示希望得到服务器的确认。这个确认是只有当写入被成功复制到副本集的大多数成员时其才算成功完成。此外如果写操作没有在 100 毫秒或更短的时间内复制到大多数副本集成员则应该返回错误。在这种情况下MongoDB 不会撤销写关注超过时间限制之前成功执行的数据修改而应该由应用程序决定如何处理这种情况下的超时。通常来说应该对wtimeout 值进行配置这样只有在不寻常的情况下应用程序才会超时而应用程序在响应超时错误时所采取的行动将确保数据处于正确的状态。在大多数情况下应用程序应该尝试确定超时是由于网络通信的短暂问题还是其他更严重的原因造成的。写关注文档中 w 参数的值可以指定为 “majority”如本例中那样。或者也可以指定为一个介于零和副本集成员数量之间的整数。最后还可以指定为副本集成员的标签比如标识那些在 SSD 或机械硬盘上的成员或者标识那些用于报表系统或 OLTP 工作负载的成员。还可以指定标签集合作为 w 的值以确保只有在提交给至少一个与所提供的标签集合匹配的副本集成员时才会对写操作进行确认。2.2、writeConcern的j日志选项除了为 w 选项提供值之外还可以通过在写关注文档中使用 j 选项来要求对写操作的日志写入情况进行确认。如果 j 的值为 true则 MongoDB 只有在请求的成员数w的值都已经将操作写入它们磁盘上的日志中时才会确认写操作成功。继续刚才的例子如果想确保大多数成员的所有写操作记录在日志中则可以像下面这样更新代码try{db.products.insertOne({sku:H1100335456,item:Electric Toothbrush Head,quantity:3},{writeConcern:{w:majority,wtimeout:100,j:true}});}catch(e){print(e);}在不等待日志记录的情况下如果服务器进程或硬件停止运行那么在每个成员上会有一个大约 100 毫秒的短暂的时间窗口可能发生写操作丢失。然而在确认对副本集成员的写操作之前等待日志记录确实会造成性能损失。在解决持久性问题时必须仔细评估应用程序的需求并权衡所选择的持久性设置对性能的影响。3、使用读关注的集群级别持久性在 MongoDB 中读关注read concern允许对何时读取结果进行配置。这可以让客户端在写操作被持久化之前就看到写入的结果。读关注可以与写关注一起使用以控制对应用程序的一致性和可用性的保证级别。不要将读关注与读偏好read preference相混淆后者处理从何处读取数据的问题。具体来说读偏好决定了副本集中承载数据的成员。默认的读偏好是从主节点中读取。读关注决定了正在读取的数据的一致性和隔离性。默认的readConcern 是 local它所返回的数据不保证已经被写入了大多数承载数据的副本集成员。这可能会导致数据在将来的某个时刻被回滚。majority 读关注只返回被大多数副本集成员确认的持久数据不会被回滚。MongoDB3.4 中增加了 linearizable 读关注它确保返回的数据反 映了在读操作开始之前已成功完成的经过大多数确认的写操作。在返回结果之前它可能会等待那些正在并发执行的写操作完成。和写关注一样在为应用程序选择合适的关注选项之前需要权衡读关注对性能的影响以及它们提供的持久性和隔离性保证。4、使用写关注的事务持久性在 MongoDB 中对单个文档的操作是原子的。可以在单个文档中使用内嵌文档和数组来表示实体之间的关系而不是使用范式化的数据模型将实体和关系拆分到多个集合中。因此很多应用程序不需要多文档事务。然而对于需要原子更新多个文档的场景MongoDB 提供了针对副本集执行多文档事务的能力。多文档事务可以跨多个操作、文档、集合和数据库使用。事务要求其中的所有数据更改都是成功的。如果任何操作失败则事务将中止所有数据更改都会被丢弃。如果所有操作都成功那么事务中所做的所有数据更改都会被保存并且写操作对之后的读操作都是可见的。与单个写操作一样可以为事务指定一个写关注。对于事务来说应该在事务级别而不是单个操作级别设置写关注。在提交时事务会使用事务级别的写关注来提交写操作。为事务内部单个操作设定的写关注会被忽略。可以在事务开始时为事务提交设置写关注。事务不支持将写关注设置为 0。如果对一个事务使用了写关注 1那么如果发生了故障转移则事务可能会回滚。如果在副本集中发生可能导致强制性故障转移的网络和服务器故障则可以使用 “majority” 的 writeConcern 来确保事务在这种情况下的持久性。functionupdateEmployeeInfo(session){employeesCollectionsession.getDatabase(hr).employees;eventsCollectionsession.getDatabase(reporting).events;session.startTransaction({writeConcern:{w:majority}});try{employeesCollection.updateOne({employee:3},{$set:{status:Inactive}});eventsCollection.insertOne({employee:3,status:{new:Inactive,old:Active}});}catch(error){print(Caught exception during transaction, aborting.);session.abortTransaction();throwerror;}commitWithRetry(session);}5、MongoDB不能保证什么MongoDB 在一些情况比如存在硬件问题或文件系统错误下无法保证持久性。特别是如果硬盘损坏则MongoDB 无法保护其中的数据。此外不同种类的硬件和软件可能有不同的持久性保证。例如一些较便宜或较旧的硬盘在写操作排队等待时而不是在实际写入之后就会报告写入成功。MongoDB 在这个级别上无法防止误报如果这时系统崩溃则数据可能会丢失。基本上MongoDB 的安全性与其底层系统相当如果硬件或文件系统破坏了数据则 MongoDB 无法防止这种情况。可以使用复制机制来应对系统问题。如果一台机器出了故障那么希望另一台机器仍能正常工作。6、检查数据损坏validate 命令可用于检查集合是否损坏。要对 movies 集合运行 validate 命令可以执行以下操作db.movies.validate({full:true}){ns:sample_mflix.movies,nInvalidDocuments:NumberLong(0),nrecords:45993,nIndexes:5,keysPerIndex:{_id_:45993,$**_text:3671341,genres_1_imdb.rating_1_metacritic_1:94880,tomatoes_rating:45993,getMovies:45993},indexDetails:{$**_text:{valid:true},_id_:{valid:true},genres_1_imdb.rating_1_metacritic_1:{valid:true},getMovies:{valid:true},tomatoes_rating:{valid:true}},valid:true,warnings:[],errors:[],extraIndexEntries:[],missingIndexEntries:[],ok:1}你要查找的主要字段是 “valid”希望这个字段为 true。否则validate 会给出所发现的数据损坏细节。validate 输出中的大部分内容描述了集合的内部结构以及用于理解跨集群操作顺序的时间戳。这些对于调试来说不是特别有用。validate 命令只适用于集合它还会检查相关联的索引并记录在 indexDetails 字段中。不过这需要使用 { full:true } 选项来启用完整的 validate。