热门标签 | 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拉取排序是一个归并排序的过程,对性能的影响代价也很小。


说明




推荐阅读
  • 本文将介绍如何编写一些有趣的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. ... [详细]
  • 本文详细介绍了如何在Linux系统上安装和配置Smokeping,以实现对网络链路质量的实时监控。通过详细的步骤和必要的依赖包安装,确保用户能够顺利完成部署并优化其网络性能监控。 ... [详细]
  • 本文详细介绍了 GWT 中 PopupPanel 类的 onKeyDownPreview 方法,提供了多个代码示例及应用场景,帮助开发者更好地理解和使用该方法。 ... [详细]
  • Explore how Matterverse is redefining the metaverse experience, creating immersive and meaningful virtual environments that foster genuine connections and economic opportunities. ... [详细]
  • 1.如何在运行状态查看源代码?查看函数的源代码,我们通常会使用IDE来完成。比如在PyCharm中,你可以Ctrl+鼠标点击进入函数的源代码。那如果没有IDE呢?当我们想使用一个函 ... [详细]
  • 在前两篇文章中,我们探讨了 ControllerDescriptor 和 ActionDescriptor 这两个描述对象,分别对应控制器和操作方法。本文将基于 MVC3 源码进一步分析 ParameterDescriptor,即用于描述 Action 方法参数的对象,并详细介绍其工作原理。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 本文介绍如何使用Objective-C结合dispatch库进行并发编程,以提高素数计数任务的效率。通过对比纯C代码与引入并发机制后的代码,展示dispatch库的强大功能。 ... [详细]
  • 技术分享:从动态网站提取站点密钥的解决方案
    本文探讨了如何从动态网站中提取站点密钥,特别是针对验证码(reCAPTCHA)的处理方法。通过结合Selenium和requests库,提供了详细的代码示例和优化建议。 ... [详细]
  • 深入理解 SQL 视图、存储过程与事务
    本文详细介绍了SQL中的视图、存储过程和事务的概念及应用。视图为用户提供了一种灵活的数据查询方式,存储过程则封装了复杂的SQL逻辑,而事务确保了数据库操作的完整性和一致性。 ... [详细]
  • 本文详细介绍了 Dockerfile 的编写方法及其在网络配置中的应用,涵盖基础指令、镜像构建与发布流程,并深入探讨了 Docker 的默认网络、容器互联及自定义网络的实现。 ... [详细]
  • 本文深入探讨 MyBatis 中动态 SQL 的使用方法,包括 if/where、trim 自定义字符串截取规则、choose 分支选择、封装查询和修改条件的 where/set 标签、批量处理的 foreach 标签以及内置参数和 bind 的用法。 ... [详细]
  • 本文详细介绍了Java中org.eclipse.ui.forms.widgets.ExpandableComposite类的addExpansionListener()方法,并提供了多个实际代码示例,帮助开发者更好地理解和使用该方法。这些示例来源于多个知名开源项目,具有很高的参考价值。 ... [详细]
  • 深入解析Spring Cloud Ribbon负载均衡机制
    本文详细介绍了Spring Cloud中的Ribbon组件如何实现服务调用的负载均衡。通过分析其工作原理、源码结构及配置方式,帮助读者理解Ribbon在分布式系统中的重要作用。 ... [详细]
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社区 版权所有