首页 > 编程知识 正文

redis面试题(mongodb回滚)

时间:2023-05-04 17:29:41 阅读:85362 作者:14

为什么是高可用性?

我们以前用Mysql的时候,总是服务器打天下。 如果只是为了学习没问题,但在生产环境中那样的风险很大。 如果服务器因为网络或崩溃,数据库将暂时不可用。 那样的体验不好。

那么,该怎么办呢? 如果一台机器不行,我坐多台机器就行了吧。 例如,我去两个台上,让他们互相主要准备,互相同步数据。 想到这里我只想说一个字,稳定。

其实redis、mongodb、kafka等分布式APP基本上就是这样的思想

MongoDB也是同样的思想。 此问题将通过复制集解决。 MongoDB复制集由一组包含一个主节点和多个次节点的MongoDB进程组成,MongoDB驱动程序(客户端)中的所有数据都写入到主节点,而次节点

要成为主节点,必须确保大多数节点都同意。 大多数节点都是副本的一半以上的成员。

为什么要求大多数呢? 其实是为了不出现两个primary节点。 例如,五个节点的复制集不允许三个成员使用。 剩下的两个人还在工作。 因为这两个工作节点无法满足复制集的大部分要求,所以当您意识到即使一个是主节点,也无法得到大部分节点的支持时,它将退位并成为备份节点。

如果允许这两个节点选择主节点,那么问题就在于,其他三个节点可能不是实际挂起的,而是网络无法到达。 因为其他三个节点一定可以选择主节点,所以将存在两个主节点。 因此,在许多情况下,要求避免两个主节点的问题。

如果MongoDB复制集可以具有多个主节点,则会遇到写入冲突问题。 在支持多线程写入的系统上,可以手动解决冲突,也可以让操作系统选择一种解决方法,但这两种方法都不容易实现,也不能保证写入的数据不会被其他节点更改,因此

如果备份节点无法连接到主节点,请联系其他复制副本集成员,请求他们将自己选为主节点,其他成员将进行一些合理的检查。 是否可以连接到主节点? 被选为主节点的备份节点的数据是最新的吗? 其他优先级高的成员会被选为主节点吗? 如果选举节点的成员能够获得大部分投票,他们就会成为主节点。 但是,如果大多数成员中只有一人否决这次选举,选举将被取消。

因为在日志中可以看到得票数比较大的负面情况,但一张否决票相当于10000张赞成票。 如果有两张赞成票,两张否决票,选举结果为-19998,同上。

配置选项

通常,在部署期间,复制副本集节点至少有三个。 这是为了允许一个失败。 也就是说,将复制三个数据。

仲裁员。

很多人使用的APP很少,不想存储三个数据,只需要存储两个数据,但存储第三个数据只是徒劳。 对这种部署MongoDB也提供了支持。 有一个被称为仲裁者(arbiter )的特别成员。 唯一的作用是参加选举,不存储数据或为客户端提供服务,只帮助两个成员的复制集满足这一大部分条件。

仲裁者其实也有缺点。 如果一个节点挂起(无法恢复数据),另一个成员称为主节点。 为了数据安全性,需要新的备份节点,并将主节点的数据备份到备份节点。 复制数据会给服务器带来很大的压力,APP速度会变慢。 相反,三个数据成员中的一个挂起时,会有主节点和备份节点,不会影响正常操作。 此时,也可以不依赖主节点,而使用其余的备份节点初始化新的备份节点服务器。 因此,在复制集中尽可能使用奇数个数据成员时,请勿使用仲裁员。

优先级)

如果要增加节点成为主节点的机会,则必须设置优先级。 例如,添加优先级为2的成员。 默认值为1

添加((_标识(:4,主机) : ) 10.17.28.190336027017,优先级) : );

假设其他为默认优先级,如果10.17.28.190有最新数据,则当前的主节点将自动退位,10.17.28.190将被选为新的主节点。 如果数据不是足够新的,则当前的主节点将

保持不变。

如果设置priority为0,表示不会被选为primary节点。

投票权(vote)

由于复制集成员最多50个,而参与Primary成员投票的最多7个,所以其他成员的vote必须设置为0(priority也必须为0)。尽管无投票权的成员不会在选举中投票,但这些成员拥有副本集数据的副本,并且可以接受来自客户端应用程序的读取操作。

隐藏成员(hidden)

客户端不会像隐藏成员发送请求,隐藏成员也不会作为复制源(尽管当其他复制源不可用时隐藏成员)。因此很多人将不够强大的服务器或者备份服务器隐藏起来。通过设置hidden:true可以设置隐藏,只有优先级为0的才能被隐藏。可使用Hidden节点做一些数据备份、离线计算的任务,不会影响复制集的服务

延迟备份节点(slaveDelay)

数据可能会因为人为错误而遭到毁灭性的破坏,为了防止这类问题,可以使用slaveDelay设置一个延迟的备份节点。

延迟备份节点的数据回比主节点延迟指定的时间(单位是秒),slaveDelay要求优先级是0,如果应用会将读请求路由到备份节点,应该将延迟备份节点隐藏掉,以免读请求被路由到延迟备份节点。

因Delayed节点的数据比Primary落后一段时间,当错误或者无效的数据写入Primary时,可通过Delayed节点的数据来恢复到之前的时间点。

修改副本集配置

比如我有个副本集叫做rs0,我想修改增加或者删除成员,修改成员的配置(vote,hidden,priority等)可以通过reconfig命令

http://docs.mongodb.com/manual/reference/method/rs.reconfig/

cfg = rs.conf(); cfg.members[1].priority = 2; rs.reconfig(cfg);

同步

Primary与Secondary之间通过oplog来同步数据,Primary上的写操作完成后,会向特殊的local.oplog.rs特殊集合写入一条oplog,Secondary不断的从Primary取新的oplog并应用。

因oplog的数据会不断增加,local.oplog.rs被设置成为一个capped集合,当容量达到配置上限时,会将最旧的数据删除掉。由于复制操作的过程是先复制数据在写入oplog,oplog必须具有幂等性,即重复应用也会得到相同的结果。

我向test库的coll集合插入了一条数据之后(db.coll.insert({count:1})),调用db.isMaster()命令可以看到当前节点的最后一次写入时间戳

> db.isMaster() { "ismaster" : true, "secondary" : false, "lastWrite" : { "opTime" : { "ts" : Timestamp(1572509087, 2), "t" : NumberLong(1) }, "lastWriteDate" : ISODate("2019-10-31T08:04:47Z"), "majorityOpTime" : { "ts" : Timestamp(1572509087, 2), "t" : NumberLong(1) }, "majorityWriteDate" : ISODate("2019-10-31T08:04:47Z") } }

命令会返回很多数据,这里我只列出了小部分,可以看到我们当前所在节点是master节点(primary),如果当前节点不是primary,也会通过primary属性告诉你当前primary节点是哪个,同时最后一次写入的时间戳是1572509087。

此时我们登录另一台secondary节点,切换到local数据库,执行命令db.oplog.rs.find()命令,会返回很多条数据,这里我们查看最后一条即可

{ "ts" : Timestamp(1572509087, 2), "t" : NumberLong(1), "h" : NumberLong("6139682004250579847"), "v" : 2, "op" : "i", "ns" : "test.coll", "ui" : UUID("1be7f8d0-fde2-4d68-89ea-808f14b326da"), "wall" : ISODate("2019-10-31T08:04:47.925Z"), "o" : { "_id" : ObjectId("5dba959fcf287dfd8727a1bf"), "count" : 1 } }

可以看到oplog的ts和isMater()命令返回的lastTime.opTime.ts的值是一致的,证明我们的数据是最新的,如果你这个时候访问其他节点查看oplog.rs的数据,会发现数据是一模一样的。在来解释下字段含义

ts : 操作时间,当前timestamp + 计数器,计数器每秒都被重置h:操作的全局唯一标识v:oplog版本信息op:操作类型i:插入操作u:更新操作d:删除操作c:执行命令(如createDatabase,dropDatabase)n:空操作,特殊用途ns:操作针对的集合o:操作内容,可以看到我这里插入的字段是count,值是1o2:操作查询条件,仅update操作包含该字段

初始化同步

副本集中的成员启动之后,就会检查自身状态,确定是否可以从某个成员那里进行同步。如果不行的话,它会尝试从副本的另一个成员那里进行完整的数据复制。这个过程就是初始化同步(initial syncing)。

init sync过程包含如下步骤

准备工作删除所有已存在的数据库,以一个全新的状态开始同步将同步源的所有记录全部复制到本地(除了local)oplog同步第一步,克隆过程中的所有操作都会被记录到oplog中,如果有文档在克隆过程中被移动了,就有可能会被遗漏,导致没有被克隆,对于这样的文档,可能需要重新克隆oplog同步过程第二步,将第一个oplog同步中的操作记录下来创建索引如果当前节点的数据仍然远远落后于同步源,那么oplog同步过程的最后一步就是将创建索引期间的所有操作全部同步过来,防止该成员成为备份节点。完成初始化同步之后,切换到普通同步状态,这时当前成员就可以称为备份节点了。

总结起来就是从其他节点同步全量数据,然后不断从Primary的local.oplog.rs集合里查询最新的oplog并应用到自身。

查询固定集合使用的tailable cursor(http://docs.mongodb.com/manual/core/tailable-cursors/)

Primary选举

Primary选举除了在复制集初始化时发生,还有如下场景

复制集reconfigSecondary节点检测到Primary宕机时,会触发新Primary的选举当有Primary节点主动stepDown(主动降级为Secondary)时,也会触发新的Primary选举

Primary的选举受节点间心跳、优先级、最新的oplog时间等多种因素影响。

节点间心跳

复制集成员间默认每2s会发送一次心跳信息,如果10s未收到某个节点的心跳,则认为该节点已宕机;如果宕机的节点为Primary,Secondary(前提是可被选为Primary)会发起新的Primary选举。

心跳是为了知道其他成员状态,哪个是主节点,哪个可以作为同步源,哪个挂掉了等等信息

成员状态:

STARTUP : 刚启动时处于这个状态,加载副本集成功后就进入STARTUP2状态STARTUP2 : 整个初始化同步都处于这个状态,这个状态下,MongDB会创建几个线程,用于处理复制和选举,然后就会切换到RECOVERING状态RECOVERING : 表示运行正常,当暂时不能处理读取请求。如果有成员处于这个状态,可能会造成轻微系统过载ARBITER : 仲裁者处于这个状态DOWN : 一个正常运行的成员不可达,就处于DOWN状态。这个状态有可能是网络问题UNKNOWN : 成员无法到达其他任何成员,其他成员就知道无法它处于什么状态,就会处于UNKNOWN。表明这个未知状态的成员挂掉了。或者两个成员间存在网络访问问题。REMOVED : 被移除副本集时处于的状态,添加回来后,就会回到正常状态ROLLBACK : 处于数据回滚时就处于ROLLBACK状态。回滚结束后,会换为RECOVERING状态,然后成为备份节点。FATAL : 发生不可挽回错误,也不再尝试恢复,就处于这个状态。这个时候通常应该重启服务器

节点优先级

每个节点都倾向于投票给优先级最高的节点优先级为0的节点不会主动发起Primary选举当Primary发现有优先级更高Secondary,并且该Secondary的数据落后在10s内,则Primary会主动降级,让优先级更高的Secondary有成为Primary的机会。

OpTime

最新optime(最近一条oplog的时间戳)的节点才能被选为主,请看上面对oplog.rs的分析。

网络分区

只有大多数投票节点间保持网络连通,才有机会被选Primary;如果Primary与大多数的节点断开连接,Primary会主动降级为Secondary。当发生网络分区时,可能在短时间内出现多个Primary,故Driver在写入时,最好设置大多数成功的策略,这样即使出现多个Primary,也只有一个Primary能成功写入大多数。

复制集的读写设置

Read Preference

默认情况下,复制集的所有读请求都发到Primary,Driver可通过设置Read Preference来将读请求路由到其他的节点。

primary:默认规则,所有读请求发到PrimaryprimaryPreferred:Primary优先,如果Primary不可达,请求Secondarysecondary:所有的读请求都发到secondarysecondaryPreferred:Secondary优先,当所有Secondary不可达时,请求Primarynearest:读请求发送到最近的可达节点上(通过ping探测得出最近的节点)

Write Concern

默认情况下,Primary完成写操作即返回,Driver可通过设置Write Concern来设置写成功的规则。

如下的write concern规则设置写必须在大多数节点上成功,超时时间为5s。

db.products.insert( { item: "envelopes", qty : 100, type: "Clasp" }, { writeConcern: { w: majority, wtimeout: 5000 } } )

上面的设置方式是针对单个请求的,也可以修改副本集默认的write concern,这样就不用每个请求单独设置。

cfg = rs.conf() cfg.settings = {} cfg.settings.getLastErrorDefaults = { w: "majority", wtimeout: 5000 } rs.reconfig(cfg)

回滚(rollback)

Primary执行了一个写请求之后挂了,但是备份节点还没有来得及复制这次操作。新选举出来的主节点就会漏掉这次写操作。当旧Primary恢复之后,就要回滚部分操作。

比如一个复制集存在两个数据中心,DC1中存在A(primary),B两个节点,DC2中存在C,D,E这三个节点。如果DC1出现了故障。其中DC1这个数据中心的最后的操作是126,但是126没有被复制到另外的数据中心。所以DC2中服务器最新的操作是125

DC2的数据中心仍然满足副本集大多数的要求(5台,DC2有3台),因此其中一个会被选举成为新的主节点,这个节点会继续处理后续的写入操作。当网络恢复之后,DC1中心的服务器就会从其他服务器同步126之后的操作,但是无法找到。这种时候DC1中的A,B就会进入回滚过程。

回滚回将失败之前未复制的操作撤销。拥有126操作的服务器会在DC2的服务器的oplog寻找共同的操作点。这里会定位125,这是两个数据中心相匹配的最后一个操作。

这时,服务器会查看这些没有被复制的操作,将受这些操作影响的文档写入一个.bson文件,保存在数据目录下的rollback目录中。

如果126是一个更新操作,服务器会将126更新的文档写入collectionName.bson文件。如果想要恢复被回滚的操作,可以使用mongorestore命令。

本文转载于公众号: 早晚程序员 的原创文章

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。