黃向平,彭明田,楊永凱
(1.中國民航信息網(wǎng)絡股份有限公司,北京 101318;2.民航旅客服務智能化應用技術重點實驗室,北京 101318)
后臺高并發(fā)高性能系統(tǒng)設計過程中,經(jīng)常碰到數(shù)據(jù)訪問寫少讀多的場景。比如民航航班運價搜索平臺,航班運價數(shù)據(jù)更新不頻繁,但搜索過程需讀取并處理大量復雜數(shù)據(jù)。如何解決復雜數(shù)據(jù)讀取效率的問題,是系統(tǒng)性能優(yōu)化的關鍵問題之一。
Linux環(huán)境下高并發(fā)高效率的數(shù)據(jù)訪問技術有NoSQL內存數(shù)據(jù)庫[1]、嵌入式數(shù)據(jù)庫[2]等。NoSQL(not only SQL)內存數(shù)據(jù)庫典型代表有redis[3],它是一種key-value類型的數(shù)據(jù)庫產品,在大數(shù)據(jù)平臺[4]、緩存系統(tǒng)[5]中得到了廣泛的應用,相對于關系型數(shù)據(jù)庫,具有服務響應快、并發(fā)訪問高、易于擴展、易于開發(fā)等優(yōu)點。嵌入式數(shù)據(jù)庫是一種輕量級的、與應用進程在同一個地址空間的數(shù)據(jù)庫,Berkeley DB是目前應用較廣泛、較穩(wěn)定的技術選擇,不但適用于嵌入式系統(tǒng),也同樣適用于高性能后臺服務系統(tǒng)[6],優(yōu)點是不需要進程間通信,讀取效率很高。
對上述兩種高性能數(shù)據(jù)庫的研究發(fā)現(xiàn),當數(shù)據(jù)結構復雜、讀取頻繁時仍會有性能問題。鑒于此,文中提出了基于內存映射文件的復雜對象快速讀取方法。主要從三個方面對讀取過程進行改進:(1)通過內存映射文件[7],省掉了內核態(tài)到用戶態(tài)的內存拷貝操作,從而提高數(shù)據(jù)讀取效率;(2)優(yōu)化文件內數(shù)據(jù)格式,無需轉化即可直接使用,從而提高數(shù)據(jù)格式轉化的效率;(3)復用模板庫和容器,避免重新編碼實現(xiàn)一些常用數(shù)據(jù)結構,從而提高開發(fā)效率。
近年來,隨著計算機技術的不斷發(fā)展,特別是互聯(lián)網(wǎng)應用的擴張普及,對于高并發(fā)環(huán)境下的數(shù)據(jù)庫技術提出了高可用、高性能的要求。在一些諸如交通、電信、金融、電子商務之類的應用中,對于響應時間有非常高的要求。當前隨著半導體技術的發(fā)展,摩爾定律依然有效,內存芯片的價格不斷降低,服務器內存存儲容量已經(jīng)向TB級別邁進。相應的64位CPU和Linux操作系統(tǒng)的普及,操作系統(tǒng)尋址空間達到了2的64次方。內存資源緊張昂貴的歷史一去不復返,如何充分、有效地利用內存資源成了軟件設計的重點。因此,對于那些數(shù)據(jù)訪問實時性要求很高的應用來說,開始考慮把整個數(shù)據(jù)庫或者部分數(shù)據(jù)存儲于內存之中,從而產生了內存數(shù)據(jù)庫的概念。
一般定義上的內存數(shù)據(jù)庫,是指數(shù)據(jù)常駐內存,在數(shù)據(jù)讀寫執(zhí)行過程中沒有內外存之間的數(shù)據(jù)I/O,顯然,它需要較多的物理內存,但也不一定把整個數(shù)據(jù)庫置于內存當中。和傳統(tǒng)的基于機械磁盤來存儲數(shù)據(jù)的數(shù)據(jù)庫系統(tǒng)相比,擁有更快的數(shù)據(jù)讀寫速度,因為內存數(shù)據(jù)庫在指令執(zhí)行過程當中沒有磁盤讀寫操作。同時,內存I/O性能遠高于磁盤,所以基于內存的數(shù)據(jù)庫讀寫性能也遠高于傳統(tǒng)磁盤數(shù)據(jù)庫。
典型的內存數(shù)據(jù)庫如memcached和redis,是一種nosql數(shù)據(jù)庫,也是一種鍵值對數(shù)據(jù)庫。通過在內存里維護一個統(tǒng)一的巨大的哈希表,能夠存儲各種格式的數(shù)據(jù)。一般的使用方法是減少數(shù)據(jù)庫訪問次數(shù),以此提高系統(tǒng)服務響應時間,也可以把全量數(shù)據(jù)存儲于內存數(shù)據(jù)庫當中,完全替代傳統(tǒng)關系型數(shù)據(jù)庫。通常情況下,受制于物理內存空間大小,在數(shù)據(jù)容量達到指定值后,系統(tǒng)就基于LRU(least recent used)算法[8]自動刪除不適用的數(shù)據(jù)。可以說這類內存數(shù)據(jù)庫是為緩存而設計的服務器,并沒有過多考慮數(shù)據(jù)的永久性問題。不過redis加入了virtual memory改進方案,把很少使用的value保存到磁盤,而所有key保存于內存當中。因此redis可以應用于海量復雜數(shù)據(jù)的高性能讀寫場景。
此類內存數(shù)據(jù)庫是基于epoll網(wǎng)絡事件處理模型的C/S服務架構,支持高并發(fā)的網(wǎng)絡連接,充分利用了網(wǎng)絡I/O能力。如果把客戶端與服務端部署于同一臺服務器,則能避免網(wǎng)絡延遲,只需進程間通信即可完成數(shù)據(jù)訪問,提升響應速度。
嵌入式數(shù)據(jù)庫通常與嵌入式操作系統(tǒng)及具體的應用集成在一起,無需獨立運行數(shù)據(jù)庫服務實例,由程序直接調用響應的API即可讀寫數(shù)據(jù),不需要對某種查詢語言進行解析,也不需要生成解析計劃。不僅僅適用于嵌入式操作系統(tǒng),在一些高性能系統(tǒng)當中也直接連接嵌入式數(shù)據(jù)庫,和應用程序運行在同一個地址空間,避免了進程間通信的消耗,因此具有較高的運行效率。
Berkeley DB是目前使用較多的一種嵌入式數(shù)據(jù)庫,作為一種輕量級嵌入式數(shù)據(jù)庫,為許多編程語言提供了API接口,包括c、c++、java、python等,所有數(shù)據(jù)庫相關操作都由Berkeley DB庫函數(shù)負責統(tǒng)一完成。這樣在多進程讀寫并行訪問,或者在同一個進程內多線程競爭訪問,都可以保證數(shù)據(jù)的一致性、完整性。底層的數(shù)據(jù)加鎖、事務日志和存儲管理等都在Berkeley DB庫函數(shù)中實現(xiàn),它們對于應用程序而言是完全透明的。
Berkeley DB對于任何存入的value數(shù)據(jù)都是原樣拷貝到數(shù)據(jù)文件當中,無論是文本數(shù)據(jù)還是二進制數(shù)據(jù)。它提供了四種存儲數(shù)據(jù)的模式:btree、hash、queue和recno,在打開數(shù)據(jù)庫的時候,需要指定其中一種存儲模式。雖然有多種存儲模式,但鍵值對是數(shù)據(jù)庫的基礎數(shù)據(jù)結構。用API函數(shù)訪問數(shù)據(jù)庫時,只需提供關鍵字就能夠訪問相應的數(shù)據(jù)。鍵值對結構在Berkeley DB中都是用一個名叫DBT的簡單數(shù)據(jù)結構來表示的,它的作用主要是保存相應的內存地址和長度。
數(shù)據(jù)的讀取過程,就是數(shù)據(jù)從數(shù)據(jù)庫傳輸?shù)綉眠M程,并轉化為運行時數(shù)據(jù)結構的過程。為了獲得比傳統(tǒng)內存數(shù)據(jù)庫、嵌入式數(shù)據(jù)庫更高讀取性能的讀取方法,在此分解研究數(shù)據(jù)傳輸轉化過程,并依此提出優(yōu)化目標對象。
進程地址空間分為內核態(tài)和用戶態(tài)[9],其中內核態(tài)地址空間為所有進程與內核共享,用戶態(tài)地址空間為每個進程獨有。不管是跨網(wǎng)絡、跨進程,還是讀取文件的數(shù)據(jù)傳輸方式,都涉及到內核態(tài)數(shù)據(jù)拷貝至用戶態(tài)的過程。具體過程如圖1所示。
圖1 數(shù)據(jù)庫數(shù)據(jù)傳輸轉化過程
(1)內存數(shù)據(jù)庫有獨立運行的數(shù)據(jù)庫引擎實例,數(shù)據(jù)依賴網(wǎng)絡傳輸,數(shù)據(jù)包到來后網(wǎng)卡通過DMA將數(shù)據(jù)送入內核態(tài)地址空間,交由內核協(xié)議棧來處理,用戶進程通過系統(tǒng)調用拷貝該數(shù)據(jù)包至用戶態(tài)地址空間。如果內存數(shù)據(jù)庫與用戶進程部署于同一臺服務器,則通過Linux回環(huán)接口(loopback)傳輸,它是一個虛擬網(wǎng)絡接口,不與任何硬件相連,數(shù)據(jù)包的傳輸不經(jīng)過網(wǎng)卡直接送入內核態(tài)地址空間,之后的協(xié)議處理傳輸與網(wǎng)絡過程相同。
(2)嵌入式數(shù)據(jù)庫把數(shù)據(jù)存儲于數(shù)據(jù)文件當中。為了避免頻繁重復的磁盤訪問,Linux操作系統(tǒng)在內核態(tài)地址空間內存里維護了一個頁面緩存(page cache)[10],這樣在從磁盤讀取文件內容前查找這頁面緩存是否已經(jīng)裝載了,只有沒有命中緩存時才真正的從磁盤讀取數(shù)據(jù)。頁面緩存的價值有兩個方面:第一,訪問磁盤的速度遠遠低于訪問內存的速度,因此,從內存訪問數(shù)據(jù)比磁盤訪問更快;第二,數(shù)據(jù)一旦被訪問,就很可能在短期內再次被訪問到。這種在短期內集中訪問同一片數(shù)據(jù)的原理被稱作臨時局部原理(temporal locality)。臨時局部原理能保證,如果在第一次訪問數(shù)據(jù)時緩存它,那就極有可能在短期內再次被高速緩存命中。
數(shù)據(jù)格式轉化一般由序列化和反序列化[11]過程組成。將程序運行過程中的對象轉化成可以存儲或運輸?shù)男问降倪^程,就是序列化(serialization)。反之,序列化結果轉化成對象形式的過程,就是反序列化(de-serialization)。在傳統(tǒng)的關系型數(shù)據(jù)庫中,序列化和反序列化操作都隱含在數(shù)據(jù)庫讀寫操作當中,對用戶是透明的。內存數(shù)據(jù)庫和嵌入式數(shù)據(jù)庫的序列化和反序列化過程一般需要用戶自定義[12]。由于各個系統(tǒng)的軟硬件規(guī)格不同,一般需要編程語言無關的格式來序列化數(shù)據(jù),常見的有xml/json[13]等可見字符文本,以及效率更高的二進制數(shù)據(jù)流傳輸方式protocol buffers[14]。當數(shù)據(jù)量很大,或者獲取頻次很高時,序列化和反序列化會大量消耗系統(tǒng)資源。
針對上述提出的內核態(tài)到用戶態(tài)的內存拷貝,以及反序列化等帶來的讀取性能問題,文中針對性地提出改進方法。
為了盡可能地減少拷貝操作,降低內存消耗,可采用系統(tǒng)調用mmap,即內存映射技術。內存映射技術是Linux的一種內存管理方法,通過這種方法,在不占用額外的內存空間條件下,就能實現(xiàn)目標磁盤文件與進程用戶態(tài)虛擬內存空間的對應關系,省去了文件IO等操作。
圖2 內核態(tài)與用戶態(tài)的內存拷貝與映射
以讀取性能較優(yōu)的嵌入式數(shù)據(jù)庫Berkeley DB為例,比較分析mmap的性能優(yōu)勢。Berkeley DB以文件的形式存儲數(shù)據(jù),讀取數(shù)據(jù)所采用的系統(tǒng)調用是read。read和mmap方式的差異比較如圖2所示。磁盤文件裝載于內核態(tài)的頁面緩存區(qū)域,進程以read方式訪問文件,在其用戶態(tài)地址空間獨立拷貝一份數(shù)據(jù),每個read進程獨享一份數(shù)據(jù)拷貝。也可以在多個read進程間共享一份用戶態(tài)數(shù)據(jù)拷貝,其弊端是為了保證數(shù)據(jù)一致性需要額外的鎖機制,讀取頻繁時多個進程競爭等待造成讀取效率降低,由于此問題較復雜且與該文相關度不高,不再詳述。相對而言,mmap方式既簡單又高效,不但沒有拷貝操作,內存消耗也很低,整個物理內存只存在一份文件數(shù)據(jù)(在頁面緩存區(qū)域)。應用程序不需要考慮文件映射內的數(shù)據(jù)是否已經(jīng)裝載,也不需要考慮如何在多個進程間共享,這些Linux內核都已處理,對用戶透明。如果磁盤文件過大超過了物理內存大小,Linux內核會以LRU(least recent used)算法把冷數(shù)據(jù)置換出來,從而釋放內存來存儲新數(shù)據(jù),最終在頁面緩存中的數(shù)據(jù)是經(jīng)常被訪問的熱數(shù)據(jù)。
復雜對象不但數(shù)據(jù)量大,且數(shù)據(jù)間關系復雜,體現(xiàn)在數(shù)據(jù)結構上就是指針引用較多。當把復雜對象序列化于文件時,指針就失效了,必須將相應的指針轉化為指針指向的目標數(shù)據(jù)塊在文件中的偏移量。如圖3所示的哈希表,哈希桶內的指針指向鍵值對結構,當序列化于文件時,該指針需修改為鍵值對結構在文件中的偏移量。當用戶進程讀取該哈希表時,得到的文件偏移量加上文件映射首地址,即是目標鍵值對結構的內存地址。
圖3 復雜對象的指針轉化
在64位地址空間[15]當中,有大量的空閑空間可供程序使用。因此,經(jīng)過如下步驟(見圖4),則可在文件中直接存儲指針,避免文件偏移量的轉化:
(1)序列化進程在指定虛擬內存區(qū)域內分配對象內存,直至整個復雜對象存儲于該指定虛擬內存區(qū)域。
(2)把該內存區(qū)域內的二進制數(shù)據(jù)dump到內存鏡像文件。
(3)由反序列化進程映射該內存鏡像文件,映射地址為之前指定的虛擬內存區(qū)域。
(4)復雜對象不需要任何格式轉化,即可被反序列化進程使用。
圖4 復雜對象共享過程
如2.2節(jié)所述,優(yōu)化數(shù)據(jù)格式的重點在于如何把一個復雜對象序列化于指定虛擬內存區(qū)域。假設某個復雜對象為多重key-value結構(value部分又是一個key-value結構),這就要求開發(fā)人員開發(fā)一個特定的哈希表數(shù)據(jù)結構。當復雜對象使用的數(shù)據(jù)結構更多時,相應的特定數(shù)據(jù)結構開發(fā)工作也更多,開發(fā)效率低下。由此,引入了可復用組件C++標準模板庫[16]。
C++標準模板庫STL(standard template library)是一套標準化組件,由六大組件組成:
(1)container:泛型容器,基本數(shù)據(jù)結構有數(shù)組vector,鏈表list,二叉樹map,哈希表unordered_map等。
(2)algorithms:泛型算法,常見的排序sort,搜索search等。
(3)iterator:迭代器,借此container和algorithms解耦,兩者可以各自獨立發(fā)展互不關聯(lián)。
(4)function object:是一種模板函數(shù)對象,常見的有map容器中的比較對象std∷less。
(5)adaptor:適配器,修飾container或function object接口的一種組件。比如棧stack作為容器適配器,可以實現(xiàn)為基礎容器類型(vector,list)的適配。
(6)allocator:內存分配器,STL提供了默認內存分配器。
泛型容器提供了一些常用的數(shù)據(jù)結構,可以嵌套使用,比如借助unordered_map可實現(xiàn)多重key-value結構。內存分配器負責數(shù)據(jù)對象的內存分配,默認內存分配器是基于gnu libc的內存分配算法[17],內存分配分散于進程堆空間之中,無法區(qū)分共享數(shù)據(jù)與私有數(shù)據(jù),兩類數(shù)據(jù)交錯存放。通過自定義內存分配器,使得需要共享的大小不一的各種C++對象從指定的地址空間中分配,其他私有對象仍然從進程堆空間分配。最終,序列化進程把指定內存區(qū)域內的連續(xù)內存塊數(shù)據(jù)dump出來保存,即為可共享的數(shù)據(jù)文件。
該方案的實現(xiàn)關鍵點在于如何把對象序列化于數(shù)據(jù)文件當中。數(shù)據(jù)的讀取操作相對簡單,把文件映射獲得的內存區(qū)域首地址指針強制轉化為C++對象指針類型即可。
初始階段創(chuàng)建空文件用以存放目標復雜對象。由于對象長度大小事先無法確定,因此一般做法是創(chuàng)建一個較小的空文件,隨著復雜對象的擴張而不斷延長文件長度。這個辦法的缺點是實現(xiàn)相對復雜,延長操作過多的話會影響寫入性能。
借助空洞文件,可以解決該問題。在Linux文件操作中,文件位移量是可以大于文件的當前長度的,在這種情況下,對該文件的下一次寫將延長該文件,并在文件中構成一個空洞,即為空洞文件。位于文件中但沒有寫過的字節(jié)都被預設為0。空洞是否占用硬盤空間由文件系統(tǒng)決定,在一般文件系統(tǒng)中不占用硬盤空間,意味著不需要大量的寫0操作,創(chuàng)建過程耗時極短。
在寫C++對象數(shù)據(jù)文件前,先創(chuàng)建一個空洞文件,文件長度為預估數(shù)據(jù)量的若干倍,比如10倍。無需擔心浪費磁盤空間,因為最終序列化完畢時,可以根據(jù)實際數(shù)據(jù)量縮減文件長度。
內存分配器是處理應用對于內存的分配和釋放要求的,默認內存分配器所分配內存處于進程堆空間。該方案自定義的基于內存映射技術的內存分配器,可分配內存來至磁盤文件映射出的連續(xù)內存空間。其主要接口和成員變量如下:
class MemoryMapAllocator
{
public:
MemoryMapAllocator(const char* filePath, unsigned long maxSize, void* address);
~MemoryMapAllocator();
void* allocate(unsigned long size);
void release(const void* ptr);
private:
void* m_currentPointer;
};
構造函數(shù)的作用是根據(jù)數(shù)據(jù)文件目錄位置,以及預估的數(shù)據(jù)最大大小,創(chuàng)建空洞文件,并映射入指定的用戶態(tài)地址空間(函數(shù)參數(shù)address),成員變量m_currentPointer初始化為映射空間的首地址。
析構函數(shù)的主要功能是以msync系統(tǒng)函數(shù)同步數(shù)據(jù)寫入磁盤,再以munmap系統(tǒng)函數(shù)撤銷內存映射,即數(shù)據(jù)持久化過程。
成員函數(shù)allocate是內存分配操作方法,首先m_currentPointer指針自增函數(shù)輸入?yún)?shù)size字符數(shù),然后函數(shù)返回自增前m_currentPointer的值。一般為了提升內存訪問效率,對輸入?yún)?shù)size做對齊工作,向上取整8的倍數(shù)(64位操作系統(tǒng)環(huán)境)。
成員函數(shù)release是釋放內存操作方法,在方案中并不必要。所有C++對象都分配于映射地址空間,所有數(shù)據(jù)寫入之后,撤銷文件映射并把內存數(shù)據(jù)同步于磁盤文件,所有對象內存自然釋放,因此release函數(shù)內部實現(xiàn)可以為空操作。一種更復雜的實現(xiàn)是重復利用釋放的內存,借助精細的內存管理機制(如slab算法[18]),記錄所有釋放的內存,內存分配算法優(yōu)先在其中尋找符合大小要求的閑置空間。實際應用當中,此類釋放內存數(shù)量有限,重復利用價值不高,且代碼復雜度高,可維護性差,不建議使用。
在創(chuàng)建數(shù)據(jù)文件時,如果數(shù)據(jù)量很大,可借助多線程并發(fā)處理能力來加速處理。為此內存分配器需設計成線程安全。
最簡單的方法是在類MemoryMapAllocator中新增鎖對象,每次調用allocate方法時加鎖,函數(shù)返回時釋放鎖,保證只有一個線程在修改m_currentPointer指針,其余試圖申請內存的線程處睡眠等待狀態(tài)。當線程爭搶鎖劇烈時,頻繁的線程切換就會成為性能瓶頸。
為了解決鎖帶來的弊端,可采用原子操作[19]。所謂的原子操作是指不會被線程調度機制打斷的操作,一旦操作開始,就一直運行到結束,中間不會被其他線程操作打斷。其實現(xiàn)原理是依靠硬件的支持,在X86平臺上,CPU提供了在指令執(zhí)行期間對總線加鎖的手段。避免了線程切換,提升處理速度。
GCC編譯器當前已經(jīng)支持了原子操作,可以對1、2、4、8字節(jié)的數(shù)值或指針類型進行原子的加、減、與、或、異等操作。對于m_currentPointer的加法操作,可以靠原子操作函數(shù)__sync_fetch_and_add(type *ptr, type value)完成,其實現(xiàn)細節(jié)是將value加到*ptr,結果更新到*ptr,并返回操作之前*ptr的值。
為了對改進復雜對象讀取性能做進一步的分析,對傳統(tǒng)內存數(shù)據(jù)庫redis、嵌入式數(shù)據(jù)庫berkeley DB和基于內存映射文件的復雜對象讀取方法進行了性能對比測試。測試目的是在讀取相同數(shù)據(jù)量的前提下,比較各個方案的響應時間值。軟件配置為Linux內核2.6.32,redis libhiredis 0.13,berkeley DB libdb_cxx 4.7;硬件配置CPU頻率2.60 GHz,內存256 G。
在以上測試環(huán)境中,創(chuàng)建一個進程順序進行三次不同方案的數(shù)據(jù)讀取操作,保證了軟硬件資源一致。其中內存數(shù)據(jù)庫redis服務端與客戶端部署于同一臺服務器,避免網(wǎng)絡延時帶來的誤差。內存映射文件數(shù)據(jù)庫采用了C++標準模板庫容器unordered_map。
三個數(shù)據(jù)庫存儲相同key-value鍵值對1 024個,每個key平均2.9 Bytes,每個value平均2 986 Bytes,讀取程序設計為遍歷讀取這1 024個key共600遍,即614 400次查詢操作,實驗結果見表1。
表1 三種數(shù)據(jù)庫方案讀取性能測試結果
從結果上看,文中提出方法的讀取性能相對于傳統(tǒng)方案有非常顯著的提升,與性能較優(yōu)的嵌入式數(shù)據(jù)庫相比其讀取響應時間提升10倍以上。而且如前所述,內存數(shù)據(jù)庫與嵌入式數(shù)據(jù)庫的內存消耗量與讀取進程個數(shù)成線性增長,因為每個進程都要拷貝存儲數(shù)據(jù),而內存映射文件數(shù)據(jù)庫不需要拷貝,其內存消耗量與讀取進程個數(shù)基本不相關。
該方案的另一個優(yōu)點在于支持各種復雜對象,而不單限于key-value結構。由于內存數(shù)據(jù)庫、嵌入式數(shù)據(jù)庫支持的結構類型有限,故只比對了key-value結構。如果把一個復雜對象分拆成多個key-value鍵值對存儲于普通數(shù)據(jù)庫中,其性能更無法與面向對象的內存映射文件數(shù)據(jù)庫相提并論。原因是前者需要多次索引搜索才能組裝成完整的復雜對象,而后者沒有這個過程,所有的對象內數(shù)據(jù)關系都已經(jīng)通過指針關聯(lián)。
傳統(tǒng)數(shù)據(jù)庫在高并發(fā)應用場景中,數(shù)據(jù)拷貝操作頻繁,數(shù)據(jù)量較大時容易成為性能瓶頸。當數(shù)據(jù)對象結構復雜時,不但數(shù)據(jù)庫表結構、索引設計復雜,而且序列化與反序列化過程耗時嚴重。該方案通過內存映射文件、自定義內存分配器、標準模板庫容器等技術,有效解決了復雜對象讀取性能問題。讀取過程沒有進程間通信,不需要數(shù)據(jù)拷貝,不需要反序列化過程即可直接引用對象,與傳統(tǒng)數(shù)據(jù)庫讀取效率對比的實驗表明性能可提升10倍以上。同時,還具有底層技術成熟穩(wěn)定、開發(fā)代碼量少、易于維護等優(yōu)點。內存映射文件技術得到了廣泛的支持,Linux等類Unix操作系統(tǒng)中都提供了相關系統(tǒng)調用。標準模板庫提供了諸多常用數(shù)據(jù)結構,大大降低了復雜對象結構設計與開發(fā)成本。借由此類成熟技術,開發(fā)過程周期短,易于實現(xiàn),后期維護更新成本低,在實際應用中運行穩(wěn)定。在數(shù)據(jù)庫災備設計方面,由于數(shù)據(jù)存儲于文件,易于傳輸,如果數(shù)據(jù)丟失,可以很快從災備數(shù)據(jù)文件中恢復。因此,該方案實用性高,適用于數(shù)據(jù)訪問方式寫少讀多的高并發(fā)高性能搜索、計算等后臺服務系統(tǒng),在工程實踐中有重要的典型意義。