MaxMind DB 文件格式规范
来源:http://maxmind.github.io/MaxMind-DB/
翻译:御风(TX:965551582)2017-03-23php
--------------------------------------------------
描述(Description)
MaxMind DB 文件格式是使用高效的二进制搜索树将IPv4和IPv6地址映射到数据记录的数据库格式java
--------------------------------------------------
版本(Version)
这个文档是版本2.0的MaxMind DB二进制格式的规范
版本号由单独的主版本和次要版本号组成。它不该被认为是十进制数。也就是说,版本2.10在版本2.9以后
可以读取给定格式的主要版本的代码应适用小版本格式node
--------------------------------------------------
概述(Overview)python
二进制数据库被分红三个部分:git
一、二叉搜索树。每一个级别的树对应于一个位在128位表示的IPv6地址
二、数据段。这些值返回到客户端的特定IP地址,如“US”,“New York”,或一个更复杂的Map类型组成的多个字段
三、数据库元数据。数据库自己的信息github
--------------------------------------------------
数据库元数据(Database Metadata)
数据库的这一部分存储在文件的结尾。它最早记录,由于了解一些元数据是了解其余部分如何工做的关键算法
经过查找二进制序列匹配,能够找到此节“\\xab\\xcd\\xefMaxMind.com”。文件中此字符串的最后一次出现标志着数据段的结束和元数据的开始。
因为咱们容许任意的二进制数据的数据段,其余一些数据能够包含这些值。这就是为何你须要找到这个序列最后一次出现位置的缘由数据库
对于元数据部分所容许的最大大小,包括标记开始的元数据,是128KB
元数据存储为Map数据结构。这种结构的规范在稍后描述。改变键的数据类型或删除键,将会影响主版本
除非另有规定,数据库所需的每一个键都被认为是有效的
添加键构成小版本更改。删除键或更改其类型则构成主版本更改数组
当前格式版本的已知键列表以下:缓存
node_count
这是一个无符号的32位整数,表示搜索树中的节点数
record_size
这是一个无符号的16位整数。它指示搜索树中记录中的位数。注意每一个节点由两个记录组成
ip_version
这是一个无符号的16位整数,老是为4或6。它指示数据库是否包含IPv4或IPv6地址数据
database_type
这是一个字符串,指示与IP地址相关联的每一个数据记录的结构。这些结构的实际定义由数据库建立者决定
以“GeoIP”开头的名字是保留给MaxMind使用的标记,“GeoIP”也是一个保留的标记
languages
一个字符串数组,每一个字符串都是一个区域代码。给定的记录可能包含已定位到某些或全部这些区域的数据项。记录不该该包含不包含在这个数组中的局部地区数据
这是一个可选的键,由于这可能并不适用于全部类型的数据
binary_format_major_version
这是一个无符号的16位整数,表示数据库二进制格式的主要版本号
binary_format_minor_version
这是一个无符号的16位整数,表示数据库二进制格式的次要版本号
build_epoch
这是一个无符号的64位整数,表示数据库生成时Unix的时间戳
description
这个键始终指向一个Map。Map的键是语言代码,值在对应语言里是UTF-8编码的字符串
代码可能包括脚本或国家标识符等附加信息,像“zh-TW”或“mn-Cyrl-MN”。附加标识符将由破折号字符分隔(“-”)
这是一个可选的键,不过仍是建议建立者至少包含一种以上的语言描述
--------------------------------------------------
计算搜索树截面大小(Calculating the Search Tree Section Size)
计算搜索树的字节大小的公式以下:
(($record_size * 2) / 8) * $number_of_nodes
搜索树的结束标志着数据段的开始
--------------------------------------------------
二叉搜索树部分(Binary Search Tree Section)
数据库文件以二进制搜索树开始。在树中的节点的数目取决于有多少惟一的网络块(网段)是特定数据库所需的。例如,城市数据库比国家数据库须要更多小的网络块(网段)
最顶部的节点老是位于搜索树节的地址空间的开始处。
每一个节点由两个记录组成,每一个记录是指向文件中地址的指针。
指针能够指向三个事物中的一个。
首先,它可能指向搜索树地址空间中的另外一个节点。下列指针做为IP地址搜索算法的一部分,以下所述
指针能够指向一个值等于$number_of_nodes的地址。若是是这种状况,则意味着咱们正在搜索的IP地址不在数据库中
最后,它可能指向数据段中的一个地址。这是给定的网络块(网段)有关的数据
--------------------------------------------------
节点布局(Node Layout)
搜索树中的每一个节点由两个记录组成,每个都是指针。记录大小因数据库而异,但单个数据库节点记录内的大小始终相同。记录长度能够从24位到128位,依赖于在树中的节点的数目。
这些指针存储在大端(Big Endian)格式(大字节序、高字节序)
下面是一些如何在2四、28和32位记录的节点中设置记录的例子。大多数的记录大小遵循一样的模式
24位(小数据库),一个节点是6字节
| <------------- node --------------->|
| 23 .. 0 | 23 .. 0 |
28位(中等数据库),一个节点是7字节
| <------------- node --------------->|
| 23 .. 0 | 27..24 | 27..24 | 23 .. 0 |
注意,每一个指针的最后4位被组合到中间字节中
32位(大数据库),一个节点是8字节
| <------------- node --------------->|
| 31 .. 0 | 31 .. 0 |
--------------------------------------------------
搜索查找算法(Search Lookup Algorithm)
首先将IP地址转换成大端二进制数据。对于IPv4地址,就是32位数据。对于IPv6就是128位数据
最左边的点对应于在搜索树的第一个节点。对于每个比特,值为0表示咱们选择节点中的左记录,值为1表示选择节点中的右记录
记录值老是被解释为无符号整数。整数的最大大小取决于记录中的位数(2四、28或32)
若是记录值是小于搜索树中节点数(不是字节数,而是实际节点数)的数目(这是存储在数据库中的元数据),那么这个值是一个节点编号。在这种状况下,咱们能够从那里重复查找找到搜索树中的节点
若是记录值等于节点数,这意味着咱们没有任何IP地址的数据,搜索到此结束
若是记录值大于搜索树中的节点数,则它是指向数据段的实际指针值。指针的值是从数据段的开始计算的,而不是从文件的开始
为了肯定咱们应该在哪里开始寻找数据段,咱们使用下面的公式:
$data_section_offset = ($record_value - $node_count) - 16
16是数据段分隔符的大小(详见下文)
最好是经过一个例子说明咱们减去$node_count的缘由:
假设咱们有一个24位的树,有1000个节点。每一个节点包含48位,或6字节。树的大小是6000字节
当树中的记录包含 <1000 的数值时,这是一个节点号,咱们查找那个节点。若是记录包含值 >1016,咱们知道它是一个数据段值。咱们减去节点数量(1000),而后减去数据段分隔符的16,获得的数值0,是数据段的第一个字节
若是记录包含值6000,这个公式将给咱们偏移到数据段的4084
为了肯定此偏移量真正指向的文件中的位置,咱们还须要知道数据段的起始位置。这能够经过肯定搜索树的大小以字节计算,而后为数据段分隔符添加一个额外的16字节
所以,最后的公式,以肯定在文件中的偏移量是:
$offset_in_file = ($record_value - $node_count) + $search_tree_size_in_bytes + 16
--------------------------------------------------
IPv6树中的IPv4地址(IPv4 addresses in an IPv6 tree)
在IPv6树中存储IPv4地址时,按原样存储,因此它们占据了地址空间的前32位(从0到2^32-1)
创造者的数据库应该决定一个策略来处理各类IPv4和IPv6之间的映射。
MaxMind所使用的策略为其GeoIP数据库包含一个 ::ffff:0:0/96 子网的根节点树中的IPv4地址空间的指针,这是IPv4映射到IPv6地址的描述
MaxMind还包括一个 2002::/16 子网的IPv4地址空间的根节点的树的指针,这是IPv6映射到IPv4地址的描述
建议数据库建立者记录他们是否为他们的数据库作相似的事情
Teredo子网在树中没法解码,相反,代码搜索树能够提供一个Teredo地址IPv4部分解码和查找
--------------------------------------------------
数据段分离器(Data Section Separator)
有16个字节的空值在搜索树和数据段之间。此分隔符存在,以便使验证工具能够区分两个部分
此分隔符不被认为是数据段自己的一部分。换句话说,数据段在文件中的“$size_of_search_tree + 16”字节开始
--------------------------------------------------
输出数据段(Output Data Section)
每一个输出数据字段都有一个关联的类型,这类型编码为一个数字开始的数据字段。某些类型的长度是可变的。在这些状况下,类型指示器后跟长度。数据有效载荷老是在字段的结尾
全部的二进制数据存储为大端(Big Endian)格式
注意给定数据类型含义的解释由高级API决定,而不是由二进制格式自己决定
一、指针(pointer - 1)
指向数据段地址空间的另外一部分的指针。指针将指向字段的开头。指针指向另外一指针是非法的。
指针的值从数据部分开始,不是文件开始
二、UTF-8字符串(UTF-8 string - 2)
包含有效UTF-8可变长度的字节序列。若是长度为零,那么这是一个空字符串
三、双精度浮点型(double - 3)
存储为大端(Big Endian)格式,长度为8个字节
四、字节数组(bytes - 4)
可变长度的字节序列包含任何类型的二进制数据。若是长度为零则这是一个零长度的字节序列
这不是当前使用的但未来可使用嵌入非文本数据(图片等)
整数格式(integer formats)
整数存储在可变长度二进制字段中
咱们支持16位、32位、64位、和128位无符号整数,还支持32位有符号整数
一个128位整数可使用多达16个字节,但可使用更少的。一样,一个32位整数,能够用0字节。对使用的字节数由在控制字节长度的说明符肯定。详情见下文
总长度为0表示数字0
当存储一个带符号整数,最左边的位是符号。1是负的,0是正的
数据类型为整数类型:
五、16位无符号整数(unsigned 16-bit int - 5)
六、32位无符号整数(unsigned 32-bit int - 6)
八、32位有符号整数(signed 32-bit int - 8)
九、64位无符号整型(unsigned 64-bit int - 9)
十、128位无符号整型(unsigned 128-bit int - 10)
无符号的32位和128位类型能够分别用来存储IPv4和IPv6地址
有符号的32位整数存储使用2的补码表示
七、Map(map - 7)
Map数据类型包含一组键/值对。与其余数据类型不一样,映射的长度信息表示它包含多少个键/值对,而不是其长度为字节。这个大小能够是零
如下是用于肯定哈希中对数的算法。该算法也被用来肯定一个字段的有效载荷的长度
十一、数组(array - 11)
数组类型包含一组有序值。数组的长度信息表示它包含多少个值,而不是字节的长度。这个大小能够是零
这种类型使用与Map相同的算法来肯定字段的有效载荷的长度
十二、数据缓存容器(data cache container - 12)
这是一个特殊的数据类型,它标志着容器用于缓存数据重复。例如,不是反复在数据库中重复字符串“United States”,而是咱们将它存储在缓存容器中,并使用指针指向这个容器
数据库中的任何内容都不包含指向该字段自己的指针。相反,各个字段将指向容器
主要缘由是生成一个单独的数据类型与内联缓存的数据,数据库dump数据段时,dump工具能够跳过这个缓存。高速缓存内容将被丢弃做为指针进入
1三、结束标记(end marker - 13)
结束标记是数据部分的结束的标记。这并非必需的,但包括这个标记容许数据部分反序列化器来处理流的输入,而不是找到的最后部分以前反序列化
此数据类型不遵循有效载荷,其大小始终为零
1四、布尔型(boolean - 14)
真值或假值。布尔类型的长度信息老是0或1,表示值。此字段没有有效载荷
1五、浮点型(float - 15)
存储为IEEE-754浮点型大端(Big Endian)格式,长度为4个字节
这种类型主要为提供完整性。因为浮点数存储方式,当序列化而后反序列化时很容易失去精度。建议使用双精度浮点型
--------------------------------------------------
数据字段格式(Data Field Format)
每一个字段以控制字节开头。此控制字节提供有关字段的数据类型和有效载荷大小的信息
控制字节的前三位告诉你该字段是什么类型。若是这些位都是0,那么这是一个“扩展”类型,这意味着下一个字节包含实际类型。不然,前三位将包含一个数字从1到7,实际类型为字段
咱们已经尝试将最经常使用的类型做为数字1-7指定为优化
在扩展类型中,第二个字节的类型号是负数7。换句话说,数组(类型11)将以第一个字节中的类型和第二个字节中的4来存储
下面是如何控制字节能够与下一个字节组合来获取类型的例子:
001XXXXX pointer
010XXXXX UTF-8 string
010XXXXX unsigned 32-bit int (ASCII)
000XXXXX 00000011 unsigned 128-bit int (binary)
000XXXXX 00000100 array
000XXXXX 00000110 end marker
--------------------------------------------------
有效载荷的大小(Payload Size)
接下来的五位控制字节告诉你数据字段的有效载荷是多长,除了Map和指针。Map和指针使用这个尺寸信息有点不一样
若是五位小于29,那么这些比特位是字节的有效载荷的大小。例如:
01000010 UTF-8 string - 2 bytes long
01011100 UTF-8 string - 28 bytes long
11000001 unsigned 32-bit int - 1 byte long
00000011 00000011 unsigned 128-bit int - 3 bytes long
若是五位等于29,30,或31,那么使用下面的算法计算有效载荷大小
若是该值为29,大小是29+接下来的一个字节,字节类型为无符号整数型
若是该值为30,大小是285+接下来的两个字节,字节类型为无符号整数型
若是该值为31,大小是65821+接下来的三个字节,字节类型为无符号整数类型
一些例子:
01011101 00110011 UTF-8 string - 80 bytes long
在这种状况下,最后五位的控制字节=29。咱们把接下来的一个字节看做无符号整数型,接下来的一个字节等于51,因此总大小(29 + 51)= 80
01011110 00110011 00110011 UTF-8 string - 13,392 bytes long
最后五位的控制字节=30。咱们把接下来的两个字节看做无符号整数型,接下来的两个字节等于13107,因此总大小(285 + 13107)= 13392
01011111 00110011 00110011 00110011 UTF-8 string - 3,421,264 bytes long
最后五位的控制字节=31。咱们把接下来的三个字节看做无符号整数型,接下来的三个字节等于3355443,因此总大小(65821 + 3355443)= 3421264
这意味着单个字段的最大有效载荷大小是16843036字节
二进制数类型老是有一个已知的大小,但为了一致起见,控制字节老是为这些类型指定正确的大小
--------------------------------------------------
Maps(Maps)
Map使用控件字节中的大小(和后面的任意字节)来指示map中键/值对的数目,而不是以字节为单位的有效载荷的大小
这意味着一个Map的键/值对的最大对数是16843036
Map列出每一个键以后,紧跟着它的值,其次才是下一对键/值
Map的键老是使用UTF-8编码的字符串,其值能够是任何数据类型,包括Map或指针
一旦咱们知道了对的数目,咱们就能够依次查看每一对,以肯定键大小和键名,以及值的类型和有效载荷
--------------------------------------------------
指针(Pointers)
指针使用控制字节中的最后五位来计算指针值
计算指针的值,咱们把开始的五位比特数据分割成两组,前两位表示大小,接下来的三位是值的一部分,因此,咱们最终的控制字节格式是这样的:001SSVVV
大小能够是0、一、二、或3
若是大小为0,指针为经过向上一个三位比特数据追加下一个字节来产生的一个11位值
若是大小是1,指针为经过向上一个三位比特数据追加下两个字节来产生的一个19位值+2048
若是大小是2,指针为经过向上一个三位比特数据追加下三个字节来产生的一个27位值+526336
最后,若是大小为3,指针的值将包含在接下来的四字节中做为32位值。在这种状况下,忽略控制字节的最后三位
这意味着,咱们被限制在4GB的地址空间的指针,因此对数据库中的数据段大小限制为4GB
--------------------------------------------------
参考实现(Reference Implementations)
写出(Writer)
Perl (https://github.com/maxmind/MaxMind-DB-Writer-perl)
读取(Reader)
C (https://github.com/maxmind/libmaxminddb)
C# (https://github.com/maxmind/MaxMind-DB-Reader-dotnet)
Java (https://github.com/maxmind/MaxMind-DB-Reader-java)
Perl (https://github.com/maxmind/MaxMind-DB-Reader-perl)
PHP (https://github.com/maxmind/MaxMind-DB-Reader-php)
Python (https://github.com/maxmind/MaxMind-DB-Reader-python)
--------------------------------------------------
做者(Authors)
本规范由下列做者建立:
Greg Oschwald <goschwald@maxmind.com>
Dave Rolsky <drolsky@maxmind.com>
Boris Zentner <bzentner@maxmind.com>
--------------------------------------------------许可(License)This work is licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative Commons, 444 Castro Street, Suite 900, Mountain View, California, 94041, USA