深入浅析mongodb的一致性模型及其实现

  我们已经知道,在zookeeper中,写请求转发给leader,读请求follower自己处理,没有sync的情况下达不到linearizability。Mongodb呢?官方文档是这么说的,“MongoDB is consistent by default: reads and writes are issued to the primary member of a replica set. ”

  这句话误导了我很多年,让我一直以为默认情况下,读写操作都发给primary,这总应该linearizable了吧?结果得知真相之后让我眼泪掉下来。

  Write Concern,Read Concern和Read Preference

  Mongodb能够提供灵活的一致性,原因在于它可以设置不同的Write Concern,Read Concern和Read Preference。

  Write Concern决定了对数据库写入操作的确认级别,比如是否大多数节点都写入成功了才向客户端返回确认。

  Read Concern决定了读取操作的返回数据的隔离级别,比如是否将大多数节点都希尔成功的数据返回给读操作。

  Read Preference决定了从哪个节点进行读取操作。

  默认情况

  在mongodb 4.0版本中,默认的Read Preference是primary,也就是读写操作都发送给primary。

  默认的Write Concern是w:1,对于副本集来说,就是只要primary确认写入就可以了。所以你以为你写入成功了,但是primary没有把数据分发出去之前就挂掉了,那么你以为写入成功的数据,就消失得无影无踪了。默认的Read Concern呢?居然是local!所以你从primary读到了一个数据x,在x还没被分发时primary挂了,新的primary没有x的信息,那么再从新的primary读数据时,x就好像从来没存在过一样。

  所以,默认情况下,mongodb的一致性和Linearizability差得远呢。

  Majority

  那把Write Concern和Read Concern都设置为majority会怎么样呢?

  Majority的Write Concern指的是数据分发给副本集的大多数成员之后再确认。

  Majority的Read Concern指的是读取到的数据是被副本集大多数成员确认的。

  想象以下情况:

  N1是副本集的primary,N2和N3是两个secondary。客户端S1读写请求都发给N1。

  N1突然发生了网络隔离,不能和N2,N3通信了。

  N2被选举为primary,但是N1对此一无所知,仍然认为自己是primary,所以此时副本集里实际上有两个primary。

  客户端S2以majority的Write Concern写入新的数据到N2。

  N2将数据写入N3后,向S2返回写入确认。

  客户端S1以majority的Read Concern从N1读取数据。

  很容易看出,这种情况下是没有linearizability的。

  

图片描述

  其实,Mongodb还有一种更糟的情况,N1和N2同时作为primary的时候,S1有可能写请求发给N2,读请求发给N1。这样它连自己写入的数据都读不出来。因为mongodb的driver一般都需要连接池,而mongodb又有Read Preference这种实现读写分离的选项,所以实现起来就很麻烦。driver除了官方的以外,还有开源社区维护的,没注意到这些细节的话,很容易出现这种问题。

  而且早期的mongodb中,根本没给你检测stale primary的机会。。。

  https://github.com/mongodb/specifications/blob/master/source/server-discovery-and-monitoring/server-discovery-and-monitoring.rst#using-setversion-and-electionid-to-detect-stale-primaries

  Linearizable Read Concern

  Linearizable Read Concern是mongodb 3.4中出现的,为了解决上面的问题。有了linearizable的Read Concern,终于可以读取副本集中最新的数据啦。Linearizable Read Concern有一些使用上的限制:

  只能用在primary的read操作上

  查询条件必须唯一的确定一个document

  linearizable Read Concern的实现原理是在read操作之前添加了一个空的写操作,如果当前的primary是stale的,空的写操作就会失败,从而被检查出来。实际上和zookeeper的sync如出一辙殊途同归。

  Causal Consistency

  上面的例子中,client的读写都发给了primary,不过总让secondary闲着也不太好,不如搞个读写分离吧。

  不过一开始的读写分离是容易出现问题的:

  N1是副本集的primary,N2和N3是两个secondary。

  客户端S设置Read Preference,可以让读请求发给secondary,写请求发给primary。

  S在N1上以majority的Write Concern写入订单数据x。

  N1把x分发给了N2之后,判定超过半数了,返回成功。

  S在N3上读取x,发现没有这个订单。

  

图片描述

  这真是非常尴尬,自己写的数据,自己都看不到。所以mongodb 3.6增加了Causal Consistency一致性。

  为了实现它,mongodb每个节点都需要能获取正确的时间。时钟是分布式系统非常重要的组成部分,在其他文章中我们会着重介绍。

  现在mongodb这样工作:

  N1是副本集的primary,N2和N3是两个secondary。

  客户端S设置Read Preference,可以让读请求发给secondary,写请求发给primary。

  S在N1上以majority的Write Concern写入订单数据x,记录时间T。

  N1把x分发给了N2之后,判定超过半数了,返回成功。

  S在N3上读取x,读取操作带有T作为参数。

  N3没有时间T的操作,开始等待。

  N3从N1得到x的数据,意味着N3拥有了T时间的数据。

  N3将数据返回给S。

  

图片描述

  为了达到Causal Consistency,mongodb的客户端需要额外进行一些操作,例如开启client session,同时也有相应的一些限制,例如同一时刻只能有一个线程在client session中执行操作。

猜你喜欢

转载自blog.csdn.net/qianfeng_dashuju/article/details/93626406