def setup() { synchronized { queueSize = 0 replayJournal() } } def replayJournal() { if (!config.keepJournal) return log.info("Replaying transaction journal for '%s'", name) xidCounter = 0 journal.replay { case JournalItem.Add(item) => _add(item) // when processing the journal, this has to happen after: if (!journal.inReadBehind && queueSize >= config.maxMemorySize.inBytes) { log.info("Dropping to read-behind for queue '%s' (%d bytes)", name, queueSize) journal.startReadBehind() } case JournalItem.Remove => _remove(false, None) case JournalItem.RemoveTentative(xid) => _remove(true, Some(xid)) xidCounter = xid case JournalItem.SavedXid(xid) => xidCounter = xid case JournalItem.Unremove(xid) => _unremove(xid) case JournalItem.ConfirmRemove(xid) => openTransactions.remove(xid) case JournalItem.Continue(item, xid) => openTransactions.remove(xid) _add(item) case x => log.error("Unexpected item in journal: %s", x) } log.info("Finished transaction journal for '%s' (%d items, %d bytes) xid=%d", name, queueLength, journal.size, xidCounter) journal.open() // now, any unfinished transactions must be backed out. for (xid <- openTransactionIds) { journal.unremove(xid) _unremove(xid) } }
我们看到setup方法主要是回放journal文件中的redo Log。我们看到该类主要是读Journal文件,并解析为JournalItem对象,并根据对象类型对PersistentQueue进行入队和出队。我们看到一共有7种不同的日志类型,下面我们看看几个主要的类型日志的结构
ADDX:
Remove:
我们会看到在回放日志文件的同时,他会记录所有到目前为止所有的没有结束的事务在openTransactions中,其中他们的key的集合为openTransactionIds,对这些没有结束的事务做unremove操作。
另外还有一个关键是在对队列进行Add和Remove操作时调用checkRotateJournal()方法,即检查是否需要对日志文件进行切分、删除等操作。下面我们看看具体的代码
// you are holding the lock, and config.keepJournal is true. private def checkRotateJournal() { /* * if the queue is empty, and the journal is larger than defaultJournalSize, rebuild it. * if the queue is smaller than maxMemorySize, and the combined journals are larger than * maxJournalSize, rebuild them. (we are not in read-behind.) * if the current journal is larger than maxMemorySize, rotate to a new file. if the combined * journals are larger than maxJournalSize, checkpoint in preparation for rebuilding the * older files in the background. */ if ((journal.size >= config.defaultJournalSize.inBytes && queueLength == 0) || (journal.size + journal.archivedSize > config.maxJournalSize.inBytes && queueSize < config.maxMemorySize.inBytes)) { log.info("Rewriting journal file for '%s' (qsize=%d)", name, queueSize) journal.rewrite(openTransactionIds.map { openTransactions(_) }, queue) } else if (journal.size > config.maxMemorySize.inBytes) { log.info("Rotating journal file for '%s' (qsize=%d)", name, queueSize) val setCheckpoint = (journal.size + journal.archivedSize > config.maxJournalSize.inBytes) journal.rotate(openTransactionIds.map { openTransactions(_) }, setCheckpoint) } }
1. 当内存中queue为空,并且日志文件的大小大于defaultJournalSize时,将创建一个新的日志文件并删除该时间点之前的文件。
2. 当之前的日志文件大小加上当前日志文件大小大于maxJournalSize,并且queue中数据的大小小于maxMemorySize时,将创建一个新的日志文件并删除该时间点之前的文件。
3. 当日志文件的大小大于maxMemorySize,将创建新的日志文件,之后达到的消息将存入新的文件。