先来看看持久化的流程.默认情况下持久化是开启的,需要关闭启动时--nodur或者--nojournal.在开启journal
时mongodb保留了多数据库的两份映射,每一个文件有两个映射的初始地址_view_write和_view_private,
_view_private是为了持久化而生的.这就是为什么用mongostat查看系统信息时会看到vsize是mapped的2倍多
了,因为一份数据有两份映射.
_view_private初始映射时是只读的,因为写时复制所以虽然其和_view_write都映射了数据,但是并未占用更多
的内存.当我们要修改数据时,mongodb会将要修改的数据所在页也就是_view_private部分的页修改属性修改为可
写,然后做实际的修改.这里的修改修改的部分并不是真正的数据库文件,也不是对_view_write映射部分的修改,而是
因为写时复制修改了一份数据库文件的拷贝.这时同一个数据可能在系统中存在两份数据,一份是没有修改的
_view_write,一份是已经修改的_view_private.当修改数据达到一个上限(32位是50M,64位是100M)或者显示的提交
调用(可能是持久化线程的调用会一般的调用),就会产生一次真正的提交,之前修改的_view_private中的记录将会被
压缩,然后存到日志文件中.然后修改_view_write映射的数据.最后由一个专门的线程DataFileSync将数据从
_view_write中刷到磁盘.这里有个问题就是随着写的持续性,_view_private实际占用的内存会不断扩大,所以一段时
间后需要释放映射,然后重新建立新的_view_private的映射.这样空间就被释放了,_view_private实际占用空间回
到0.一般操作如查询,删除,修改等操作使用的都是_view_private映射,所以可以保证最新的数据.
journal目录: 持久化目录.
j._0 j._1 ... j._n: 持久化的日志文件.
prealloc.0 prealloc.1: 预分配的持久化日志文件.
lsn: 记录上一次提交到磁盘的时间戳以及其位反的校验的文件.
需要注意如果在写了数据后持续化过程还没有生成日志时因为一些原因系统crash,那么这部分数据将丢失,
可以明确一次最多丢失100M数据.下面我们实际来看代码.首先从写入动作开始.一次常规的写入如下:
recNew = (Record *) getDur().writingPtr(recNew, lenWHdr);
来看看这里的writingPtr的实现 void* DurableImpl::writingPtr(void *x, unsigned len) {
void *p = x;
declareWriteIntent(p, len);
return p;
}
writingPtr->declareWriteIntent
void DurableImpl::declareWriteIntent(void *p, unsigned len) {
cc().writeHappened();
MemoryMappedFile::makeWritable(p, len);//映射部分_view_private初始化为只读,所以将要写入部分的内存设置为可写
ThreadLocalIntents *t = tlIntents.getMake();//得到tls变量ThreadLocalIntents指针
t->push(WriteIntent(p,len));
}
void ThreadLocalIntents::push(const WriteIntent& x) { //每21次写unspool一次
if( !commitJob._hasWritten )
commitJob._hasWritten = true;
if( n == 21 )//每21次写就将数据从本地线程传递到全局的commitjob中,并清空本地存储
unspool();
i[n++] = x;
}
writingPtr->declareWriteIntent->push->unspool->_unspool->CommitJob::note
void CommitJob::note(void* p, int len) {
// from the point of view of the dur module, it would be fine (i think) to only
// be read locked here. but must be at least read locked to avoid race with
// remapprivateview
if( !_intentsAndDurOps._alreadyNoted.checkAndSet(p, len) ) {//返回false表示新插入的,或者修改了长度的
// remember intent. we will journal it in a bit
_intentsAndDurOps.insertWriteIntent(p, len);//实际的记录操作,将修改的位置和修改长度记录到一个vector中
{
// a bit over conservative in counting pagebytes used
static size_t lastPos; // note this doesn't reset with each commit, but that is ok we aren't being that precise
size_t x = ((size_t) p) & ~0xfff; // round off to page address (4KB)
if( x != lastPos ) {
lastPos = x;
unsigned b = (len+4095) & ~0xfff;
_bytes += b;//需要更新的byte数
if (_bytes > UncommittedBytesLimit * 3) {//超过了3倍需要更新的文件了
static time_t lastComplain;
static unsigned nComplains;
// throttle logging
if( ++nComplains < 100 || time(0) - lastComplain >= 60 ) {
lastComplain = time(0);
warning() << "DR102 too much data written uncommitted " << _bytes/1000000.0 << "MB" << endl;
if( nComplains < 10 || nComplains % 10 == 0 ) {
// wassert makes getLastError show an error, so we just print stack trace
printStackTrace();
}
}
}
}
}
}
}
下面再来看看创建删除文件的操作. void DurableImpl::createdFile(string filename, unsigned long long len) {
shared_ptr<DurOp> op( new FileCreatedOp(filename, len) );//记录创建文件的地址以及其长度
commitJob.noteOp(op);
}
void CommitJob::noteOp(shared_ptr<DurOp> p) {