Download as pdf or txt
Download as pdf or txt
You are on page 1of 33

MongoDB对raft的改进

魏兴华 2022/5
MongoDB的演化
• 多存储引擎支持
• MMAPv1集合级别的锁
• WiredTiger 文 档 级 别 的 • 2017年纳斯达克上市
锁,默认snappy压缩, • 第一个支持ACID的
关闭压缩开关通常不会 NOSQL数据库
带来明显的性能提升,
• 2009年2月发布MongoDB 尽可能不要去动它
1.0,支持复制集 • MMAPv1 DB级别的锁
• 2010 年 8 月, 10gen 发布了 • 2013年8月,更名为
MongoDB 1.6,第四个大版 MongoDB
本,支持Sharding,自动分
片。

• 2016年MongoDB推出
Atlas,在AWS,Azure,
• 10gen
GCP上提供托管服务
• 2014 年 12 月,MongoDB 收
• 2018年11月修改开源授权
购了 Keith Bostic 和 Michael
为SSPL
Cahil 的 WiredTiger 存储引擎
• 2018年12月AWS推出兼容
团队,并将其集成到 3.0
MongoDB的DocumentDB
版本中,性能提升7-10
我在这个时期接触MongoDB 倍。
• 2015年,微软推出兼容
MongoDB的CosmosDB
MongoDB功能列表
• 复制集
• 分片集群
• 强大的查询(aggregate)
• B+Tree 索引
• geospatial(地理空间索引)
• 全文检索(Elasticsearch)
• TTL索引(解决历史数据⾃动过期的需求、“验证码保留15分钟有效期”、“消
息保存7天”)
• GridFS 用于存储超过16M(BSON文件限制)的文件(如:图片、音频、视频等)
• Change Stream
• Join(3.2版本$lookup,left outer join)
• ACID
• 分布式事务
从AGPL到SSPL
• 开源是开源厂家的通行证,也是开源厂家的墓志铭

• MongoDB的协议修改得非常巧妙,它允许对MongoDB开源版本进行托管服务,但是如果要基于以后的版本继续

提供服务,那么,下面的托管平台就必须要开源。也就是说,如果AWS或者阿里云要继续托管MongoDB的最

新版本,底下的管控平台就要开源

• MongoDB推出了自己的Atlas多云服务

• 从MongoDB最新的财报就能看到,Atlas云托管服务已经增长到MongoD整个营收的50%左右

• MongoDB的思路很简单,比起让云厂商提供托管服务并基于MongoDB开源版来占领市场份额,它更希望自己做

托管服务,加上自己的内核,来把蛋糕整个切下来,把云厂商定位成只是做IaaS(Infrastructure as a Service)的一

• MongoDB是一种策略,其他的开源数据库厂商包括商业数据库Oracle、SAP, Oracle做Oracle cloud,SAP也做自己的

SAP Cloud,它们背后的思路和逻辑都是如出一辙
MongoDB三种架构
4.0版本取消了
master-slave架构的
支持
Raft=UnderStandable
mongodb、redis、etcd、cockroachdb
tidb、polardb、consul
zookeeper(like raft)

paper链接:https://web.stanford.edu/~ouster/cgi-bin/papers/raft-atc14
怎么让多个副本的数据保持一致?
相同的初识状态 + 相同的输入 = 相同的结束状态
raft:基于leader的复制

leader:我是霸道总裁,听我的
• 在raft中,leader将客户端请求封装到一个个log entry
• 将这些log entries复制到所有follower节点
• 如果一个写操作被复制到大多数节点,那么之后只要任意大多数节点可用,这个
写操作都是安全的,称作“已提交”的写操作
• 大家按相同顺序应用log entry中的command
• 则每一个状态机的状态肯定是一致的
leader election & log replication
它们是如何工作的
 leader election
• leader,霸道总裁,说一不二,唯我独尊
• 从所有可用节点中选择一个主节点为客户提供服务
• RequestVote RPC类型的信息,候选人在选举期间发起,通知各节点投票

 log replication
• 经典raft:从主节点向从节点推送日志
• 与raft不同,mongodb设计为,从节点从主节点拉取
• AppendEntries RPC类型的信息,由领导者发起,用来复制日志和提供心跳消
息,从节点日志回放信息也是通过AppendEntries RPC
leader election

• 所有节点启动时都是follower状态
• 在一段时间内(mongodb为10秒)如果没有收到来自leader的心跳,从follower切换到candidate,发
起选举
• 如果收到majority的赞成票(含自己的一票)则切换到leader状态;如果收到其他节点当选的消
息或发现有更高term的节点,则主动切换到follower
• 赢得了选举之后,新的leader会立刻给所有节点发消息,广而告之,避免其余节点触发新的选

• leader周期性地向所有跟随者发送心跳消息(即不包含日志项的AppendEntries RPC 消息),通知
大家我是领导者,阻止跟随者发起新的选举
log replication
从leader收到来自客户端的写请求,到返回给客户端,整个过程从
leader的视角来看会经历以下步骤:
① leader append log entry
② leader issue AppendEntries RPC in parallel
③ leader wait for majority response
④ leader apply entry to state machine
⑤ leader reply to client
⑥ leader notify follower apply log

mongodb中,什么时候返回客户端可以由用户进行动态设置,设置
项为writeConcern:
db.products.insert(
{ item: "envelopes", qty : 100, type: "Clasp" },
{ writeConcern: { w: "majority" , wtimeout: 5000 } } )
log 结构
log的结构构成:
• 由顺序编号的log entry组成
• 每个log entry包含客户端执行的command
• 包含产生该log entry时的leader term

上图中,五个节点的日志并不完全一致,raft算法为了保证可用性,并不是强一致性,而
是最终一致性,leader会不断尝试给follower发log entries,直到所有节点的log entries都相同
Term

Term 任期
• 集群不是在选举,就是在运行
• term是一个递增的数字,用来区分不同的选举时,选举可能成功或失败
• 集群运行期间的所有日志都会被打上term的标签
• 选举时的意义是为了选出一个leader,每个leader工作一段时间,之后遭遇了各种各样的事
件,接着选出新的leader继续工作
• 这跟民主社会的选举很像,每一届新的履职期称之为一届任期

上图中,term 3展示了一种比较特殊的情况,还没有选举出leader就结束了,然后会发起新的选
举,这种场景叫split vote
Term

什么时候决定发起选举?
• 生存信息 & 计时器
• 发起选举,增加随机超时机制,减少平票split vote
投票约束
投票需要遵循的规范:
• 在任一任期内,单个节点最多只能投一票
• 候选人知道的信息不能比自己的少
• first-come-first-served

候选人知道的信息不能比自己的少:
• 比较term大小
• 如果term相同,比较日志长短
选举安全
选举安全性
• 即任一任期内最多一个leader被选出
• 系统中同时有多个leader,称为脑裂(brain split),
这会导致数据的覆盖丢失

在raft中,由两点来保证
• 一个节点某一任期内最多只能投一票
• 只有获得majority投票的节点才会成为leader
课堂小练习 1:D会当主吗?
A、B、C、D、E 5个节点,A为Primary,出现故障,D第一个发起了选举,假如网络条件等
都没有问题,D能当选吗?
Node A Node D
Term Timestamp Ops Term Timestamp Ops

1 Today 8:00 x=1 1 Today 8:00 x=1

1 Today 8:01 x=2 1 Today 8:01 x=2

Node C
1 Today 8:04 x=3
Term Timestamp Ops

1 Today 8:00 x=1


Node B Node E
1 Today 8:01 x=2
Term Timestamp Ops Term Timestamp Ops

1 Today 8:00 x=1 1 Today 8:00 x=1

1 Today 8:01 x=2 1 Today 8:01 x=2

1 Today 8:04 x=3


课堂小练习 2:D会当主吗?
A、B、C、D、E 5个节点,A为Primary,A、B出现故障,D第一个发起了选举,假如网络条
件等都没有问题,D能当选吗?
Node A Node D
Term Timestamp Ops Term Timestamp Ops

1 Today 8:00 x=1 1 Today 8:00 x=1

1 Today 8:01 x=2 1 Today 8:01 x=2

Node C
1 Today 8:04 x=3
Term Timestamp Ops

1 Today 8:00 x=1


Node B Node E
1 Today 8:01 x=2
Term Timestamp Ops Term Timestamp Ops

1 Today 8:04 x=3


1 Today 8:00 x=1 1 Today 8:00 x=1

1 Today 8:01 x=2 1 Today 8:01 x=2

1 Today 8:04 x=3


课堂小练习划重点
• 在 Raft 中,不是所有节点都能当选领导者,只有日志较完整的节点,也就是
日志完整度不比半数节点低的节点,才能当选领导者
• 在raft中,也不一定是只有日志最新的节点会当选领导者
如何实现日志的一致?
• Raft 是通过以领导者的日志为准,来实现各节点日志的一致的
• 领导者通过日志复制 RPC 一致性检查,找到跟随者节点上与自己相同日志项的最大索引
值,然后复制并更新覆盖该索引值之后的日志项,实现了各节点日志的一致
例子 1
假如Node A发生了故障:
• NodeB增加节点本地的 current term ,切换到candidate状态
• 投自己一票
• 并行给其他节点发送 RequestVote RPCs
• 等待其他节点的回复

在这个过程中,根据来自其他节点的消
息,可能出现三种结果
• 收到majority的投票(含自己的一票),则赢得选举,成
为leader
• 被告知别人已当选,那么自行切换到follower
• 一段时间内没有收到majority投票,则保持candidate状态,
重新发出选举

第一种情况,赢得了选举之后,新的leader会立刻给所有节
点发消息,广而告之,避免其余节点触发新的选举。
例子2
• 三个节点A、B、C
• A、B同时发起选举,而A的选举消息先到达C,C给A投了一票,当B的消息到达C时,
已经不能满足之前讲过的投票约束:每个term只有一次投票权,而A和B显然都不会给
对方投票
• A胜出之后,会给B、C发心跳消息,节点B发现节点A的term不低于自己的term,知道有
已经有Leader了,于是转换成follower

Node B Node A Node C


Term Timestamp Ops Term Timestamp Ops Term Timestamp Ops

1 Today 8:00 x=1 1 Today 8:00 x=1 1 Today 8:00 x=1

1 Today 8:01 x=2 1 Today 8:01 x=2 1 Today 8:01 x=2

1 Today 8:04 x=3 1 Today 8:04 x=3 1 Today 8:04 x=3


例子3
split vote的场景:
• A、B、C、D四个节点
• Node C、Node D同时成为了candidate,进入了term 4
• Node A投了NodeD一票,NodeB投了Node C一票
• 这就出现了平票 split vote的情况
• 这个时候大家都会等到超时后重新发起选举

平票的情况,延长了系统不可用的时间,没有leader,就不能处理
客户端写请求。

如何避免选举失败,raft的优化:
• raft引入了randomized election timeouts来尽量避免平票情况
• 同时,leader-based 共识算法中,节点的数目都是奇数个,尽量
保证majority的出现
State Machine Safety

上图是一个很热闹的场景:
在时刻(a), s1是leader,在term2提交的日志只赋值到了s1 s2两个节点就crash了
在时刻(b), 任期增加到3,s5成为了leader,日志只赋值到了s5,然后crash
在时刻(c),s1,s2恢复,任期增加到4,s1成为leader,开始接受客户端请求,并且把term2期间的日志复制到了
s3,此刻,可以看出term2对应的日志已经被复制到了majority,因此是committed,可以被状态机应用

时刻(d),不幸的是,s1又crash了,s5重新当选,然后将term3(是集群最新的日志)的日志复制到所有节
点,这就出现了一种奇怪的现象:被复制到大多数节点(或者说可能已经应用)的日志被回滚
State Machine Safety
究其根本,是因为term4时的leader s1在(C)时刻提交了之前term2任期的日志。

raft的优化:
某个leader选举成功之后,不会直接提交前任leader时期的日志,而是通过提交当前任期的日志的时候
“顺手”把之前的(S3节点)日志也提交了。那么问题来了,如果leader被选举后没有收到客户端的
请求呢,那么,在任期开始的时候发立即尝试复制、提交一条空的log

这样就能避免,不会出现(C)时刻的情况,即term4任期的leader s1不会复制term2的日志到s3。而是
如同(e)描述的情况,通过复制-提交 term4的日志顺便提交term2的日志。如果term4的日志提交成
功,那么term2的日志也一定提交成功,此时即使s1 crash,s5也不会重新当选。
raft在mongodb中的扩展
• 过期leader
• chaining 从节点链
• Priority
• new leader catchup
过期leader
经典raft算法下的问题
• raft的论文中看到,leader转换成follower的条件是收到来自
更高term的消息,左图中的情况:如果出现网络分割,
且一直持续,那么stale leader就会一直存在

mongodb的优化
• primary在election timeout时间还没有收到来自majority 节点的
消息时,会主动切换成secondary。这样可以避免过期的
Primary继续对外提供服务(尤其是MongoDB允许
writeConcern:1)
chaining 从节点链
Node C
Term Timestamp Ops
• raft中,主节点向其他节点推送raft log
Node A 1 Today 8:00 x=1
• mongodb中,从节点从主节点拉取日志 Term Timestamp Ops
1 Today 8:00 x=1 1 Today 8:01 x=2

优点 1 Today 8:01 x=2 1 Today 8:04 x=3


• 减少主节点负载
1 Today 8:04 x=3 Node D
Term Timestamp Ops
缺点
Node B 1 Today 8:00 x=1
• 叶子从节点报告进度时,延时会增加
Term Timestamp Ops
1 Today 8:01 x=2
1 Today 8:00 x=1

1 Today 8:01 x=2 Node E


Term Timestamp Ops
1 Today 8:04 x=3
1 Today 8:00 x=1

1 Today 8:01 x=2


dry-run or pre-vote

上图由s1 s2 s3三个节点组成,其中s1是leader,另外两个节点是follower

场景:
(s2)与(s1 s3)之间出现了网络分割,那么按照raft算法,s2会不断的尝试发起选举,意味着不断的增加
term。那么当网络自愈之后,s2将继续发起选举,将消息发送到s1 s3。

集群进入选举状态,这时s1 会切换到follower, s1 s3 将其term修改为57,但s2的log是过旧的,因此s2无法获得


选举,s1 s3会在election timeout后发起选举,其中一个成为term 58的leader。
dry-run or pre-vote
pre-vote价值:
pre-vote 避免了「没有必要的重新选举」(还能避免term膨胀),上述场景中s1会切换到follower,然后s1或者s3
再次发起选举,在这个过程中,由于没有leader,整个系统其实是不可用的(至少不可写)

The replica set can continue to serve read queries if such queries are configured to run on secondaries.
mongodb priority
mongodb设计的priority很有用,比如在multi datacenter deploy的情况下
• raft,民主原则:大家都是平等的
• 基于优先级的选举发生在集群稳定期
• 集群会不断发起选举,直到最高优先级的节点成为primary
• 优先级不同,选举等待时间不同,优先级越高,等待时间越短
• 在选举-投票的过程中,还是必须满足候选者数据足够新的约束
• 只考虑自己和当前主节点的优先级,其他节点优先级不决定选举与否

思考:
一个高优先级的节点被关闭了一段时间后,它发起的重新选举,会被接受吗?
new leader catchup
• 经典raft,“Leader’s log is “the truth””
• mongodb,默认会有2s的catchup时间,如果发现从节点数据比新主新,那么在这时间内会
catchup
Thank you

You might also like