MongoDB丢数据问题的分析

坊间有很多传说MongoDB会丢数据。特别是最近有一个InfoQ翻译的Sven的一篇水文(为什么叫做水文?因为里面并没有他自己的原创,只是搜罗了一些网上的博客,炒了些冷饭吃),其中又提到了丢数据的事情。大家知道作为一个数据库来说,数据的持久性基本上是数据库的最低要求了。如果MongoDB真的有那么糟糕的数据安全问题,它早就在技术选择众多的今天被无情地淘汰掉了。那么真相到底如何呢?

实事求是地来说,MongoDB确实在其发展的过程中,有一些数据持久化的问题没有处理好,特别是一些默认值的选定上。大部分用户会拿来就用,直到遇到问题之后才发现他们应该在开始的时候做一些必要的配置。但是,所有这些已经被发现的问题也好,默认设置也好,已经在 MongoDB 2.6以后得到了妥善的解决。我可以负责任地告诉你,你看到的数据安全问题,基本上都是2.4或者之前版本的问题或者是用户配置的问题。接下来我们来仔细分析一下MongoDB的数据安全机制,通过这个分析来更好地理解为什么有丢数据问题的说法,以及如何来正确的配置MongoDB来保证数据的安全。

MongoDB的数据安全包括以下几个概念:

  • 恢复日志(Journal)
  • 写关注(Write Concern)

恢复日志

在MySQL, PostgreSQL,Oracle等关系型数据库里都有一个Write Ahead Log(Redo Log)的机制用来解决因为系统掉电或者崩溃时导致内存数据丢失问题。MongoDB 的journal就是实现这个目的的一种WAL日志。在MongoDB 2.0之前,Journal没有被支持或者不是一个默认开的选项。所以当你进行写入操作时。在没有Journal的情况下,MongoDB是这样保存数据的:

简单来说,数据在写入内存之后即刻返回给应用程序。而数据刷盘动作则在后台由操作系统来进行。MongoDB会每隔60秒强制把数据刷到磁盘上。那么大家可以想象得到,如果这个时候发生了系统崩溃或者掉电,那么未刷盘的数据就会彻底丢失了。如果大家看到的博客是2011年左右的,那基本上是碰到了这种情况。

自从2.0开始,MongoDB已经把Journal日志设为默认开启。

在上图的情况下,MongoDB会先把数据更新写入到Journal Buffer里面然后再更新内存数据,然后再返回给应用端。Journal会以100ms的间隔批量刷到盘上。这样的情况下,即使出现断电数据尚未保存到文件,由于有Journal文件的存在,MongoDB会自动根据Journal里面的操作历史记录来对数据文件重新进行追加。

有细心的同学可能注意到,Journal文件是100ms 刷盘一次。那么要是系统掉电正好发生在上一次刷journal的50ms之后呢?这个时候,我们就可以来看一下MongoDB持久化的下一个概念了:写关注

写关注(Write Concern)

写关注(或翻译为写安全机制)是MongoDB特有的一个功能。它可以让你灵活地指定你写操作的持久化设定。这是一个在性能和可靠性之间的一个权衡。 写关注有以下几个级别:

{w: 0} Unacknowledged

Unacknowledged指的是对每一个写入操作,MongoDB并不会返回一个是否成功的状态值。这个级别是写入性能最好但也是最不安全的级别。比如说,你试图插入一个违反了唯一性的文档(重复的身份证号),那么MongoDB会拒绝写入并报错。但是由于驱动端并没有在乎你的报错,应用程序还满心欢喜以为一切都没问题,下回再来查询那条数据的时候就会出现数据缺失的情况。

有不少时候MongoDB用来保存一些监控和程序日志数据,这个时候如果你有1、2条数据丢失,是不会对应用程序有什么影响的。基于这些MongoDB早些时候不成熟考量,MongoDB在2.2之前的默认设置就是 {w:0}。这是个让MongoDB 悔恨无比的选择,因为这个是很多人觉得MongoDB数据不安全的根本原因。

在MongoDB 2.4,这个设置已经被改为下面的 {w:1}

{w: 1} Acknowledged

Acknowledged 的意思就是对每一个写入MongoDB都会确认操作的完成状态,不管是成功还是失败。当然这个确认只是基于主节点的内存写入。但就是这个级别,可以侦测到重复主键, 网络错误,系统故障或者是无效数据等错误。

自2.4版本起,MongoDB的默认写安全设置就是 {w:1} Acknowledged。在这种情况下,出现因为系统故障掉电原因而导致的数据丢失只会是我们早些提到的日志没有及时刷盘的情况。如果你不能接受因为系统崩溃而引起的可能的100ms的数据损失,那么你可以选用下一个级别: {j:1} Journaled

{j:1} Journaled

使用这种方式意味着每一次的写操作会在MongoDB实实在在的把journal落盘以后才会返回。当然这并不意味着每一个写操作就等于一个IO。MongoDB并不会对每一个操作都立即刷盘,而是会等最多30ms,把30ms内的写操作集中到一起,采用顺序追加的方式写入到盘里。在这30ms内客户端线程会处于等待状态。这样对于单个操作的总体响应时间将有所延长,但对于高并发的场景,综合下来平均吞吐能力和响应时间不会有太大的影响。特别是你能给journal部署一个对顺序写有优化的IO带宽足够的专门的存储系统的话,这个对性能的影响可以降到最低。

那么使用 {j:1} 是不是就100% 安全了呢?如果是单机版本,这个基本上就是可以确保的了(除非硬盘损坏)。可是在复制集的场景下,我们还需要来考虑一种更高的级别: {w: “majority”}

{w: “majority”} 写到多数节点

MongoDB 的默认部署是至少3个节点的复制集(Replicaset)。使用复制集的好处很多,最关键的就是提高系统的高可用性。另外一个好处就是提供数据的持久性。在复制集下哪怕你的整个主机连内存带硬盘坏掉,你的数据还是健康的存在在第二台或者第N台从节点上。但是复制集作为一种分布式的架构也对我们数据一致性提出了新的挑战。以上述的{w: 1} 写安全配置为例,我们来分析一种比较复杂的场景。

  • 01:00:00 网络故障,主从之间网络断开
  • 01:00:01 应用写入一个文档: {ts: “01:00:01″} 注意这个文档无法复制到B和C。此时主节点尚未完全确认网络已故障,所以按照{w:1}规则继续接受并确认写入。
  • 01:00:02 主节点A意识到自己无法和从节点B,C 联络上,主动降级为从节点,停止接受写操作
  • 01:00:05 B,C 选举结果成功,B升级为主节点。B开始接受写操作。{ts: “01:00:06″}
  • 01:00:08 网络恢复,A重新加入集群。这个时候A的oplog 和B的oplog已经有不一致了。A会主动把B上面不存在的写操作回滚掉(rollback),并写入一个回滚文件。

在这个时候应用如果再去查询 {ts: “01:00:01″}这个文档,MongoDB 将会说文档不存在!

怎么办怎么办? {w: “majority”} 就是我们的答案。 “majority” 指的是“大多数节点”。使用这个写安全级别,MongoDB只有在数据已经被复制到多数个节点的情况下才会向客户端返回确认。

我们来看一下在使用 {w: “majaority”} 之后,刚才的情况就变成了:

  • 01:00:00 网络故障,主从之间网络断开
  • 01:00:01 应用要求写入一个文档: {ts: “01:00:01″} 文档会首先成功写入主节点。但是由于网络断开这个文档无法复制到B和C。因为无法满足{w:”majority”}要求,从应用的角度这个文档并没有写入成功。
  • 01:00:02 主节点A意识到自己无法和从节点B,C 联络上,主动降级为从节点,停止接受写操作
  • 01:00:05 B,C 选举结果成功,B升级为主节点。B开始接受写操作。{ts: “01:00:06″}
  • 01:00:08 网络恢复,A重新加入集群。这个时候A会产生回滚,把{ts: “01:00:01″}这个文档删除。 此时集群的数据状态为一致和正确的。

至此,如果使用 {w: “majority”, j:1 }, 那么MongoDB可以满足所有级别数据持久性的要求。值得注意的是在2013年5月Kyle Kingsly 发表了一篇博客 Call Me Maybe: http://aphyr.com/posts/284-call-me-maybe-mongodb 在这片文章里Kyle 汇报了一些关于 {w: “majority”} 的bug, 这些bug已经在2.6里被解决了。当然像Sven那样的哗众取宠之流,估计并没有去研究3.0里面是否真的有问题,而是随便google了一下几年前的东西来做文章。

总结

一般来说,MongoDB建议在集群中使用 {w: “majority”} 设置。在一个集群是健壮的部署的情况下(如:足够网络带宽,机器没有满负荷),这个可以满足绝大部分数据安全的要求,因为MongoDB的复制在正常情况下是毫秒级别的,往往在Journal刷盘之前已经复制到从节点了。如果你追求完美,那么可以再进一步使用{j:1} 。两者相结合,

传说中MongoDB 丢数据的事情,确实已经成为传说了。

后记:在我写这篇文章之时,社区又有人汇报数据丢失的问题。说的是每一万条记录就会丢一两条记录。对于这种情况,我的第一反应就是:查查你的代码吧,很多时候往往问题出在程序上。果不其然,经过仔细检查,原来是代码的问题。

时间: 2024-10-07 23:23:18

MongoDB丢数据问题的分析的相关文章

(转)Memcache,Redis,MongoDB(数据缓存系统)方案对比与分析

Memcache,Redis,MongoDB(数据缓存系统)方案对比与分析 数据库表数据量极大(千万条),要求让服务器更加快速地响应用户的需求. 二.解决方案: 1.通过高速服务器Cache缓存数据库数据 2.内存数据库 (这里仅从数据缓存方面考虑,当然,后期可以采用Hadoop+HBase+Hive等分布式存储分析平台) 三.主流解Cache和数据库对比: 上述技术基本上代表了当今在数据存储方面所有的实现方案,其中主要涉及到了普通关系型数据库(MySQL/PostgreSQL),NoSQL数据

海量大数据大屏分析展示一步到位:DataWorks数据服务对接DataV最佳实践

概述数据服务(https://ds-cn-shanghai.data.aliyun.com) 是DataWorks产品家族的一员,提供了快速将数据表生成API的能力,通过可视化的向导,一分钟"零代码"就可以生成API,让API开发从未有过如此便捷!同时支持自定义API查询SQL功能,对您的个性化复杂查询逻辑支持照样不在话下. DataWorks数据服务提供HTTP API服务,采用Serverless架构,您只需关注API本身的查询逻辑,无需关心运行环境等基础设施,零运维成本. Dat

Mysql丢数据及主从数据不一致的场景

Mysql丢数据及主从数据不一致的场景 随着对MySQL的学习,发现了MySQL的很多问题,最重要的就是丢数据的问题.对于丢数据问题,我们应该了解丢数据的场景,这样在以后的学习中多考虑如何去避免及解决这些问题. 1.MySQL数据库层丢数据场景   本节我们主要介绍一下在存储引擎层上是如何会丢数据的. 1.1.InnoDB丢数据   InnoDB支持事务,同Oracle类似,事务提交需要写redo.undo.采用日志先行的策略,将数据的变更在内存中完成,并且将事务记录成redo,顺序的写入red

flume到底会丢数据吗?其可靠性如何?——轻松搞懂Flume事务机制

先给出答案: 需要结合具体使用的source.channel和sink来分析,具体结果可看本文最后一节. Flume事务 ? 一提到事务,我们首先就想到的是MySQL中的事务,事务就是将一批操作做成原子性的,即这一批要么都成功,要么都失败. ? 同样的道理,在flume中也有事务,那么Flume中的事务在哪个地方呢?在Flume中的批量操作又是指什么呢? Flume中的事务存在于哪个位置? ? 在Flume中一共有两个事务,一个是在Source到Channel之间,一个是Channel到Sink

netback的tasklet调度问题及网卡丢包的简单分析

最近在万兆网卡上测试,出现了之前千兆网卡没有出现的一个现象,tasklet版本的netback下,vm进行发包测试,发现vif的interrupt默认绑定在cpu0上,但是vm发包运行时发现host上面cpu1, cpu2的ksoftirqd很高. 从之前的理解上来说,包从netfront出来通过eventchannel notify触发vif的irq处理函数,然后tasklet_schedule调用tx_action,通过一系列处理流程把包发给网卡.所以vif的interrupt绑在哪个cpu

【数据分析 R语言实战】学习笔记 第五章 数据的描述性分析(上)

5.1R内置的分布 分布是描述一个样本数据最核心.最重要的方式.R内嵌了很多常用的统计分布,提供了四类函数:概率密度函数(density),累积分布函数(probability).分位数(quantile)和伪随机数(random).在R中分别用d,p,q,r表示这4个项目,后面接分布的英文名称或缩写. 5.2集中趋势的分析 5.2.1集中趋势的测度 描述统计分布集中趋势的指标主要是平均数.中位数.众数,也称为“平均指标”.这些指标的主要作用包括: 反映总体各单位变量分布的集中趋势和一般水平;

压缩 MongoDB 的数据文件

MongoDB采用了磁盘空间预分配的机制,为了避免磁盘碎片以及使用mmap后造成的近一步的内存碎片,但是随着数据的增删除改操作,数据文件不可避免的会产生空洞,造成磁盘空间和内存的浪费.本文说的是这方面的压缩,数据使用某些压缩算法进行压缩的讨论不在此范围. 在MongoDB 中,大概有两种方法可以解决这种问题,但是都不是无痛方式,所以并不推荐使用. 1.通过 repairDatabase 整理数据文件 repairDatabase 是MongoDB 内置的一个db 上的方法,调用这个方法,Mong

mysql recovery 1 (允许停机,不许丢数据)

1.备份策略: 1)按天备份: 优点:恢复时间短,维护成本低 缺点:占用空间大,占用资源多(比如说老是要锁表) 2)按周备份: 优点:占用空间小,资源占用低 缺点:维护成本大 2.增量恢复的场景 1)数据库迁移,或者跨机房灾备. 2)增加从库. 3)认为操作失误,从库也没办法. 3.案例(可以停机,但是不能丢数据) 1)增量,全备开启 [[email protected] ~]# grep log-bin /etc/my.cnf log-bin=mysql-bin mysqldump -uroo

mongoDB 插入数据 用java实现

import java.net.UnknownHostException; import com.mongodb.BasicDBObject; import com.mongodb.DB; import com.mongodb.DBCollection; import com.mongodb.DBObject; import com.mongodb.Mongo; /**  * 用java 往mongoDB插入数据  * @author wwd *  */ public class InsertD