热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

MongoDB里面的混合逻辑时钟

在混合逻辑时钟这篇博客里,我介绍了关于混合逻辑时钟的基本知识,本文介绍一下MongoDB里面的混合逻辑时钟,参考ImplementationofCluster-wideLogica


在混合逻辑时钟这篇博客里,我介绍了关于混合逻辑时钟的基本知识,本文介绍一下MongoDB里面的混合逻辑时钟,参考 Implementation of Cluster-wide Logical Clock and Causal Consistency in MongoDB 。最后,还会介绍如何根据这个混合逻辑时钟解决 MongoShake 里面move chunk的问题。


混合逻辑时钟是MongoDB在3.6版本开始推出的。


ClusterTime


ClusterTime表示的就是节点的混合逻辑时钟(下面也叫“时钟”),其由 组成, 字段是32位的秒级粒度的机器的物理时钟, 是32位的自增计数,其遵循混合逻辑时钟里面所说的规则。


Logical Physical Clocks and Consistent Snapshots in Globally Distributed Databases 里面介绍的,时钟递增是通过“发送”,“接受”事件来触发的。而MongoDB里面消息的“发送”和“接受”并不会导致时钟的变化,只有“写”事件才会触发,也就是说只有用户的 写请求 才会导致时钟的增加,在论文里面讲的是“节点状态发生了变化”,所以读请求并不会导致时钟增加。


ClusterTime可以转换成OpTime写到oplog里面,OpTime的格式是: ,其多了一个 复制协议字段。 (也就是ClusterTime)对应oplog里面就是 ts 字段, 对应的是 t 字段:


{
"ts" : Timestamp(1571389994, 1),
"t" : NumberLong(1),
...
}

下面是ClusterTime时钟递增的伪代码,其实也就是对应混合逻辑时钟这篇博客里介绍的时钟递增的方法。


ClusterTime getNextClusterTime() {
newCounter = 0;
wallClockSecs = now();
// _clusterTime is a current local value of node’s ClusterTime
currentSecs = _clusterTime.getSecs();
if (currentSecs > wallClockSecs) {
newSecs = currentSecs; newCounter =
_clusterTime.getCounter() + 1;
}
else {
newSecs = wallClockSecs;
}
// 要么物理时钟增加,计数清0;要么计数递增。
_clusterTime = ClusterTime(newSecs, newCounter);
return _clusterTime;
}

能否只根据write majority和read majority实现因果一致性?


有一个直观感觉就是,假如我write的时候配置了concern majority,read的时候也配置concern majority不就可以read own write了吗?


答案是不行的,原因是read majority并不是广播读,而是读一个本地的RaftCommitPoint,这可能导致读到的是旧的快照,而不是上次write majority的结果。举个例子,有3个节点:P1(主),S2(从), S3(从)。write majority写了P1, S2,数据更新到最新,而read majority请求发到了S3,其还没有更新快照,这时候本地快照读就读到了老的数据。


so,答案就是还需要ClusterTime。


session内的因果一致性



首先,介绍一下客户端与服务器端交互的简单过程,客户端保存一份ClusterTime的值,MongoDB节点收到写请求触发时钟的递增,同时ClusterTime的值会回复给客户端,该值将会在客户端进行存储。


那如何实现read own write?客户端可以携带
afterClusterTime 参数,表示只读那些大于等于给定时钟的数据,如果当前结点快照位点还没有更新,则会block等待,直到位点更新为止。

我们可以看到,在上图的第5步,客户端携带了 {afterClusterTime: T2} 参数,这个时候该请求发到了secondary结点,而结点位点没有更新的话,会一直等到primary的T2位点传播到当前读的secondary,才把数据返回,这样也就实现了read own write。


通过这个 afterClusterTime 可以实现一个 等待 的逻辑,那么这里有个问题,假如各种原因,当前结点一直没有更新到T2怎么办?比如sharding情况,写的是shard1,但是读的是shard2,恰好shard2一直没有写流量,导致位点一直没有更新。MongoDB的解决方式就是通过添加noop实现:mongod定期(默认10s)写一个心跳请求(也叫noop)到oplog,这样如果读错节点,最多经过一个心跳周期,位点也会进行更新(这个例子会返回空数据),而不会导致一直block。




客户端攻击的风险


上面我们介绍了,客户端写可以携带ClusterTime给MongoDB,那假如客户端是一个恶意程序,携带了一个非常大的时间戳,比如最大的timestamp,那么服务端对其自增时肯定会出错,那么如何解决?


MongoDB通过对客户端增加签名机制来实现,客户端发送需要携带一个签名,MongoDB收到请求以后,一旦发现客户端携带的ClusterTime大于当前结点的ClusterTime,就会对这个签名进行验证,查看是否是正常的签名,不是的话就会拒绝掉这个请求。关于签名的细节可以参考 Implementation of Cluster-wide Logical Clock and Causal Consistency in MongoDB 中的A1.4小节。


逻辑时钟与物理时钟差距的限制


在某些极端情况下,逻辑时钟可能远远领先于物理时钟,假如逻辑时钟真的跑到了最大的时间戳,那么这个值就没办法更新了。为了限制这种情况的发生,MongoDB加了一个差值的阈值maxAcceptableLogicalClockDriftSecs,也就是逻辑时钟和物理时钟的差值不能超过这个阈值(默认1年)。这个差值的限制也是混合逻辑时钟的特性:逻辑时钟和物理时钟的差值有一个上确界。


MongoShake解决sharding的move chunk问题


a. 3.6以前版本move chunk的问题


对于sharding,MongoShake是直接拉取源端所有节点的oplog,然后进行并行回放的方式进行实现。也就是说,不同shard之间是没有交互的,在正常情况下,这个是ok的,但一旦发送move chunk,这个就有问题了,如下图所示,shard1上面写入了一条 {a:1} ,然后发生了move chunk,这条数据跑到了shard2,shard2后面又执行了一个更新操作,把 {a:1} 改成了 {a:3} MongoShake对于不同的shard是并行拉取回放的,我们 并不能 保证shard1的这个update操作一定先于shard2的update写入,所以并发同步就有问题了。为了解决这个问题,MongoShake在v2.2代码里做了很多工作,在大体上可以解决这个问题,但是会有一些的corner case,导致对性能会有较大的影响,极端情况下正确性也会有问题。


b. 3.6以后的解决方式


在3.6以及之后的版本,我们可以根据逻辑时钟可以排序的特性(如果事件 a happened before b ,那么 a 的时钟一定小于 b ,反之不成立)来解决move chunk的问题。


对于sharding,MongoShake可以拉取所有shard的oplog,剔除move chunk的报文(含fromMigate字段),然后对所有oplog进行排序即可。因为一旦发生move chunk,则必然有happened before的关系,从而排序可以解决因果一致性。但这个也是只对3.6以后才可以,3.6以前的ts字段并不是混合逻辑时钟,所以没办法排序。在MongoShake的v2.4版本,我们将会用这种方式解决move chunk的问题。不同shard的oplog拉取排序是一个归并排序的过程,对性能的影响代价也很小。


说明




推荐阅读
  • 本文详细介绍了如何在Linux系统上安装和配置Smokeping,以实现对网络链路质量的实时监控。通过详细的步骤和必要的依赖包安装,确保用户能够顺利完成部署并优化其网络性能监控。 ... [详细]
  • 本文详细探讨了JDBC(Java数据库连接)的内部机制,重点分析其作为服务提供者接口(SPI)框架的应用。通过类图和代码示例,展示了JDBC如何注册驱动程序、建立数据库连接以及执行SQL查询的过程。 ... [详细]
  • 深入解析JVM垃圾收集器
    本文基于《深入理解Java虚拟机:JVM高级特性与最佳实践》第二版,详细探讨了JVM中不同类型的垃圾收集器及其工作原理。通过介绍各种垃圾收集器的特性和应用场景,帮助读者更好地理解和优化JVM内存管理。 ... [详细]
  • 本文详细介绍了 GWT 中 PopupPanel 类的 onKeyDownPreview 方法,提供了多个代码示例及应用场景,帮助开发者更好地理解和使用该方法。 ... [详细]
  • 本文将介绍如何编写一些有趣的VBScript脚本,这些脚本可以在朋友之间进行无害的恶作剧。通过简单的代码示例,帮助您了解VBScript的基本语法和功能。 ... [详细]
  • Explore a common issue encountered when implementing an OAuth 1.0a API, specifically the inability to encode null objects and how to resolve it. ... [详细]
  • 本文介绍如何使用Objective-C结合dispatch库进行并发编程,以提高素数计数任务的效率。通过对比纯C代码与引入并发机制后的代码,展示dispatch库的强大功能。 ... [详细]
  • 本文详细介绍了 Dockerfile 的编写方法及其在网络配置中的应用,涵盖基础指令、镜像构建与发布流程,并深入探讨了 Docker 的默认网络、容器互联及自定义网络的实现。 ... [详细]
  • DNN Community 和 Professional 版本的主要差异
    本文详细解析了 DotNetNuke (DNN) 的两种主要版本:Community 和 Professional。通过对比两者的功能和附加组件,帮助用户选择最适合其需求的版本。 ... [详细]
  • 本章将深入探讨移动 UI 设计的核心原则,帮助开发者构建简洁、高效且用户友好的界面。通过学习设计规则和用户体验优化技巧,您将能够创建出既美观又实用的移动应用。 ... [详细]
  • 图数据库中的知识表示与推理机制
    本文探讨了图数据库及其技术生态系统在知识表示和推理问题上的应用。通过理解图数据结构,尤其是属性图的特性,可以为复杂的数据关系提供高效且优雅的解决方案。我们将详细介绍属性图的基本概念、对象建模、概念建模以及自动推理的过程,并结合实际代码示例进行说明。 ... [详细]
  • 本文探讨了MariaDB在当前数据库市场中的地位和挑战,分析其可能面临的困境,并提出了对未来发展的几点看法。 ... [详细]
  • 探讨如何从数据库中按分组获取最大N条记录的方法,并分享新年祝福。本文提供多种解决方案,适用于不同数据库系统,如MySQL、Oracle等。 ... [详细]
  • MySQL 数据库迁移指南:从本地到远程及磁盘间迁移
    本文详细介绍了如何在不同场景下进行 MySQL 数据库的迁移,包括从一个硬盘迁移到另一个硬盘、从一台计算机迁移到另一台计算机,以及解决迁移过程中可能遇到的问题。 ... [详细]
  • 本文介绍如何在SQL Server中对Name列进行排序,使特定值(如Default Deliverable Submission Notification)显示在结果集的顶部。 ... [详细]
author-avatar
137381372_e57647
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有