Mongo数据一致性浅析

这篇具有很好参考价值的文章主要介绍了Mongo数据一致性浅析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一致性简介

根据 CAP 理论的一致性(Consistency)问题,即在读写发生在不同节点的情况下,怎么保证每次读取都能获取到最新写入的数据。这个一致性即是我们今天要讨论的MongoDB 可调一致性模型中的一致性,区别于单机数据库系统中经常提到的 ACID 理论中的一致性。

可调性具体指的是什么呢
这里就不得不提分布式系统中的另外一个理论,PACELC。PACELC 在 CAP 提出 10 年之后,即 2012 年,在一篇 Paper 中被正式提出,其核心观点是,根据 CAP,在一个存在网络分区( P )的分布式系统中,我们面临在可用性( A )和一致性( C )之间的选择,但除此之外( E ),即使暂时没有网络分区的存在,在实际系统中,我们也要面临在访问延迟( L )和一致性( C )之间的抉择。所以,PACELC 理论是结合现实情况,对 CAP 理论的一种扩展。

以复制为基础构建的分布式系统中,一致性模型通常可按照以数据为中心(Data-centric)以客户端为中心(Client-centric)来划分,

mongo一致性

MongoDB 的 Causal Consistency Session 即提供了上述几个承诺:RYW,MR,MW,WFR。

但是,这里是 MongoDB 和标准不太一样的地方,MongoDB 的因果一致性提供的是 Client-centric 一致性模型下的承诺,而非 Data-centric。这么做主要还是从系统开销角度考虑,实现 Data-centric 下的因果一致性所需要的全局一致性视图代价过高,在真实的场景中,Client-centric 一致性模型往往足够了,关于这一点的详细论述可参考 MongoDB 官方在 SIGMOD’19 上 Paper 的 2.3 节。

Causal Consistency 在 MongoDB 中是相对比较独立一块实现,只有当客户端读写的时候开启 Causal Consistency Session 才提供相应承诺没有开启 Causal Consistency Session 时,MongoDB 通过 writeConcern 和 readConcern 接口提供了可调一致性,具体来说,包括线性一致性和最终一致性

最终一致性在标准中的定义是非常宽松的,是最弱的一致性模型,但是在这个一致性级别下 MongoDB 也通过 writeConcern 和 readConcern 接口的配合使用,提供了丰富的对性能和正确性的选择,从而贴近真实的业务场景。

ReadConcern

readConcern 的初衷在于解决『脏读』的问题,比如用户从 MongoDB 的 primary 上读取了某一条数据,但这条数据并没有同步到大多数节点,然后 primary 就故障了,重新恢复后 这个primary 节点会将未同步到大多数节点的数据回滚掉,导致用户读到了『脏数据』。
当指定 readConcern 级别为 majority 时,能保证用户读到的数据『已经写入到大多数节点』,而这样的数据肯定不会发生回滚,避免了脏读的问题。
需要注意的是, readConcern 能保证读到的数据『不会发生回滚』,但并不能保证读到的数据是最新的,这个官网上也有说明。

WriteConcern

MongoDB支持的WriteConcern选项如下

  1. w: 数据写入到number个节点才向用客户端确认

  • {w: 0} 对客户端的写入不需要发送任何确认,适用于性能要求高,但不关注正确性的场景

  • {w: 1} 默认的writeConcern,数据写入到Primary就向客户端发送确认

  • {w: “majority”} 数据写入到副本集大多数成员后向客户端发送确认,适用于对数据安全性要求比较高的场景,该选项会降低写入性能

  1. j: 写入操作的journal持久化后才向客户端确认

  • 默认为”{j: false},如果要求Primary写入持久化了才向客户端确认,则指定该选项为true

  1. wtimeout: 写入超时时间,仅w的值大于1时有效。

  • 当指定{w: }时,数据需要成功写入number个节点才算成功,如果写入过程中有节点故障,可能导致这个条件一直不能满足,从而一直不能向客户端发送确认结果,针对这种情况,客户端可设置wtimeout选项来指定超时时间,当写入过程持续超过该时间仍未结束,则认为写入失败。

三方库支持

源码地址:https://github.com/mongodb/mongo-go-driver

这里以golang的mongo-driver库为例:

type Collection struct {
   client *Client
   db *Database
   name string
   readConcern *readconcern.ReadConcern
   writeConcern *writeconcern.WriteConcern
   readPreference *readpref.ReadPref
   readSelector description.ServerSelector
   writeSelector description.ServerSelector
   registry *bsoncodec.Registry
}
// ReadPref determines which servers are considered suitable for read operations.
type ReadPref struct {
  maxStaleness time.Duration
  maxStalenessSet bool
  mode Mode
  tagSets []tag.Set
  hedgeEnabled *bool
}
// Mode indicates the user's preference on reads.
type Mode uint8
// Mode constants
const (
  _ Mode = iota
  // PrimaryMode indicates that only a primary is
  // considered for reading. This is the default
  // mode.
  PrimaryMode
  // PrimaryPreferredMode indicates that if a primary
  // is available, use it; otherwise, eligible
  // secondaries will be considered.
  PrimaryPreferredMode
  // SecondaryMode indicates that only secondaries
  // should be considered.
  SecondaryMode
  // SecondaryPreferredMode indicates that only secondaries
  // should be considered when one is available. If none
  // are available, then a primary will be considered.
  SecondaryPreferredMode
  // NearestMode indicates that all primaries and secondaries
  // will be considered.
  NearestMode
)

读写分离

mongodb进行了读写分离,写入往主库,读取从从库,这样减轻了主库的压力。但由于从库同步数据的延时性,某数据在主库写入后马上从从库读,会读取到旧数据并且会将旧数据塞入了缓存。mongodb写的操作有相关设置,writeConcern可设置为Replica Acknowledged级别,即数据被写入到至少两个从库节点返回。这样虽然一定程度上解决从库延时,但写的性能大大降低。

同步流程

Primary上的写入会记录oplog,存储到一个固定大小的capped collection里,Secondary主动从Primary上拉取oplog并重放应用到自身,以保持数据与Primary节点上一致。

initial sync

新节点加入(或者主动向Secondary发送resync)时,Secondary会先进行一次initial sync,即全量同步,遍历Primary上的所有DB的所有集合,将数据拷贝到自身节点,然后读取『全量同步开始到结束时间段内』的oplog并重放。全量同步不是本文讨论的重点,将不作过多的介绍。

tailing oplog

全量同步结束后,Secondary就开始从结束时间点建立tailable cursor,不断的从同步源拉取oplog并重放应用到自身,这个过程并不是由一个线程来完成的,mongodb为了提升同步效率,将拉取oplog以及重放oplog分到了不同的线程来执行。

  • producer thread,这个线程不断的从同步源上拉取oplog,并加入到一个BlockQueue的队列里保存着,BlockQueue最大存储240MB的oplog数据,当超过这个阈值时,就必须等到oplog被replBatcher消费掉才能继续拉取。

  • replBatcher thread,这个线程负责逐个从producer thread的队列里取出oplog,并放到自己维护的队列里,这个队列最多允许5000个元素,并且元素总大小不超过512MB,当队列满了时,就需要等待oplogApplication消费掉。

  • oplogApplication会取出replBatch thread当前队列的所有元素,并将元素根据docId(如果存储引擎不支持文档锁,则根据集合名称)分散到不同的replWriter线程,replWriter线程将所有的oplog应用到自身;等待所有oplog都应用完毕,oplogApplication线程将所有的oplog顺序写入到local.oplog.rs集合。

producer的buffer和apply线程的统计信息都可以通过db.serverStatus().metrics.repl来查询到
默认情况下,Secondary采用16个replWriter线程来重放oplog,可通过启动时设置replWriterThreadCount参数来定制线程数,当提升线程数到32时,同步的情况大大改观,主备写入的qps基本持平,主备上数据同步的延时控制在1s以内。

修改replWriterThreadCount参数的方法,具体应该调整到多少跟Primary上的写入负载如写入qps、平均文档大小等相关,并没有统一的值。

通过mongod命令行来指定 mongod --setParameter replWriterThreadCount=32

在配置文件中指定

setParameter:
    replWriterThreadCount: 32

oplog示例

cmgo-9t1zzciv_0:PRIMARY> use local
switched to db local
cmgo-9t1zzciv_0:PRIMARY> db.oplog.rs.find({}).limit(1).pretty()
{
        "ts" : Timestamp(1672717376, 3),
        "t" : NumberLong(2),
        "h" : NumberLong(0),
        "v" : 2,
        "op" : "u",
        "ns" : "component.deploy",
        "ui" : UUID("a164a9ab-7b3a-43e5-890d-64e1142b3455"),
        "o2" : {
                "_id" : NumberLong(8814)
        },
        "wall" : ISODate("2023-01-03T03:42:56.762Z"),
        "lsid" : {
                "id" : UUID("f2ce9e4a-b863-4830-9824-d5e773846512"),
                "uid" : BinData(0,"+WgOVnKmrMF6/mSQ4vglRC+SRli4twPTLNSeVRPyVy8=")
        },
        "txnNumber" : NumberLong(1325),
        "stmtId" : 0,
        "prevOpTime" : {
                "ts" : Timestamp(0, 0),
                "t" : NumberLong(-1)
        },
        "o" : {
                "$v" : 1,
                "$set" : {}
      }
}

http://mysql.taobao.org/monthly/2021/04/02/文章来源地址https://www.toymoban.com/news/detail-491712.html

到了这里,关于Mongo数据一致性浅析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 如何保持数据一致性

    数据库和缓存(比如:redis)双写数据一致性问题,是一个跟开发语言无关的公共问题。尤其在高并发的场景下,这个问题变得更加严重。 以下是我无意间了解很好的文章,分享给大家。 通常情况下,我们使用缓存的主要目的是为了提升查询的性能。大多数情况下,我们是这

    2024年02月08日
    浏览(55)
  • Redis 数据一致性

    当我们在使用缓存时,如果发生数据变更,那么你需要同时操作缓存和数据库,而它们两个又分属不同的系统,因此无法做到同时操作成功或失败,因此在并发读写下很可能出现缓存与数据库数据不一致的情况 理论上可以通过分布式事务保证同时操作成功或失败,但这会影响

    2024年02月03日
    浏览(47)
  • 深入理解高并发下的MySQL与Redis缓存一致性问题(增删改查数据缓存的一致性、Canal、分布式系统CAP定理、BASE理论、强、弱一致性、顺序、线性、因果、最终一致性)

    一些小型项目,或极少有并发的项目,这些策略在无并发情况下,不会有什么问题。 读数据策略:有缓存则读缓存,然后接口返回。没有缓存,查询出数据,载入缓存,然后接口返回。 写数据策略:数据发生了变动,先删除缓存,再更新数据,等下次读取的时候载入缓存,

    2024年03月20日
    浏览(53)
  • Redis 原理缓存过期、一致性hash、雪崩、穿透、并发、布隆、缓存更新策略、缓存数据库一致性

    redis的过期策略可以通过配置文件进行配置 redis会把设置了过期时间的key放在单独的字典中,定时遍历来删除到期的key。 1).每100ms从过期字典中 随机挑选20个,把其中过期的key删除; 2).如果过期的key占比超过1/4,重复步骤1 为了保证不会循环过度,导致卡顿,扫描时间上限

    2024年02月08日
    浏览(58)
  • 通过kafka学习数据一致性

    数据从主节点(leader)复制到从节点(follower)的过程中,由于网络延迟、节点故障或其他原因 可能导致从节点未能及时获取或处理主节点的数据变更,从而产生数据不一致 消息提交涉及多个阶段,包括生产者发送消息、消息被写入日志、消息被复制到从节点等。 如果在这

    2024年02月19日
    浏览(37)
  • 缓存和数据库一致性

    项目的难点是如何保证缓存和数据库的一致性。无论我们是先更新数据库,后更新缓存还是先更新数据库,然后删除缓存,在并发场景之下,仍然会存在数据不一致的情况(也存在删除失败的情况,删除失败可以使用异步重试解决)。有一种解决方法是延迟双删的策略,先删

    2024年01月17日
    浏览(45)
  • 使用双异步后,如何保证数据一致性?

    大家好,我是哪吒。 在上一篇文章中,我们 通过双异步的方式导入了10万行的Excel ,有个小伙伴在评论区问我, 如何保证插入后数据的一致性呢? 很简单,通过对比Excel文件行数和入库数量是否相等即可。 那么,如何获取异步线程的返回值呢? 我们可以通过给异步方法添加

    2024年01月23日
    浏览(62)
  • 一致性哈希(哈希环)解决数据分布问题

    哈希算法是程序开发过程中最广泛接触到的的算法之一,典型的应用有安全加密、数据校验、唯一标识、散列函数、负载均衡、数据分片、分布式存储。前些天遇到用一致性哈希(哈希环)的场景,不过我细想一下,对这个知识点好像了解过,但是又没太深印象,说不出具体

    2024年02月04日
    浏览(54)
  • ZooKeeper是如何保证数据一致性的?

             目录 一、分布式一致性原理 二、ZooKeeper架构         2.1 ZAB 协议操作顺序性         2.2 领导者选举         成员身份         成员状态         领导者选举 三、总结         在分布式系统里的多台服务器要对数据状态达成一致,其实是一件很有难度和挑

    2024年04月11日
    浏览(50)
  • 谈了千百遍的数据一致性

    今天来说一个老生常谈的问题,来看一个实际案例: 现有业务中往往都会通过缓存来提高查询效率,降低数据库的压力,尤其是在分布式高并发场景下,大量的请求直接访问Mysql很容易造成性能问题。 有一天老板找到了你...... 老板:听说你会缓存? 你:来看我操作。 你设计

    2024年02月11日
    浏览(34)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包