劉海房 莫世鴻 龔 振 范冰冰
(華南師范大學計算機學院 廣東 廣州 510631)
隨著互聯網及移動互聯網的不斷發展,人類生產、生活等各領域數據信息量呈爆炸性增長,其中大量數據為政府、組織機構、企業所擁有。目前數據已是生產要素和戰略資源,數據開放共享成為大數據應用發展的基礎和保障,社會對開放數據的需求越來越強烈[1]。近年來,各國政府、組織機構、企業等紛紛加入數據開放的浪潮中,主要國家和組織機構均已建立數據開放平臺,開放交通、醫療、稅務等幾十個領域的龐大數據,部分企業也制定了數據開放戰略,對擁有的海量數據實施開放[2]。
開放數據一般以數據集的形式進行發布,數據集是被標識且能夠被系統或設備識別處理的數據集合,是一種數據資源收集與組織的新型數據組織方式和數據資源的“封裝”單元[3]。開放數據的數據集一般包含若干個文件和數據集描述信息(數據集元數據),如“上市公司財報”數據集,包含一個記錄財報數據的JSON文件,同時還包含數據集名稱、發布者等數據集描述信息。數據文件可分為結構化文件、半結構化文件和非結構化文件,文件大小各異。數據集發布者可以對數據集進行新增、刪除、更新等操作,但頻率較低。獲取開放數據主要有數據集下載和API調用兩種方式,數據集下載方式不足有:對數據集完全下載,針對應用數據需進一步加工處理,無法和業務系統直接對接。API調用是通過API有條件(如時間、地理空間、關聯等)精確獲取所需數據,調用方式靈活便捷、響應快。未來的業務系統和智能應用將會更多依賴API方式調用獲取數據。
目前,國際上大部分數據開放平臺基于CKAN、DKAN開源框架搭建[4],通過關系型數據庫和文件系統存儲管理開放數據,將數據集元數據存儲在關系型數據庫中,將數據文件存儲在文件系統,文件路徑存儲到對應的數據表中,如圖1所示。這種傳統的開放數據管理方式,能提供基于數據集元數據的數據集檢索和下載功能,但不支持高效可用的API[5],制約了開放數據應用發展。為了支持API數據調用,同時考慮未來海量的開放數據,需要一種新的開放數據存儲管理模型,對開放數據的各類形式數據進行有效管理,且具有較高的API調用效率。

圖1 傳統開放數據存儲管理方式
ODSMM模型示意圖如圖2所示,對數據集元數據、文件、結構化數據進行存儲管理,結構化數據是對結構化和半結構化文件數據轉換后得到的具體內容數據。ODSMM模型使用MongoDB[6]作為存儲載體,將所有數據集元數據存儲在一個獨立的元數據集合中,利用MongoDB良好的擴展性及支持大文件存儲等特性,將數據文件及文件描述信息(文件元數據)根據文件大小存儲到小文件集合或GridFS中[7],利用MongoDB在海量結構化數據處理方面的優越性能及模式自由等特性,將每個數據集的結構化數據分別存儲在一個集合中[8]。元數據集合中每個文檔存儲一個數據集元數據,同時存儲該數據集對應的所有文件引用及對應的結構化數據集合引用。
基于ODSMM模型,在數據集檢索時,應用程序調用MongoDB接口從數據集元數據集合中查找滿足條件的數據集。在數據集下載時,應用程序先通過文件大小判斷文件存儲在GridFS還是小文件集合,再通過文件ID定位到具體文件,調用相應的MongoDB接口讀取文件二進制數據。在API調用時,先傳入篩選條件、排序字段及排序方式等參數,再根據頁碼及每頁大小定位到數據起始位置,調用相應的MongoDB接口從結構化數據集合中精確查找出滿足條件的數據。所以ODSMM模型不僅能夠支持通過數據集下載方式獲取數據,而且能夠支持通過API調用方式獲取數據。
為了更方便存儲管理開放數據,需對數據集進行描述,數據集描述的元數據主要包括數據集名稱、簡介、創建時間、發布者、關鍵字等核心元數據[9],還包括文件個數、文件引用等擴展元數據。如果數據文件為結構化或半結構化文件,為了了解文件內部數據結構和提供更詳細的接口說明,還需要添加結構化數據描述相關的擴展元數據,如字段描述信息、結構化數據集合大小、結構化數據集合引用等。通過查看數據集元數據,可以大致了解一個數據集的基本信息。
在ODSMM模型中,元數據存儲程序調用MongoDB接口將數據集元數據存儲到元數據集合中,該集合中每個文檔都存儲了一個數據集的基本信息。為了能夠在數據集查找及下載時找到對應的文件,每個元數據文檔還存儲了文件引用,隨著數據集的更新,很可能一個數據集包含多個文件,所以使用數組來存儲所有文件引用。為了能夠在API調用時找到對應的結構化數據,每個元數據文檔還存儲了結構化數據集合引用。此外,因結構化數據字段描述信息較復雜,所以采用BSON數組來存儲字段描述信息。數據集元數據集合詳細存儲結構如下所示:
{
″_id″:ObjectId(″IDENTIFIER″),
″title″: ″TITLE″,
″publisher″: ″PUBLISHER″,
″creator″: ″CREATOR″,
″date″: DATE_ TIMESTAMP,
″language″: ″LANGUAGE″,
″rights″: ″RIGHTS″,
″modified″: MODIFIED_ TIMESTAMP,
″description″: ″DESCRIPTION″,
″category″: ″CATEGORY″,
″keyword″: ″KEYWORD″,
″fileCount″: COUNT_INTEGER,
″dataCount″: COUNT_INTEGER,
″files″:[
ObjectId(″FILE_ID1″),
ObjectId(″FILE_ID2″)
],
″fields″: [
{
″name″: ″NAME″,
″type″: ″TYPE″,
″value″: ″VALUE″,
″description″: ″DESCRIPTION″
},
{
″name″: ″NAME″,
″type″: ″TYPE″,
″value″: ″VALUE″,
″description″: ″DESCRIPTION″
}
],
″ref″: ″DATA_COLLECTION″
}
為了更方便存儲管理數據文件,首先需對單個文件進行描述,主要包括文件名、文件大小、文件格式、上傳時間、下載次數等信息,這些描述文件的屬性稱為文件元數據。在ODSMM模型中,文件管理主要是對文件元數據,結構化文件、半結構化文件和非結構化文件進行存儲管理。MongoDB使用了BSON(Binary JSON)格式存儲數據,無需定義任何結構,可以把完全不同結構的數據存儲到一個集合。目前由于MongoDB的BSON單個文檔16 MB大小限制,同時又因為文件文檔還需要存儲一些必要的文件元數據信息,所以規定15.5 MB以下的文件,稱為小文件,超過15.5 MB的文件,稱為大文件,故文件管理可分為小文件管理和大文件管理兩部分[10]。
1.2.1 小文件管理
對于小文件存儲,可以將文件元數據和文件二進制數據直接存儲到一個文檔中,小文件集合的數據結構如下:
{
″_id″:ObjectId(″IDENTIFIER″),
″filename″:″FILENAME″,
″format″: ″FORMAT″,
″uploadDate″: UPLOAD_TIMESTAMP,
″length″: INTEGER,
″downloadCount″: INTEGER,
″data″: BINARY DATA
}
小文件存儲:文件存儲程序調用MongoDB接口將文件元數據和二進制內容數據寫入小文件集合,寫入成功后,更新數據集元數據集合中相應的元數據文檔,即將小文件ID映射添加到對應文檔中。
小文件讀取:應用程序首先調用MongoDB接口從數據集元數據集合中找到小文件ID映射,然后根據文件ID調用MongoDB接口讀取小文件元數據和二進制內容數據。
1.2.2 大文件管理
大文件使用GridFS進行存儲,GridFS是存儲和檢索超過BSON文檔16 MB大小限制的二進制數據存儲解決方案。GridFS不是將文件存儲在單個文檔中,而是將文件分割成部分或塊,并將每塊存儲為單獨的文檔,默認情況下,GridFS使用255 KB的塊大小,也就是說,除了最后一個塊之外,GridFS將文件分成255 KB大小的若干塊。所以對于大文件存儲,需要兩個集合,一個是fs.files,用于存儲文件基本信息、文件塊總塊數(chunkSize)、文件校驗值(MD5)等文件元數據,fs.files存儲結構如下所示:
{
″_id″:ObjectId(″IDENTIFIER″),
″filename″: ″FILENAME″,
″format″: ″FORMAT″,
″uploadDate″: UPLOAD_TIMESTAMP,
″length″: INTEGER,
″downloadCount″: INTEGER,
″md5″: ″MD5″,
″chunkSize″: INTEGER
}
另一個集合是fs.chunks,主要包括文件ID(fileId),文件塊序號(n),文件二進制數據(data),數據結構如下所示:
{
″_id″:ObjectId(″IDENTIFIER″),
″fileId″: ObjectId(″FILE_IDENTIFIER″),
″n″: INTEGER,
″data″: BINARY DATA
}
大文件存儲:文件存儲程序調用GridFS接口將文件元數據寫入fs.files集合,再將大文件分塊后的各個數據塊依次寫入fs.chunks集合,寫入成功后,更新數據集元數據集合中對應的元數據文檔,將文件ID映射添加到元數據文檔,即完成了大文件寫入。
大文件讀取:應用程序首先調用MongoDB接口從數據集元數據集合中找到大文件ID映射,然后根據文件ID調用GridFS接口讀取大文件元數據和二進制數據。讀取文件二進制數據時,GridFS驅動程序將根據需要重新組合塊,按照塊序號拼接數據塊,生成一個大文件返回給應用程序,即完成大文件讀取。此外,若只讀取大文件的部分數據,只需讀取文件部分數據塊,而不用將整個文件加載到內存。
在ODSMM模型中,對于數據集結構化和半結構化文件,除了需要存儲文件二進制數據外,還需對文件進行數據轉換,將轉換后的具體內容數據存儲到結構化數據集合,結構化數據存儲過程如下:
(1) 數據轉換程序將結構化和半結構化文件轉換成BSON數據。
(2) 根據存儲在數據集元數據文檔中的結構化數據集合引用,找到對應的結構化數據集合。
(3) 結構化數據存儲程序調用MongoDB接口將BSON格式的文件內容數據寫入結構化數據集合。
當數據集新增結構化或半結構化文件時,需將文件內容數據添加到結構化數據集合中,當刪除數據集結構化或半結構化文件時,需將結構化數據集合中相應的文檔刪除。對于結構化數據集合,通常并不會修改單個文檔中的部分數據,一般是對整個文檔刪除或新增。
由開放數據的特點可知,對數據集的新增、刪除及更新操作頻率并不高,大部分操作為應用程序通過API調用對結構化數據進行有條件的查詢操作。API調用的實現方式主要是數據分頁加載,這種方式只需將部分數據加載到內存,只返回當前頁面的數據給應用程序,在需要展示下一頁數據時,再執行一次調用返回下一頁數據。這種方式不會占用過多的內存,快速高效,從而被廣泛使用。API調用參數如表1所示。

表1 API調用參數列表
基于MongoDB的分頁算法中,主要有Skip-Limit算法和Where-Limit算法。MongoDB中使用limit函數限制返回的結果數,使用skip函數跳過指定條數的記錄,所以使用limit并結合skip能夠方便地實現數據分頁,此種方法稱之為Skip-Limit分頁算法。當數據量和并發訪問量較小時,Skip-Limit分頁算法沒有問題,但是當數據量很大時,尤其是需要查找定位大量數據的中間或者后面幾頁數據時,skip函數需要跳過的數據量(偏移量)會很大,有時可能達幾萬、幾十萬,甚至上百萬,這時skip操作會變得很慢。所以文獻[11]中提出一種分頁優化算法Where-Limit算法,算法核心不是計算數據偏移量,而是傳上一頁的數據標記或關鍵詞,根據where條件來查詢實現分頁,這種模式必須有一個連續的索引,才能通過直接指定位置,查找到要顯示頁的起始數據標記。方法是在查詢開始時先將所有數據關鍵詞讀取到數組中,需要分頁時通過頁碼記錄和數組下標的對應關系去查詢頁碼首記錄的數據標記,這種分頁算法也體現了以空間換時間的思想[11]。
充分利用Skip-Limit和Where-Limit兩種算法的優點,提出一種新的算法Skip-Where,主要思路是在偏移量小的時候使用Skip-Limit算法,在偏移量大的時候使用Where-Limit算法。定義偏移量為Offset,即采用Skip-Limit算法時需要跳過的數據量,閾值M表示的Offset的臨界值,則有:
Offset=(PageNumber-1)×PageSize
當Offset小于等于M時,此時需要skip的數據量小,采用Skip-Limit算法更簡單方便、響應時間快,同時不需要使用額外的內存空間。當Offset大于M時,Skip-Limit算法則會變慢,此時采用Where-Limit算法響應時間更快。Skip-Where算法流程圖如圖3所示。

圖3 Skip-Where算法流程圖
基于ODSMM模型,選取2015年1月至2017年3月“上市公司財報”數據集作為實驗數據進行實驗。該數據集包含一個數據文件(financial_statements.json),文件中包含1 100萬條數據。根據第1節描述的方法將數據集存儲到MongoDB中,根據1.2節中描述的文件讀取過程實現了文件下載功能。
根據第2節描述的API調用算法,設Where-Limit算法中采用的關鍵詞為4字節的id字段,則Where-Limit算法需額外的輔助空間為1 100萬×4字節=4 400萬字節,約42 兆字節內存空間。 設Skip-Where算法中偏移量閾值M為10 000,PageSize為10,PageNumber分別為11、101、1 001、10 001、100 001、1 000 001,則對應的Offset分別為1 001 000、1萬、10萬、100萬、1 000萬。分別基于Skip-Limit、Where-Limit、Skip-Where算法執行API調用,得到API調用平均響應時間和所需輔助空間分別如圖4和圖5所示。

圖4 API調用平均響應時間

圖5 API調用輔助空間
通過實驗可以看出,ODSMM模型能夠高效存儲管理開放數據,能夠支持API調用和數據集下載兩種方式獲取開放數據。基于ODSMM模型的API調用優化算法Skip-Where,在海量數據情況下,響應時間在毫秒級,相對于Where-Limit算法,在時間上并沒有太大改進,但在偏移量小于閾值時節省了不必要的輔助空間。此外,當數據量和偏移量達到一定量級時,對內存容量要求會越來越高,如何在保證快速響應情況下進一步優化API調用的輔助空間等問題仍有待進一步研究。