Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

Vinllen Chen


但行好事,莫问前程

MongoDB里面的混合逻辑时钟

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

ClusterTime

  ClusterTime表示的就是节点的混合逻辑时钟(下面也叫“时钟”),其由<Time><Increment>组成,<Time>字段是32位的秒级粒度的机器的物理时钟,<Increment>是32位的自增计数,其遵循混合逻辑时钟里面所说的规则。
  Logical Physical Clocks and Consistent Snapshots in Globally Distributed Databases里面介绍的,时钟递增是通过“发送”,“接受”事件来触发的。而MongoDB里面消息的“发送”和“接受”并不会导致时钟的变化,只有“写”事件才会触发,也就是说只有用户的写请求才会导致时钟的增加,在论文里面讲的是“节点状态发生了变化”,所以读请求并不会导致时钟增加。
  ClusterTime可以转换成OpTime写到oplog里面,OpTime的格式是:<Time><Increment><ElectionTerm>,其多了一个<ElectionTerm>复制协议字段。<Time><Increment>(也就是ClusterTime)对应oplog里面就是ts字段,<ElectionTerm>对应的是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的值会回复给客户端,该值将会在客户端进行存储。
1.png
  那如何实现read own write?客户端可以携带afterClusterTime参数,表示只读那些大于等于给定时钟的数据,如果当前结点快照位点还没有更新,则会block等待,直到位点更新为止。 2.png
我们可以看到,在上图的第5步,客户端携带了{afterClusterTime: T2}参数,这个时候该请求发到了secondary结点,而结点位点没有更新的话,会一直等到primary的T2位点传播到当前读的secondary,才把数据返回,这样也就实现了read own write。
  通过这个afterClusterTime可以实现一个等待的逻辑,那么这里有个问题,假如各种原因,当前结点一直没有更新到T2怎么办?比如sharding情况,写的是shard1,但是读的是shard2,恰好shard2一直没有写流量,导致位点一直没有更新。MongoDB的解决方式就是通过添加noop实现:mongod定期(默认10s)写一个心跳请求(也叫noop)到oplog,这样如果读错节点,最多经过一个心跳周期,位点也会进行更新(这个例子会返回空数据),而不会导致一直block。
3.png

客户端攻击的风险

  上面我们介绍了,客户端写可以携带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}4.png
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拉取排序是一个归并排序的过程,对性能的影响代价也很小。

说明

转载请注明出处:http://vinllen.com/mongodbli-mian-de-hun-he-luo-ji-shi-zhong/

参考

https://dl.acm.org/doi/pdf/10.1145/3299869.3314049?download=true
http://vinllen.com/hun-he-luo-ji-shi-zhong/
https://cse.buffalo.edu/tech-reports/2014-04.pdf
https://docs.mongodb.com/manual/reference/command/replSetGetStatus/
https://docs.mongodb.com/manual/reference/bson-types/#document-bson-type-timestamp
http://www.mongoing.com/archives/25302
https://github.com/alibaba/MongoShake/issues/65


About the author

vinllen chen

Beijing, China

格物致知


Discussions

comments powered by Disqus