本篇是在学习BDB JE的使用、阅读源码并参读文档后的笔记,涉及到内部实现细节,写的比较零散,也有很多疏漏,仅作为笔记待查。
本篇是在学习BDB JE的使用、阅读源码并参读文档后的笔记,涉及到内部实现细节,写的比较零散,也有很多疏漏,仅作为笔记待查。
Berkeley DB JE简介及主要特点
Java版Berkeley DB,嵌入式数据库引擎,K/V存储,可提供高可用性(HA)。
日志结构存储(LFS),采用B+树存储节点,Evictor进行内存回收,Cleaner释放磁盘空间。
树的基本结构
每个数据库环境在内存中会建立一棵DbTree。DbTree的结构如下所示。
dbid从2开始分配,0和1被作为保留的dbid序号分配给了iddb和namedb。分别建立iddb和namedb。Namedb中存储数据库名信息databaseName与数据库id作为NameLN,iddb中存储每个db对应的mapLN,mapLN下面存储这个db的数据tree。
内存中的逻辑结构主要通过com.sleepycat.je.tree里的类来实现。
其中,主要的逻辑结构为节点Node,它实现了Loggable接口,这个接口定义了一组将Node的信息写入ByteBuffer的方法。Node分别对应于Tree中的各个逻辑节点,的层次关系如下:
物理存储即日志读写主要通过包com.sleepycat.je.log里的类来实现。
写日志主要用到LogManager的几个log方法,最终体现为调用其内部方法serialLogInternal,里面调用FileManagerd的writeLogBuffer方法。实现将内存中的数据写入文件的操作。
其中,把树中的节点变为日志的中间结构为Entry。在包中定义了3个接口INContainingEntry,LogEntry,NodeLogEntry(继承LogEntry),一共有7个不同的Entry类,根据功能实现不同的接口。Entry的层次关系如下:
在类LogEntryType中定义并初始化了30种不同的LogEntry类型,每个LogEntry包装的logClass类型都实现了Loggable接口。
每个LogEntry再经过包装成为LogItem,进行日志写入。
日志文件
数据文件类型在类FileManager中定义,如下所示:
public static final String JE_SUFFIX = ".jdb"; // regular log files
public static final String DEL_SUFFIX = ".del"; // cleaned files
public static final String BAD_SUFFIX = ".bad"; // corrupt files
private static final String LOCK_FILE = "je.lck";// lock file
/*
* The suffix used to denote a file that is in the process of being
* transferred during a network backup. The file may not have been
* completely transferred, or its digest verified.
*/
public static final String TMP_SUFFIX = ".tmp";
/*
* The suffix used to rename files out of the way, if they are being
* retained during a backup. Note that the suffix is used in conjunction
* with a backup number as described in <code>NetworkBackup</code>
*/
public static final String BUP_SUFFIX = ".bup";
非数据日志的文件:EnvironmentImpl.java中定义了INFO文件je.info,LoggerUtils.java将程序日志(区别于dbd中的数据日志)写到这些文件里面:
private static final String INFO_FILES = "je.info";
节点回收部分(Evictor)
这部分做的是内存回收的工作,主要目的是判断内存空间是否达到阈值,若达到阈值则应用策略回收cache中的节点以释放足够的空间。
节点回收的三部分组件:
仲裁(Arbiter),查询内存预算空间判断是否需要回收。
目标选择器(TargetSelector),选择目标节点。
回收器(Evictor),执行节点回收。
节点回收的三种线程:
1. The application thread, in the course of doing critical eviction (执行)
2. Daemon threads, such as the cleaner or INCompressor, in the course of doing their respective duties (后台守护线程)
3. Eviction pool threads (线程池)
节点回收分为两种类型PrivateEvictor和SharedEvictor,分别对应单/多Environment的情况,但是逻辑上是一样的。
内存预算通过com.sleepycat.je.dbi.MemoryBudget实现。
Cache通过com.sleepycat.je.dbi.INList实现。
目标选择器通过方法selectIN在Cache中取出IN,若使用LRU策略,则挑出最低generation的节点,否则策略优先级由高到低选择:最低tree level脏数据LRU策略。选择IN时的迭代顺序没有特别定义。
对选出的IN,回收器使用方法evictRoot或evictIN回收内存。若IN是一个tree的root,若它的所有子节点都可以被回收,则它也可以被回收,若它是dirty,则需要写入磁盘。对非root的IN,找到其父节点,如果IN是dirty的,则写入磁盘并更新父节点的指针。其中,若IN是BIN,还要先做剪枝,然后将其引用的叶节点释放掉,若其中有数据需要写入磁盘,则将该BIN标记为dirty。
以下几种节点不能被回收:Mapping Tree的节点,内存中正在使用的节点的IN父节点,有游标指向的BIN节点。
Cleaner
Cleaner用来释放磁盘空间。
Cleaner的两个目标:
使migration 的开销尽可能小
Migration操作与用户的其它线程同时来做,避免clean跟不上的情况
JE的树结构中存储了系统数据库_jeUtilization,存储每个文件的利用率信息。
使用lazy migration和proactive migration策略移动节点。Lazy migration是说对LN不是立即移动,而是作以标记在下一次ckpt或者evictor的时候在写BIN时一起flush到日志里面,好处是在写一个BIN时可以尽可能多的将它引用的LN一次写入。
Cleaner做几件事情:1. 标记obsolete;2. 移动entry;3. 删除log文件。
日志中entry可能被obsolete的原因:1. 应用删除/更新了entry;2. Proactive migration主动移动了entry;3. trackDetail被置为false。
待删除文件的几种状态(为了保证文件删除时所有有效内容都被移动):
TO_BE_CLEANED,FlieSelector选出的待clean的文件被标记为该状态
BEING_CLEANED,FileProcessor选择处理的文件被标记为该状态
CLEANED,文件中所有entry被扫描和处理后文件被标记为该状态,这时entry有些未移动有些在pending队列,db也在pending队列
CHECKPOINTED,文件在一个ckpt开始时处于CLEANED状态,则在结束时会被标记为该状态,这时被标记为migrate的该移动的都移动了,但是pending的entry和db还没有处理好
FULLY_PROCESSED,当pending队列处理完时文件被标记为该状态,但此时LN的BIN还没有被重写
SAFE_TO_DELETE,文件在一个ckpt开始时处于SAFE_TO_DELETE状态,则在结束时会被标记为该状态,这个时候就可以删除了
几个记录文件利用率的类:
类UtilizationProfile,记录所有log文件的利用率。
类PackedOffsets,记录一组LSN的偏移量,在summary里用来标记废弃的entrys。
类FileSummary,记录每个log文件的利用率信息,在UtilizationProfile里使用。(summary)
类TrackedFileSummary,记录每个tracked的log文件的增量信息,对应一个FileSummary,并存储obsolete的entry的偏移量列表。(detail)
类DbFileSummary,记录每个db的每个文件的利用率信息,在相应的DatabaseImpl里使用。
类DbFileSummaryMap,文件fileNum到DbFileSummary的映射。
类LNInfo,记录被挂起或加锁的需要之后做migrate的叶节点,在FileProcessor里使用。
类FileProcessor执行清理操作,读取文件中所有entry,并决定entry是obsolete还是migration。LN的migration通过设置BIN实现,IN的migration通过标记dirty实现。简化流程如下:
2. 选择文件(类FileSelector,按利用率最低文件最老的规则选择)。
3. 处理文件(对LN的处理-启用lazy migration时就标记它为migrate同时标记其bin为dirty,禁用lazy migration时就立即加锁执行migrate,若加锁失败则挂起;对IN的处理,标记为dirty。对每个要处理的LN和IN,都会再做一次obsolete验证)。
小结
1. evictor和cleaner在处理时主要解决的问题:
节点/文件选择策略
内存树结构和日志系统的数据一致性
2. 期望达到的目的:
内存中查找的命中率高
磁盘的有效entry比例大
参考文献:
Berkeley DB Java Edition Architecture
BDB_Getting_Started_Guide
Performing Queries in Oracle Berkeley DB Java Edition