Mongodump的archive(归档)模式原理解析

Mongodump是MongoDB官方的逻辑备份工具,本文将介绍其archive(归档)模式的相关原理。

archive模式

使用archive输出的话,mongodump会将各个集合的数据进行一个混合,最后输出到一个文件里。这样有两个好处:

  1. 方便进行网络传输。
  2. 方便和其他工具配合使用如直接作为mongorestore的输入。

Mongodump的archive模式的实现支持多个集合并发进行dump,通常来说要实现并发就很难做到输出到一个文件中,我们来看下它是怎么做的。

流程

Mongodump使用go写的,因此大量使用了goroutine。主要需要关注两类goroutine,一类是dump数据的(可指定并行度即goroutine个数)Backup goroutine,还有一个则是混合各集合数据并真正输出到归档文件的Multiplexer goroutine。如下图所示:
Mongodump的archive(归档)模式原理解析

初始

这个Multiplexer goroutine主要通过一个select调用来通过channel接收backup goroutine过来的数据。它被设计成可以动态添加和删除select的channel,初始化时会有一个control channel用于接收需要监听哪些channel。当一个backup goroutine开始准备对某个集合进行备份时,它会创建3个channel:一个用于发送集合数据给Multiplexer goroutine的writeChan、一个用于从Multiplexer goroutine接收写成功数据长度的writeLenChan,还有一个用于从Multiplexer goroutine接收集合dump完毕信号的writeCloseFinishedChan。除了这3个channel之外,它还会创建一个16MB大小的buf。它把这些数据封装在一个MuxIn数据结构里,通过control channel发送给Multiplexer goroutine。
Mongodump的archive(归档)模式原理解析
当Multiplexer goroutine启动后就不停的执行select(),当它从control channel收到MuxIn数据结构时,就会将其中的writeChan添加到select的channel中,并将这个MuxIn保存在自己的数组中。然后下一轮循环就会开始监听control channel和这个writeChan。

dump过程

每个Backup goroutine开始干活时,它一边从mongodb dump数据,一边将数据通过writeChan发送给Multiplexer goroutine。这是一个典型的生产者消费者模型,为了不让消费者的磁盘I/O影响到生产者(从mongodb读数据),它又起了一个Dump goroutine,通过一个buffChan来传输数据,顺便在这个goroutine中处理退出信号(主goroutine起了一个信号处理goroutine用于捕获SIGTERM、SIGINT和SIGHUP,再通过一个termChan和其他goroutine通信),当接收到退出信号时,关闭buffChan并退出。
Mongodump的archive(归档)模式原理解析

Backup goroutine的buf收满之后,就将数据通过writeChan发送给Multiplexer goroutine,并等待从writeLenChan接收数据,如果返回的写成功的数据长度和buf的数据长度不一致 ,则返回失败。

Multiplexer goroutine从writeChan收到数据,将数据写入归档文件。由于同时可能有多个Backup goroutine在工作,因此需要对数据进行混合。这里主要是按集合进行分条(slice)处理,每个条有一个header、body和一个terminator。Header就是集合的Namespace(db和集合名),body是集合的数据,terminator是一个4字节的结束标记。另外每个集合的最后一条之后还有一个标明集合结束的EOF条。Multiplexer goroutine会维护一个当前正在处理的集合名,如果发生集合数据交叉,会结束当前条(写入一个terminator),然后另起一个新的条(写入一个新的header)。写完数据后,会将这次写成功的数据通过writeLenChan发送给Backup goroutine。归档文件的物理格式如下图所示:
Mongodump的archive(归档)模式原理解析

结束

Backup goroutine针对某个集合dump结束时,会依次关闭writeChan、writeLenChan,然后等待从writeCloseFinishedChan接收数据。Multiplexer goroutine感知到writeChan关闭时,发送数据给writeCloseFinishedChan,然后往当前条写入条结束标记以及EOF条。这里之所以要有一个writeCloseFinishedChan是为了避免Multiplexer goroutine的control channel比writeChan先关闭。如果control channel关闭了,那就会结束整个dump过程。Backup goroutine从writeCloseFinishedChan接收到数据返回后又可以处理下一个集合,直到所有集合处理完毕。

Goroutine们何时退出

主goroutine在创建Backup goroutine时会创建一个resultChan。当Backup goroutine发现所有集合都dump完毕时,或者在dump过程中遇到错误时,都会往resultChan发送数据并退出。当主goroutine从resultChan接收到每个Backup goroutine发送的数据时,或者收到某个Backup goroutine发送的错误,就会返回。当主goroutine干完所有事情返回时,它会关闭Multiplexer goroutine的control channel,这样Multiplexer goroutine就可以退出了。

结语

Mongodump通过这种架构实现了输出到一个归档文件兼顾支持并发的需求,可以作为一个参考。

参考文献

  1. Multiplexing Golang Channels to Maximize Throughput
上一篇:MongoDB副本集回滚那些事


下一篇:使用SharpSvn方便调用svn