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