圖數據庫NEO4J 底層分析

圖數據庫NEO4J

Neo4j主要經過構成圖來存儲數據,圖中的數據包括節點、關係以及節點的屬性和關係的屬性,關係能夠是雙向的,也能夠是隻有單向的.html

如下是它的一些特色 + 支持完整的ACID(原子性、一致性、隔離性和持久性) + 支持常數級時間複雜度的圖遍歷 + 支持查詢的數據導出爲JSON和XLS格式 + 支持經過瀏覽器圖形化界面形式訪問 + 能夠經過多種語言進行訪問管理(Java、Python、Ruby、PHP、C#、Js)java

原生圖處理 native processing

原生圖處理:存在免索引鄰接屬性(Index Free Adjacency),提供快速高效的圖遍歷node

使用免索引鄰接的數據庫引擎中的每一個節點都會維護其對相鄰節點的引用。所以每一個節點都表現爲其附近節點的微索引,這比使用全局索引代價小不少。這意味着查詢時間與圖的總體規模無關,它僅和所搜索圖的數量成正比。

相反,一個非原生圖數據庫引擎使用(全局)索引鏈接各個節點。這些索引對每一個遍歷都添加一個間接層,所以會致使更大的計算成本。原生圖處理的擁護者認爲免索引鄰接相當重要,由於它提供快速、高效的圖遍歷。

索引查找在小型網絡中能夠工做,但對於大圖的查詢代價過高。具備原生圖處理能力的圖數據庫在查詢是否是使用索引查找來扮演聯繫的角色,而是使用免索引鄰接來確保高性能遍歷的。
 非原生圖處理引擎使用索引進行節點間遍歷

上圖中要尋找Alice的朋友,咱們必需要首先執行索引查找,成本爲O(log n ) ,這對於偶爾或者淺層的查找來講是能夠接受的,但當咱們改變遍歷的方向時,他的代價就變得很是昂貴起來,若是相對於尋找alice的朋友,就必需要執行多個索引來完成查找,每一個節點所表明的人都有可能把Alice看成他的朋友,這使得成本很高,找到Alice的朋友代價是O(log n ) ,而找到和Alice交朋友的人的代價則是O(mlogn)。mysql

索引查找在小型網絡中還能夠,可是在大圖中的查詢代價過高,具備原生圖處理能力的圖數據庫在查詢時不是使用索引查找的,而是使用免索引零鏈接來確保高性能的遍歷的,下圖爲Neo4j使用關係而非索引實現快速遍歷sql

在通用圖數據庫中,能夠以極小的代價雙向(從尾部到頭部或者從頭部到尾部)遍歷關係,上圖中尋找ALICE的朋友,直接向外尋找friend就能夠。其遍歷的成本爲O(1),要尋和Alice交朋友的人,咱們只須要全部指向ALICE的friend關係聯繫在一塊兒便可,這樣的成本是O(1).數據庫

原生圖存儲 native graph storage

免索引鄰接(index-free adjacency) 是圖數據庫相比於傳統的 mysql 的優點的核心 key,那麼圖數據庫用什麼結構去存儲 index-free adjacency 是關鍵設計點。api

架構上層是對外訪問的 api,右邊是事務管理,左邊有 cache 等,下面咱們看下 disk 上存儲的結構:數組

neo4j 在磁盤上會分不一樣的 store file 存儲瀏覽器

  • neostore.nodestore.db:存儲 node
  • neostore.propertystore.db:存儲屬性
  • neostore.relationshipstore.db:存儲關係

一個重要的設計點是 store 中存儲的 record 都是固定大小的,固定大小帶來的好處是:由於每一個 record 的大小固定,所以給定 id就能快速進行定位。緩存

節點與關係的存儲文件的物理結構圖 上圖第一個是 node record 的結構:

  • 1byte:in-use flag,代表該 node 是否在使用
  • 4byte:第一個 relation id(-1表示無)
  • 4byte:第一個 property id(-1表示無)
  • 5byte:label 信息(可能直接 inline 存儲)
  • 1byte:reversed

圖中的節點和聯繫的存儲文件都是固定大小的,每一個記錄長度爲9字節,所以能夠能夠在O(1)的時間複雜度下計算位置.

節點(指向聯繫和屬性的單向鏈表,neostore.nodestore.db):第一個字節,表示是否被使用的標誌位,後面4個字節,表明關聯到這個節點的第一個關係的ID,再接着的4個字符,表明第一個屬性ID,後面緊接着的5個字符是表明當前節點的標籤,指向該節點的標籤存儲,最後一個字符做爲保留位.

聯繫(雙向鏈表,neostore.relationshipstore.db):第一個字節,表示是否被使用的標誌位,後面4個字節,表明起始節點的ID,再接着的4個字符,表明結束個節點的ID,而後是關係類型佔用5個字節,而後依次接着是起始節點的上下聯繫和結束節點的上下節點,以及一個指示當前記錄是否位於聯繫鏈的最前面.

同時還有屬性存儲(neostore.propertystore.db)也是固定大小,每一個屬性記錄包括4個屬性塊(一個屬性記錄最多容納4個屬性)和指向屬性鏈中下一個屬性的ID. 屬性記錄包括屬性類型和指向屬性索引文件的指針(neostore.propertysotre.db.index). 同時屬性記錄中能夠內聯和動態存儲,在屬性值存儲佔用小時,會直接存儲在屬性記錄中,對於大屬性值,能夠分別存儲在動態字符存儲(neostore.propertysotre.db.strings)和動態數組存儲(neostore.propertysotre.db.arrays)中,因爲動態記錄一樣由記錄大小固定的記錄鏈表組成,所以大字符串和大數組會佔據多個動態記錄.

節點存儲文件用來存儲節點的記錄。每一個用戶級的圖中建立的節點最終會終結於節點存儲,其物理文件是"neostore.nodestore.db"。像大多數Neo4j存儲文件同樣,節點存儲區是固定大小的記錄存儲,每一個記錄長度爲9字節。經過大小固定的記錄能夠快速查詢存儲文件中的節點。 一個節點記錄的第一個字節是「是否在使用」標誌位。它告訴數據庫該記錄目前是被用於存儲節點,仍是可回收用於表示一個新的節點。接下來的4字節表示關聯到該節點的第一個聯繫,隨後4字節表示該節點的第一個屬性的ID。標籤的5字節指向該節點的標籤存儲(若是標籤不多的話也能夠內聯到節點中)。最後的字節extra是標誌保留位。這樣一個標誌是用來標識緊密鏈接節點的,而省下的空間爲未來預留。節點記錄是至關輕量級的:它真的只是幾個指向聯繫和屬性列表的指針。 相應的,聯繫被存儲於聯繫存儲文件中,物理文件是neostore.relationshipstore.db。像節點存儲同樣,聯繫存儲區的記錄的大小也是固定的。每一個聯繫記錄包含聯繫的起始點ID和結束節點ID、聯繫類型的指針(存儲在聯繫類型存儲區),起始節點和結束節點的上一個聯繫和下一個聯繫,以及一個指示當前記錄是否位於聯繫鏈最前面。

下面是 relation record 的結構:

剛開始是開始和結束節點的 node id,接着是 relation type pointer,而後開始和結束節點的前驅和後繼 relation id

更形象一點的圖

一個可能的搜索過程是:對於給定的一個 node record,能夠經過 id 進行簡單的偏移計算獲得 node,而後經過 relation_id 定位到 relation record,而後獲得 end node id,經過偏移計算獲得 node

兩個節點記錄都包含一個指向該節點的第一個屬性的指針和聯繫鏈中第一個聯繫的指針。要讀取節點的屬性,咱們從指向第一個屬性的指針開始遍歷單向鏈表結構。要找到一個節點的聯繫,咱們從指向第一個聯繫(在示例中爲LIKES聯繫)的節點聯繫指針開始,順着特定節點的聯繫的雙向鏈表尋找(即起始節點的雙向鏈表或結束節點的雙向鏈表),直到找到感興趣的聯繫。一旦找到了咱們想要的聯繫記錄,咱們可使用和尋找節點屬性同樣的單向鏈表結構讀取這種聯繫的屬性(若是有的話),也可使用聯繫關聯的起始節點ID和結束節點ID檢查它們的節點記錄。用這些ID乘以節點記錄的大小,就能夠當即算出每一個節點在節點存儲文件中的偏移量。 聯繫存儲文件中的雙向鏈表:

雙向存儲

這種 partner 的關係自然就是雙向的,可是咱們存儲的時候,難道要存儲兩個關係嗎,以下圖:

那確定是不須要的,這種存儲就是一種浪費,那到底 neo4j 中是怎麼存儲 partner 這種雙向關係的呢? 答案是:以任意一個節點爲開端,另外一個爲尾端,即存儲成爲單向的關係

在 neo4j 中任意的關係都有一個 start node 和一個 end node,並且 start node 和 end node 都會有個關聯的雙向鏈表,這個雙向鏈表中就記錄了從該節點出去和進入的全部關係

示例1

在這個例子中,A ~ E表示Node 的編號,R1~R7 表示 Relationship 編號,P1~P10 表示Property 的編號。

Node 的存儲示例圖以下,每一個Node 保存了第1個Property 和 第1個Relationship:

關係的存儲示意圖以下:

從示意圖能夠看出,從 Node-B 開始,能夠經過關係的 next 指針,遍歷Node-B 的全部關係,而後能夠到達與其有關係的第1層Nodes,在經過遍歷第1層Nodes的關係,能夠達到第2層Nodes,...

neo4j graph db的存儲文件介紹

下載neo4j-community-3.5.6並安裝,運行幾個例子後 在安裝目錄的/data/databases/graph.db下

[root@localhost graph.db]# ll
總用量 596
drwxr-xr-x 2 root root     10 5月  27 10:32 index
-rw-r--r-- 1 root root   8192 5月  28 09:30 neostore
-rw-r--r-- 1 root root   1152 5月  28 09:30 neostore.counts.db.a
-rw-r--r-- 1 root root   1152 5月  28 09:26 neostore.counts.db.b
-rw-r--r-- 1 root root      9 5月  28 09:30 neostore.id
-rw-r--r-- 1 root root  49152 5月  28 09:30 neostore.labelscanstore.db
-rw-r--r-- 1 root root   8190 5月  27 16:59 neostore.labeltokenstore.db
-rw-r--r-- 1 root root      9 5月  28 09:30 neostore.labeltokenstore.db.id
-rw-r--r-- 1 root root   8192 5月  27 16:59 neostore.labeltokenstore.db.names
-rw-r--r-- 1 root root      9 5月  28 09:30 neostore.labeltokenstore.db.names.id
-rw-r--r-- 1 root root   8190 5月  28 09:30 neostore.nodestore.db
-rw-r--r-- 1 root root    297 5月  28 09:30 neostore.nodestore.db.id
-rw-r--r-- 1 root root   8192 5月  27 10:32 neostore.nodestore.db.labels
-rw-r--r-- 1 root root      9 5月  28 09:30 neostore.nodestore.db.labels.id
-rw-r--r-- 1 root root  48954 5月  28 09:30 neostore.propertystore.db
-rw-r--r-- 1 root root  73728 5月  28 09:30 neostore.propertystore.db.arrays
-rw-r--r-- 1 root root    137 5月  28 09:30 neostore.propertystore.db.arrays.id
-rw-r--r-- 1 root root    265 5月  28 09:30 neostore.propertystore.db.id
-rw-r--r-- 1 root root   8190 5月  27 16:59 neostore.propertystore.db.index
-rw-r--r-- 1 root root      9 5月  28 09:30 neostore.propertystore.db.index.id
-rw-r--r-- 1 root root   8192 5月  27 16:59 neostore.propertystore.db.index.keys
-rw-r--r-- 1 root root      9 5月  28 09:30 neostore.propertystore.db.index.keys.id
-rw-r--r-- 1 root root  16384 5月  28 09:30 neostore.propertystore.db.strings
-rw-r--r-- 1 root root     89 5月  28 09:30 neostore.propertystore.db.strings.id
-rw-r--r-- 1 root root   8192 5月  27 10:32 neostore.relationshipgroupstore.db
-rw-r--r-- 1 root root      9 5月  28 09:30 neostore.relationshipgroupstore.db.id
-rw-r--r-- 1 root root  32640 5月  28 09:30 neostore.relationshipstore.db
-rw-r--r-- 1 root root    273 5月  28 09:30 neostore.relationshipstore.db.id
-rw-r--r-- 1 root root   8190 5月  27 16:59 neostore.relationshiptypestore.db
-rw-r--r-- 1 root root      9 5月  28 09:30 neostore.relationshiptypestore.db.id
-rw-r--r-- 1 root root   8192 5月  27 16:59 neostore.relationshiptypestore.db.names
-rw-r--r-- 1 root root      9 5月  28 09:30 neostore.relationshiptypestore.db.names.id
-rw-r--r-- 1 root root   8192 5月  27 10:32 neostore.schemastore.db
-rw-r--r-- 1 root root      9 5月  28 09:30 neostore.schemastore.db.id
-rw-r--r-- 1 root root 229292 5月  28 09:30 neostore.transaction.db.0

存儲 node 的文件

  1. 存儲節點數據及其序列Id
    • neostore.nodestore.db: 存儲節點數組,數組的下標便是該節點的ID
    • neostore.nodestore.db.id :存儲最大的ID 及已經free的ID
  2. 存儲節點label及其序列Id
    • neostore.nodestore.db.labels :存儲節點label數組數據,數組的下標便是該節點label的ID
    • neostore.nodestore.db.labels.id

存儲 relationship 的文件

  1. 存儲關係數據及其序列Id
    • neostore.relationshipstore.db 存儲關係 record 數組數據
    • neostore.relationshipstore.db.id
  2. 存儲關係組數據及其序列Id
    • neostore.relationshipgroupstore.db 存儲關係 group數組數據
    • neostore.relationshipgroupstore.db.id
  3. 存儲關係類型及其序列Id
    • neostore.relationshiptypestore.db 存儲關係類型數組數據
    • neostore.relationshiptypestore.db.id
  4. 存儲關係類型的名稱及其序列Id
    • neostore.relationshiptypestore.db.names存儲關係類型 token 數組數據
    • neostore.relationshiptypestore.db.names.id

存儲 label 的文件

  1. 存儲label token數據及其序列Id
    • neostore.labeltokenstore.db 存儲lable token 數組數據
    • neostore.labeltokenstore.db.id
  2. 存儲label token名字數據及其序列Id
    • neostore.labeltokenstore.db.names 存儲 label token 的 names 數據
    • neostore.labeltokenstore.db.names.id

存儲 property 的文件

  1. 存儲屬性數據及其序列Id
    • neostore.propertystore.db 存儲 property 數據
    • neostore.propertystore.db.id
  2. 存儲屬性數據中的數組類型數據及其序列Id
    • neostore.propertystore.db.arrays 存儲 property (key-value 結構)的Value值是數組的數據。
    • neostore.propertystore.db.arrays.id
  3. 屬性數據爲長字符串類型的存儲文件及其序列Id
    • neostore.propertystore.db.strings 存儲 property (key-value 結構)的Value值是字符串的數據。
    • neostore.propertystore.db.strings.id
  4. 屬性數據的索引數據文件及其序列Id
    • neostore.propertystore.db.index 存儲 property (key-value 結構)的key 的索引數據。
    • neostore.propertystore.db.index.id
  5. 屬性數據的鍵值數據存儲文件及其序列Id
    • neostore.propertystore.db.index.keys 存儲 property (key-value 結構)的key 的字符串值。
    • neostore.propertystore.db.index.keys.id

其餘的文件

  1. 存儲版本信息
    • neostore
    • neostore.id
  2. 存儲 schema 數據
    • neostore.schemastore.db
    • neostore.schemastore.db.id

neo4j 中,主要有4類節點,屬性,關係等文件是以數組做爲核心存儲結構;同時對節點,屬性,關係等類型的每一個數據項都會分配一個惟一的ID,在存儲時以該ID 爲數組的下標。這樣,在訪問時經過其ID做爲下標,實現快速定位。因此在圖遍歷等操做時,能夠實現 free-index。

store 部分類圖

CommonAbstractStore

CommonAbstractStore 是全部 Store 類的基類,下面的代碼片斷是 CommonAbstractStore 的成員變量,比較重要的是飄紅的幾個,特別是IdGenerator,每種Store 的實例都有本身的 id 分配管理器; StoreChannel 是負責Store文件的讀寫和定位;WindowsPool 是與Store Record相關的緩存,用來提高性能的。

public abstract class CommonAbstractStore<RECORD extends AbstractBaseRecord,HEADER extends StoreHeader>
        implements RecordStore<RECORD>, AutoCloseable
{
    static final String UNKNOWN_VERSION = "Unknown";

    protected final Config configuration;
    protected final PageCache pageCache;
    protected final IdType idType;
    protected final IdGeneratorFactory idGeneratorFactory;
    protected final Log log;
    protected final String storeVersion;
    protected final RecordFormat<RECORD> recordFormat;
    final File storageFile;
    private final File idFile;
    private final String typeDescriptor;
    protected PagedFile pagedFile;
    protected int recordSize;
    private IdGenerator idGenerator;
    private boolean storeOk = true;
    private RuntimeException causeOfStoreNotOk;

    private final StoreHeaderFormat<HEADER> storeHeaderFormat;
    private HEADER storeHeader;

    private final OpenOption[] openOptions;

neo4j 的db文件及對應的存儲格式類型


文件名 文件存儲格式


neostore.labeltokenstore.db LabelTokenStore(TokenStore)

neostore.labeltokenstore.db.id ID 類型

neostore.labeltokenstore.db.names StringPropertyStore (AbstractDynamicStore, NAME_STORE_BLOCK_SIZE = 30)

neostore.labeltokenstore.db.names.id ID 類型

neostore.nodestore.db NodeStore

neostore.nodestore.db.id ID 類型

neostore.nodestore.db.labels ArrayPropertyStore (AbstractDynamicStorelabel_block_size=60)

neostore.nodestore.db.labels.id ID 類型

neostore.propertystore.db PropertyStore

neostore.propertystore.db.arrays ArrayPropertyStore (AbstractDynamicStorearray_block_size=120)

neostore.propertystore.db.arrays.id ID 類型

neostore.propertystore.db.id ID 類型

neostore.propertystore.db.index PropertyIndexStore

neostore.propertystore.db.index.id ID 類型

neostore.propertystore.db.index.keys StringPropertyStore (AbstractDynamicStore, NAME_STORE_BLOCK_SIZE = 30)

neostore.propertystore.db.index.keys.id ID 類型

neostore.propertystore.db.strings StringPropertyStore (AbstractDynamicStorestring_block_size=120)

neostore.propertystore.db.strings.id ID 類型

neostore.relationshipgroupstore.db RelationshipGroupStore

neostore.relationshipgroupstore.db.id ID 類型

neostore.relationshipstore.db RelationshipStore

neostore.relationshipstore.db.id ID 類型

neostore.relationshiptypestore.db RelationshipTypeTokenStore(TokenStore)

neostore.relationshiptypestore.db.id ID 類型

neostore.relationshiptypestore.db.names StringPropertyStore (AbstractDynamicStore, NAME_STORE_BLOCK_SIZE = 30)

neostore.relationshiptypestore.db.names.id ID 類型

neostore.schemastore.db SchemaStore(AbstractDynamicStore, BLOCK_SIZE = 56)

neostore.schemastore.db.id ID 類型

通用的Store 類型

下面是 neo4j db 中,每種Store都有本身的ID文件(即後綴.id 文件),它們的格式都是同樣的。

[root@localhost graph.db]# ll | grep .id
-rw-r--r-- 1 root root      9 5月  28 09:30 neostore.id
-rw-r--r-- 1 root root      9 5月  28 09:30 neostore.labeltokenstore.db.id
-rw-r--r-- 1 root root      9 5月  28 09:30 neostore.labeltokenstore.db.names.id
-rw-r--r-- 1 root root    297 5月  28 09:30 neostore.nodestore.db.id
-rw-r--r-- 1 root root      9 5月  28 09:30 neostore.nodestore.db.labels.id
-rw-r--r-- 1 root root    137 5月  28 09:30 neostore.propertystore.db.arrays.id
-rw-r--r-- 1 root root    265 5月  28 09:30 neostore.propertystore.db.id
-rw-r--r-- 1 root root      9 5月  28 09:30 neostore.propertystore.db.index.id
-rw-r--r-- 1 root root      9 5月  28 09:30 neostore.propertystore.db.index.keys.id
-rw-r--r-- 1 root root     89 5月  28 09:30 neostore.propertystore.db.strings.id
-rw-r--r-- 1 root root      9 5月  28 09:30 neostore.relationshipgroupstore.db.id
-rw-r--r-- 1 root root    273 5月  28 09:30 neostore.relationshipstore.db.id
-rw-r--r-- 1 root root      9 5月  28 09:30 neostore.relationshiptypestore.db.id
-rw-r--r-- 1 root root      9 5月  28 09:30 neostore.relationshiptypestore.db.names.id
-rw-r--r-- 1 root root      9 5月  28 09:30 neostore.schemastore.db.id

ID類型文件的存儲格式

neo4j 中後綴爲 ".id"的文件格式如上圖所示,由文件頭(9 Bytes)和 long類型 數組 2部分構成:

  • sticky(1 byte) : if sticky the id generator wasn't closed properly so it has to berebuilt (go through the node, relationship, property, rel type etc files).
  • nextFreeId(long) : 保存最大的ID,該值與對應類型的存儲數組的數組大小相對應。
  • reuseId(long):用來保存已經釋放且可複用的ID值。經過複用ID ,能夠減小資源數組的空洞,提升磁盤利用率。

IdGeneratorImpl.java

原先,每一種資源類型的ID 分配 neo4j 中是經過 IdGeneratorImpl 來實現的,其功能是負責ID管理分配和回收複用。對於節點,關係,屬性等每一種資源類型,均可以生成一個IdGenerator 實例來負責其ID管理分配和回收複用。

當前版本(v3.5)有所變更,應該是將原 IdGeneratorImpl 拆成了 IdGeneratorImpl 和 IdContainer .

FreeIdKeeper.java

/**
 * Instances of this class maintain a list of free ids with the potential of overflowing to disk if the number
 * of free ids becomes too large. This class has no expectations and makes no assertions as to the ids freed.
 * Such consistency guarantees, for example uniqueness of values, should be imposed from users of this class.
 * <p>
 * There is no guarantee as to the ordering of the values returned (i.e. FIFO, LIFO or any other temporal strategy),
 * primarily because the aggressiveMode argument influences exactly that behaviour.
 * <p>
 * The {@link #aggressiveMode} parameter controls whether or not IDs which are freed during this lifecycle will
 * be allowed to be reused during the same lifecycle. The alternative non-aggressive behaviour is that the IDs
 * will only be reused after a close/open cycle. This would generally correlate with a restart of the database.
 */

此類的實例維護一個可用ID列表,若是可用ID的數量太大,則可能溢出到磁盤。 此類沒有指望,也沒有對釋放的ID作出斷言。 一致性應有用戶保證(諸如值的惟一性)

對於返回值的順序(即FIFO、LIFO或任何其餘時間策略)沒有任何保證,主要是由於參數 aggressiveMode 影響. 參數 aggressiveMode 控制了在生命週期中已經被釋放的 ID 是否會在同個生命週期中被複用. 另外一種非激進行爲是指,ID只在一個週期關閉/打開後才從新使用.這一般與從新啓動數據庫接近。

IDContainer.java

/**
 * This class handles the persisting of a highest id in use. A sticky byte is present in the header to indicate
 * whether the file was closed properly. It also handel delegation of reusable ids to the {@link FreeIdKeeper}
 * class.
 *
 * This class is <b>not thread-safe</b> and synchronization need to be handed by the caller.
 */

此類處理使用中的最高ID的持久化。 頭部中的 sticky byte 指示文件是否正確關閉. 它還將可重用ID委託給 freeidmeeper 類.

此類是 非線程安全 的,同步須要由調用者進行處理。

IdGeneratorImpl.java
/**
 * This class generates unique ids for a resource type. For example, nodes in a
 * nodes space are connected to each other via relationships. On nodes and
 * relationship one can add properties. We have three different resource types
 * here (nodes, relationships and properties) where each resource needs a unique
 * id to be able to differ resources of the same type from each other. Creating
 * three id generators (one for each resource type ) will do the trick.
 * <p>
 * <CODE>IdGenerator</CODE> makes use of so called "defragged" ids. A
 * defragged id is an id that has been in use one or many times but the resource
 * that was using it doesn't exist anymore. This makes it possible to reuse the
 * id and that in turn makes it possible to write a resource store with fixed
 * records and size (you can calculate the position of a record by knowing the
 * id without using indexes or a translation table).
 * <p>
 * The id returned from {@link #nextId} may not be the lowest
 * available id but will be one of the defragged ids if such exist or the next
 * new free id that has never been used.
 * <p>
 * The {@link #freeId} will not check if the id passed in to it really is free.
 * Passing a non free id will corrupt the id generator and {@link #nextId}
 * method will eventually return that id.
 * <p>
 * The {@link #close()} method must always be invoked when done using an
 * generator (for this time). Failure to do will render the generator as
 * "sticky" and unusable next time you try to initialize a generator using the
 * same file. There can only be one id generator instance per id generator file.
 * <p>
 * In case of disk/file I/O failure an <CODE>IOException</CODE> is thrown.
 */

此類爲資源類型生成惟一的ID.例如,節點空間中的節點(node)經過關係(relationship)相連.節點和關係上均可以添加屬性(properties). 這裏有3種不一樣的資源類型(節點,關係,屬性),每種資源類型都須要惟一的 ID 以和同類資源區分開來. 建立 3 種 ID 生成器(Idgenerator)(每類資源1種)以實現.

IdGenerator 使用所謂的'碎片'id.碎片 id 指的是一類 id,它們被使用過一次或者屢次,但使用其的資源已不存在. 這使得重用 id 成爲可能,反過來,能夠寫入固定記錄和大小的資源存儲(不使用索引和轉換表,只須要知道id就能夠計算記錄的地址).

從 {[@link] #nextId} 返回的ID可能不是最低的可用ID,但將是碎片ID之一(若是存在)或下一個從未使用過的新空閒ID。

{[@link] freeId}不檢查傳遞給它的 ID 是否真的是空閒.傳非空閒ID將損壞ID生成器,{[@link] nextid}方法最終將返回該ID。

每次調用完生成器(generator)後,老是要調用{[@link] #close()}方法.不然將使生成器'粘滯',這樣下次你試圖用同一個文件初始化生成器時就不可用. 每一個id 生成器文件只能有一個id生成器實例.

若是磁盤/文件的I/O出現故障,將拋出一個 IOException

讀取id 文件進行初始化

public IdContainer( FileSystemAbstraction fs, File file, int grabSize, boolean aggressiveReuse )
{
    if ( grabSize < 1 )
    {
        throw new IllegalArgumentException( "Illegal grabSize: " + grabSize );
    }

    this.file = file;
    this.fs = fs;
    this.grabSize = grabSize;
    this.aggressiveReuse = aggressiveReuse;
}

public boolean init()
{
    boolean result = true;
    try
    {
        if ( !fs.fileExists( file ) )
        {
            createEmptyIdFile( fs, file, 0, false );
            result = false;
        }

        fileChannel = fs.open( file, OpenMode.READ_WRITE );
        initialHighId = readAndValidateHeader();
        markAsSticky();

        this.freeIdKeeper = new FreeIdKeeper( new OffsetChannel( fileChannel, HEADER_SIZE ), grabSize, aggressiveReuse );
        closed = false;
    }
    catch ( IOException e )
    {
        throw new UnderlyingStorageException( "Unable to init id file " + file, e );
    }
    return result;
}
/***************************
FreeIdKeeper.java
*****************************/
/*
 * After this method returns, if there were any entries found, they are placed in the readFromDisk list.
 */
private void readIdBatch()
{
    try
    {
        readIdBatch0();
    }
    catch ( IOException e )
    {
        throw new UnderlyingStorageException( "Failed reading free id batch", e );
    }
}

private void readIdBatch0() throws IOException
{
    if ( stackPosition == 0 )
    {
        return;
    }

    long startPosition = max( stackPosition - batchSize * ID_ENTRY_SIZE, 0 );
    int bytesToRead = toIntExact( stackPosition - startPosition );
    ByteBuffer readBuffer = ByteBuffer.allocate( bytesToRead );

    channel.position( startPosition );
    channel.readAll( readBuffer );
    stackPosition = startPosition;

    readBuffer.flip();
    int idsRead = bytesToRead / ID_ENTRY_SIZE;
    for ( int i = 0; i < idsRead; i++ )
    {
        long id = readBuffer.getLong();
        readFromDisk.enqueue( id );
    }
    if ( aggressiveMode )
    {
        truncate( startPosition );
    }
}

釋放id(freeId)

/********* 
IdGeneratorImpl.java 
**********/
public synchronized void freeId( long id )
{
    idContainer.assertStillOpen();

    if ( IdValidator.isReservedId( id ) )
    {
        return;
    }

    if ( id < 0 || id >= highId )
    {
        throw new IllegalArgumentException( "Illegal id[" + id + "], highId is " + highId );
    }
    idContainer.freeId( id );
}

/********* 
IdContainer.java 
**********/
public void freeId( long id )
{
    freeIdKeeper.freeId( id );
}

/**********************
FreeIdKeeper.java
**********************/
public void freeId( long id )
{
    freeIds.enqueue( id );
    freeIdCount++;

    if ( freeIds.size() >= batchSize )
    {
        long endPosition = flushFreeIds( ByteBuffer.allocate( batchSize * ID_ENTRY_SIZE ) );
        if ( aggressiveMode )
        {
            stackPosition = endPosition;
        }
    }
}

申請id ( nextId)

當用戶申請一個 id 時,IdGeneratorImpl 在分配時,有2種分配策略:"正常的分配策略" 和"激進分配策略"(aggressiveMode),能夠根據配置進行選擇。

/**
 * Returns the next "free" id. If a defragged id exist it will be returned
 * else the next free id that hasn't been used yet is returned. If no id
 * exist the capacity is exceeded (all values <= max are taken) and a
 * {@link UnderlyingStorageException} will be thrown.
 *
 * @return The next free id
 * @throws UnderlyingStorageException
 *             If the capacity is exceeded
 * @throws IllegalStateException if this id generator has been closed
 */
@Override
public synchronized long nextId()
{
    assertStillOpen();
    long nextDefragId = idContainer.getReusableId();
    if ( nextDefragId != IdContainer.NO_RESULT )
    {
        return nextDefragId;
    }

    if ( IdValidator.isReservedId( highId ) )
    {
        highId++;
    }
    IdValidator.assertValidId( idType, highId, max );
    return highId++;
}


/*********************
IdContainer.java
**********************/
/**
 * @return next free id or {@link IdContainer#NO_RESULT} if not available
 */
public long getReusableId()
{
    return freeIdKeeper.getId();
}

/***************************
FreeKeeper.java
****************************/
public long getId()
{
    long result;
    if ( freeIds.size() > 0 && aggressiveMode )
    {
        result = freeIds.dequeue();
        freeIdCount--;
    }
    else
    {
        result = getIdFromDisk();
        if ( result != NO_RESULT )
        {
            freeIdCount--;
        }
    }
    return result;
}

private long getIdFromDisk()
{
    if ( readFromDisk.isEmpty() )
    {
        readIdBatch();
    }
    if ( !readFromDisk.isEmpty() )
    {
        return readFromDisk.dequeue();
    }
    else
    {
        return NO_RESULT;
    }
}
  • 激進分配策略(aggressiveMode)
    1. freeIds (剛回收的ID Queue)中分配。
    2. getIdFromDisk()
  • 正常的分配策略
    1. getIdFromDisk()

其中, getIdFromDisk() 從 readFromDisk 文件中讀取已釋放且可複用的 id. 沒有已釋放且可複用的 id了,則分配全新的id.

DynamicStore 類型

AbstractDynamicStore 的存儲格式

/**
 * An abstract representation of a dynamic store. Record size is set at creation as the contents of the
 * first record and read and used when opening the store in future sessions.
 * <p>
 * Instead of a fixed record this class uses blocks to store a record. If a
 * record size is greater than the block size the record will use one or more
 * blocks to store its data.
 * <p>
 * A dynamic store don't have a {@link IdGenerator} because the position of a
 * record can't be calculated just by knowing the id. Instead one should use
 * another store and store the start block of the record located in the
 * dynamic store. Note: This class makes use of an id generator internally for
 * managing free and non free blocks.
 * <p>
 * Note, the first block of a dynamic store is reserved and contains information
 * about the store.
 * <p>
 * About configuring block size: Record size is the whole record size including the header (next pointer
 * and what not). The term block size is equivalent to data size, which is the size of the record - header size.
 * User configures block size and the block size is what is passed into the constructor to the store.
 * The record size is what's stored in the header (first record). {@link #getRecordDataSize()} returns
 * the size which was configured at the store creation, {@link #getRecordSize()} returns what the store header says.
 */

動態存儲的抽象表示。記錄大小在建立時設置,爲在之後的會話中打開存儲時,首先記錄並讀取並使用。 此類使用塊來存儲記錄,而不是固定記錄。

若是記錄大小大於塊大小,則記錄將使用一個或多個塊來存儲其數據。

動態存儲沒有{[@link] IdGenerator},由於不能只經過ID來計算記錄。應該使用另外一個存儲,存儲儲位於動態存儲中的記錄的起始塊。 注意:此類在內部使用 Id生成器管理空閒和非空閒塊。

注意,動態存儲的第一個塊被保留,幷包含有關存儲的信息。

關於配置塊大小:記錄大小是包括頭(下一個指針等等)在內的整個記錄大小。術語塊大小等價於數據大小,數據大小是記錄頭大小。 戶配置塊大小,塊大小是傳遞到構造函數到存儲的。 記錄大小是存儲在頭文件中的內容(第一個記錄)。 {[@link] #getRecordDataSize()}返回在建立存儲時配置的大小,{[@link] #getRecordSize()}返回存儲頭的內容。

AbstractDynamicStore 類對應的存儲文件格式如上圖所示, 整個文件是有一個 block_size=BLOCK_HEADER_SIZE(8Bytes)+block_content_size 的定長數組和一個字符串 "StringPropertyStore v0.A.2" 或 "ArrayPropertyStore v0.A.2"或"SchemaStore v0.A.2" (文件類型描述TYPE_DESCRIPTOR和 neo4j 的 ALL_STORES_VERSION構成)。 訪問時,能夠經過 id 做爲數組的下標進行訪問。其中,文件的第1個 record 中前4 字節用來保存 block_size。文件的第2個 record開始保存實際的block數據,它由8個字節的block_header和定長的 block_content(可配置)構成. block_header 結構以下:

  • inUse(1 Byte):第1字節,共分紅3部分 [x__ , ] 0: start record, 1: linked record [ x, ] inUse [ ,xxxx] high next block bits
    • 第1~4 bit 表示next_block 的高4位
    • 第5 bit表示block 是否在 use;
    • 第8 bit 表示 block 是不是單向鏈表的第1個 block;0表示第1個block, 1表示後續 block.
  • nr_of_bytes(3Bytes):本 block 中保存的數據的長度。
  • next_block(4Bytes): next_block 的低 4 個字節,加上 inUse 的第1~4 位,next_block 的實際長度共 36 bit。以數組方式存儲的單向鏈表的指針,指向保存同一條數據的下一個 block 的id.

類DynamicArrayStore, DynamicStringStore

類SchemaStore,DynamicArrayStore(ArrayPropertyStore), DynamicStringStore(StringPropertyStore)都是繼承成自類AbstractDynamicStore,因此與類DynamicArrayStore, DynamicStringStore和 SchemaStore對應文件的存儲格式,都是遵循AbstractDynamicStore的存儲格式,除了block塊的大小(block_size)不一樣外。


db 文件 存儲類型 block_size


neostore.labeltokenstore.db.names StringPropertyStore NAME_STORE_BLOCK_SIZE=30

neostore.propertystore.db.index.keys StringPropertyStore NAME_STORE_BLOCK_SIZE=30

neostore.rela tionshiptypestore.db.names StringPropertyStore

neostore.propertystore.db.strings StringPropertyStore string_block_size=120

neostore.nodestore.db.labels ArrayPropertyStore label_block_size=60

neostore.propertystore.db.arrays ArrayPropertyStore array_block_size=120

neostore.schemastore.db SchemaStore BLOCK_SIZE=56

block_size 經過配置文件或缺省值來設置的,下面的代碼片斷展現了neostore.propertystore.db.strings 文件的建立過程及block_size 的大小如何傳入。

GraphDatabaseSettings.java
/**
 * Block size properties values depends from selected record format.
 * We can't figured out record format until it will be selected by corresponding edition.
 * As soon as we will figure it out properties will be re-evaluated and overwritten, except cases of user
 * defined value.
 */
@Description( "Specifies the block size for storing strings. This parameter is only honored when the store is " +
        "created, otherwise it is ignored. " +
        "Note that each character in a string occupies two bytes, meaning that e.g a block size of 120 will hold " +
        "a 60 character long string before overflowing into a second block. " +
        "Also note that each block carries a ~10B of overhead so record size on disk will be slightly larger " +
        "than the configured block size" )
@Internal
public static final Setting<Integer> string_block_size = buildSetting( "unsupported.dbms.block_size.strings", INTEGER,
        "0" ).constraint( min( 0 ) ).build();

@Description( "Specifies the block size for storing arrays. This parameter is only honored when the store is " +
        "created, otherwise it is ignored. " +
        "Also note that each block carries a ~10B of overhead so record size on disk will be slightly larger " +
        "than the configured block size" )
@Internal
public static final Setting<Integer> array_block_size = buildSetting( "unsupported.dbms.block_size.array_properties",
        INTEGER, "0" ).constraint( min( 0 ) ).build();

@Description( "Specifies the block size for storing labels exceeding in-lined space in node record. " +
        "This parameter is only honored when the store is created, otherwise it is ignored. " +
        "Also note that each block carries a ~10B of overhead so record size on disk will be slightly larger " +
        "than the configured block size" )
@Internal
public static final Setting<Integer> label_block_size = buildSetting( "unsupported.dbms.block_size.labels", INTEGER,
        "0" ).constraint( min( 0 ) ).build();

@Description( "Specifies the size of id batches local to each transaction when committing. " +
        "Committing a transaction which contains changes most often results in new data records being created. " +
        "For each record a new id needs to be generated from an id generator. " +
        "It's more efficient to allocate a batch of ids from the contended id generator, which the transaction " +
        "holds and generates ids from while creating these new records. " +
        "This setting specifies how big those batches are. " +
        "Remaining ids are freed back to id generator on clean shutdown." )
@Internal
public static final Setting<Integer> record_id_batch_size = buildSetting( "unsupported.dbms.record_id_batch_size", INTEGER,
        "20" ).constraint( range( 1, 1_000 ) ).build();
NeoStores.java

CommonAbstractStore createPropertyStringStore()
{
    return createDynamicStringStore( layout.propertyStringStore(), layout.idPropertyStringStore(), IdType.STRING_BLOCK,
            GraphDatabaseSettings.string_block_size );
}

CommonAbstractStore createPropertyArrayStore()
{
    return createDynamicArrayStore( layout.propertyArrayStore(), layout.idPropertyArrayStore(), IdType.ARRAY_BLOCK,
            GraphDatabaseSettings.array_block_size );
}

Property 的存儲

下面是neo4j graph db 中,Property數據存儲對應的文件:


neostore.propertystore.db PropertyStore

neostore.propertystore.db.arrays ArrayPropertyStore (AbstractDynamicStorearray_block_size=120)

neostore.propertystore.db.arrays.id ID 類型

neostore.propertystore.db.id ID 類型

neostore.propertystore.db.index PropertyIndexStore

neostore.propertystore.db.index.id ID 類型

neostore.propertystore.db.index.keys StringPropertyStore (AbstractDynamicStore, NAME_STORE_BLOCK_SIZE = 30)

neostore.propertystore.db.index.keys.id ID 類型

neostore.propertystore.db.strings StringPropertyStore (AbstractDynamicStorestring_block_size=120)

neostore.propertystore.db.strings.id ID 類型


參考資料

相關文章
相關標籤/搜索