H2 Database MVStore 初始化

作用

MVStore(multi-version store):
H2 的默认存储子系统,支持多版本,持久化的、日志结构 的 键值存储。

功能

1
2
3
4
5
6
7
8
9
10
11
12
Maps
每个 store 包含多个 mvMaps,可以通过 java.util.Map 接口访问
Versions
支持多版本
Transactions
支持事务(并发事务&两阶段提交)
Concurrent Operations and Caching
支持并发读写
Log Structured Storage
日志结构化存储
支持文件存储和内存操作
支持可插拔

文件存储格式

1
2
3
4
5
# 文件存储格式
[ file header 1 ] [ file header 2 ] [ chunk ] [ chunk ] ... [ chunk ]

# chunk 存储格式
[ header ] [ page ] [ page ] ... [ page ] [ footer ]
1
2
3
4
5
结构
File Header 文件头
Chunk Format 块
Page Format 页
Metadata Map 元数据

FileHeader

文件头有两个 file header 1 和 file header 2,内容完全相同。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
file header 1: 
H:2
代表H2数据库
block:2
最新 block 之一的开始 blockNumber(未必是最新)
blockSize:1000
默认固定是 4096,对应 hex 1000
chunk:7
chunk id,通常与 version 相同
clean:1
是否是clean shutdown标识,官方文档上没有提到
created:1441235ef73
自 1970 年创建文件以来的毫秒数
format:1
目前固定1
version:7
chunk 的版本号
fletcher:3044e6cc
fileHeader对应的 Fletcher-32 校验和
filer header 2:
内容和 header1 完全相同

chunks

每个版本对应一个 chunk,一个 chunk 可能对应多个 block。
每个 chunk 只包含在该版本中被修改的 page,以及这些 page 的所有父节点,递归到根 page。
如果 map 中的内容被更改、删除或添加,则会复制相应的 page,并在下一个 chunk 中存储修改后的 page。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
header
chunk:1
chunk的ID
block:2
chunk的第一个block的编号(乘以block大小可以得到文件中的位置)
len:1
块的大小(以 block 数量为单位)
map:6
最新map的ID,每次创建新map时递增
max:1c0
所有最大 page 大小的总和
next:3
下一个chunk的起始 block
pages:2
chunk中 page 的数量
root:4000004f8c
元数据根页面(page)的位置
time:1fc
chunk被写入的时间,从文件创建后的毫秒数开始计算
version:1
chunk的版本号
pages:
参见下面的 pages 结构
footer:
chunk:1
block:2
version:1
fletcher:aed9a4f6

pages

存储maps的实际数据。
每个map都是一个btree。
map内容存储的是字节数组。
Page 对象表示了 B+树 的一个节点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
存储结构
length
页面的字节数
checksum
校验和,计算方法为 chunk id 异或 page 在 chunk 中的偏移量 offset 异或 page length。
mapId
该页面所属 map 的 ID
len
该页面中 key 的数量
type (byte)
页面的类型。叶节点:0;内部节点:1
children
子节点位置 (long 类型数组;仅仅是内部节点)
childCountsChild
页面的数量
keys
字节数组,数组存储了该节点的所有键,类型取决于数据类型
values
字节数组,(仅适用于叶子节点)存储了该节点的所有值,类型取决于数据类型

Metadata Map

元数据映射,包含 用户映射的名称和位置以及 chunk metadata。chunk 的最后一页包含该元数据映射的 root page。该页面(直接或间接)指向所有其他地图的根页面。

1
2
3
4
5
6
7
8
9
chunk.xxx:
块 xxx 的元数据。这与 chunk header 的数据相同,加上活动页数和最大活动长度。
map.xxx:
map xxx 的元数据。条目为:name、createVersion 和 type。
name.data:
名为 data 的 map & map id
root.xxx:
map xxx 的根位置。
setting.storeVersion:

流程

打开 mvStore 流程

MVStore#open 流程如下。启动阶段会根据 H2 的文件存储,创建对应的 fileChannel。创建 MVMap,然后根据文件存储的内容恢复 MVMap 内存结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
org.h2.mvstore.MVStore#open 
打开 mvStore
org.h2.mvstore.MVStore#<init>
创建mvStore
org.h2.mvstore.SingleFileStore#<init>
1.创建文件存储
org.h2.mvstore.FileStore#<init>
org.h2.mvstore.cache.CacheLongKeyLIRS#<init>
创建 page 缓存
org.h2.mvstore.SingleFileStore#open
2.打开文件存储 fileChannel
org.h2.mvstore.SingleFileStore#open
fileChannel#open
打开文件存储
org.h2.mvstore.SingleFileStore#lockFileChannel
文件存储 channel 加锁
org.h2.mvstore.FileStore#bind
3.文件存储绑定 mvStore
org.h2.mvstore.MVMap#<init>
创建 mvMap,先创建一个 empty 叶子节点&原子设置 mvMap 的根 page 节点
org.h2.mvstore.MVMap#setRootPos
设置根节点
org.h2.mvstore.MVMap#readOrCreateRootPage 
从存储中读取或创建 root page
org.h2.mvstore.MVMap#setInitialRoot
原子设置根节点
org.h2.mvstore.MVMap#setWriteVersion
设置 write version
org.h2.mvstore.FileStore#start
4.启动文件存储
读取文件存储,初始化 root page 设置到 mvMap

FileStore#start

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
1.若存储为空
1.1.初始化公共头部属性
1.2.初始化存储 header
写出 header 到存储
2.若存储不为空
readStoreHeader 从文件存储中读取 & 恢复 header
1.解析文件头&定位 newest chunk
读取前两个block(两个header)的数据到 ByteBuffer 中
依次读取文件头
解析头部属性并做checksum
读取header里的version
校验两个header里的version相同
读取文件头里的 chunk id 和 block
readChunkHeaderAndFooter
读取 chunk header
根据 blockNumber * blockSize 定位到 chunk header 的 position 进行 chunk header 读取
创建 chunk 对象校验 chunk 的起始 block 和文件头里 block 是否一致
读取 Chunk Footer
chunk header 里记录了当前 chunk 的 block 数量,所以加上 blockNumber 偏移量能定位到 chunk footer 位置
读取 chunk footer 并解析属性创建 chunk 对象,校验 blockNumber
chunk 读取成功则更新 newest chunk
2.读取最新的20个chunks
setLastChunk(newest) 读取最新 chunk 里的 root page 设置到 mvMap
1.磁盘读取 root page
chunk header 里存储了 root page 的位置,可以定位到 root page
先从 cache 读取 page,没有就从文件读取后放入 CacheLongKeyLIRS 缓存
2.原子设置 root page 到 mvMap