數據導入功能在報表項目中是常常可見的,由於它是報表數據展現的基礎,但對於大量數據的導入,真正從性能、效率等方面兼顧的方案卻不多有。最近在南航廣西數據服務平臺的項目開發中,我須要設計一個能快速將40多萬條數據導入Oracle數據庫的方案,爲了實現導入的高效,我經過在網上收集資料以及動手實踐測試,得出了一些分析總結與你們分享探討。
談到數據導入功能的實現,無可厚非應該包括兩個過程,首先是數據文件的上傳,其次是數據的導入。
1、數據文件上傳
文件上傳本應該是與導入無關,但它處於數據導入功能的一個環節,其效率也顯得有些重要。對於傳統項目,文件上傳一般採用Struts等框架實現的文件上傳機制以及一些開源的文件上傳組件,好比SmartUpload等,經過Html中類型爲File的Input標籤將數據文件獲取,經過流的形式發送服務端,最後由服務端獲取流並寫入文件,如此實現了文件從客戶端到服務器的上傳過程,這些方式咱們均可以將其統一稱爲Web文件上傳。
Web文件上傳是經過Http協議實現的文件流傳輸,其受限於Web數據傳輸的瓶頸,基於Http協議傳輸的數據在傳輸的速度上有必定影響,首先可能出現數據文件請求超時須要數據重傳;其次在每秒能傳輸的字節數在Web方式裏受到限制;最後,因爲Web在傳輸數據文件以前須要將數據文件轉化爲數據流,經過Web的File標籤實現的文件流化效率很低,經過對比試驗發現,一樣對一個50M的文件流化,用Web的File標籤流化的時間要大於採用IO方式流化所消耗的時間,也就是說,對於大數據文件,要將其經過Web標籤流化再傳輸,可能會有很長一段時間是處於發送請求狀態,甚至會由於這個過程的時間持續較長而形成網絡請求超時。這多是優酷等相似的網站上傳視頻不採用Web方式的一個緣由。
除了Web方式,能夠考慮使用Applet,做爲一個客戶端小程序嵌入到網頁中,以IO的方式讀取本地數據文件,而後經過Socket將文件流發送到服務端。這種方式從效率上比Web方式有明顯的改進,首先是經過IO將文件轉化爲文件流的效率提高,其次數據經過Socket方傳輸式,是一種基於TCP協議的網絡傳輸,去除了Web下Http協議對文件傳輸的限制,經過TCP協議直接從網絡的傳輸層進行數據通訊,傳輸速度上必然會更快。綜上,採用Applet加Socket實現網絡文件上傳性能優於Web方式。
然而,Applet實現的網絡文件上傳又並不是最優,緣由在於Applet在運行時受到沙箱的限制。出於對客戶機和服務器的保護,web中的applet程序只能運行在限制的沙箱中,其受到不少安全策略的限制,在applet中不能直接訪問客戶端本地文件系統,除非使用applet受權,採用數字簽名的方式使applet能確認該客戶端系統是可信的。這樣一來,要使用該功能的客戶機系統都須要安裝安全證書,在項目的部署上就顯得十分繁瑣。
以上方式都各有弊端,最終,經過和用戶協商,咱們決定藉助外部的FTP工具,使用開源的文件傳輸工具讓用戶將數據文件直接傳到服務器指定目錄下,在網站系統上就只執行數據文件列表加載。另外,目前也有在web上嵌入FTP功能的插件,其經過activeObject的形式嵌入web,實現相似ftp的文件上傳功能,打算抽空繼續研究。
2、數據導入
大數據導入Oracle數據庫是功能實現的重點。
大數據導入的特色在於數據記錄多,數據插入須要批量執行,分批量commit,以此來減小對數據庫的交互訪問次數,減輕數據庫壓力。在此,我主要探索了兩種導入方式,一種是基於多線程的併發插入,另外一種是利用SqlLoader實現的大數據導入。
(一)基於多線程的併發插入
該方案是在批量到插入的基礎之上採用多線程來執行的方式實現的。
該方案第一步是加載所需的數據文件到內存,生成一個Sql的數組。對於導入的數據文件,通常是EXCEL格式的,對於此種類型數據文件,咱們須要藉助POI來實現EXCEL文件的加載,並經過POI讀取EXCEL中行數據來生成數據插入Sql,文件代碼以下:
private XSSFSheet get07SheetForExcel(File file, String sheetName) {
if (file != null) {
try {
FileInputStream fileInputStream = new FileInputStream(file);
// 建立對Excel工做簿文件的引用
XSSFWorkbook workbook = new XSSFWorkbook(fileInputStream);
// 建立對工做表的引用�1�7�1�7
XSSFSheet sheet = workbook.getSheetAt(0);
// 也可用getSheetAt(int index)按索引引用,
// 在Excel文檔中,第一張工做表的缺省索引是0$1�7
// 其語句爲:HSSFSheet sheet = workbook.getSheetAt(0);
return sheet;
} catch (Exception e) {
}
}
return null;
}
……
for (int i = begin; null != sheet
&& i < sheet.getLastRowNum() - sheet.getFirstRowNum()
+ 1; i++) {
HSSFRow row = sheet.getRow(i);
System.out.println(i);
String[] valuesPerRow = getHSSFRowValues(row);
if (null == valuesPerRow || valuesPerRow.length < 1) {
continue;
}
rows.add(valuesPerRow);
if (rows.size() == CommonParas.PER_IMPORT_SIZE) {
v.importToDB(rows);
rows.clear();
}
}
v.importToDB(rows);
|
這兩個分別是POI加載Excel文件方法和讀取文件行的數據的片斷,從這裏暴露出POI處理大數據EXCEL的問題所在。首先,是將EXCEL文件加載到XSSFSheet對象的一個過程須要消耗不少時間及內存資源,經過測試得出,當EXCEL數據記錄在6萬以上,文件大小超過30M時,此處就會出現異常「JVM內存溢出」,緣由就是在加載EXCEL文件時開銷太大了,即便咱們在啓動服務器時擴大JVM內存分配,這也只是治標不治本的方法;其次,在循環讀取EXCEL行數據時須要預先得到EXCEL中總行數,而得到總行數的方法則是經過函數sheet.getLastRowNum() - sheet.getFirstRowNum()+ 1 實現,在此又出現了另外一個問題,sheet.getLastRowNum()返回值是一個int類型的數據,其最大值只多是65535,那麼它又如何能夠計算出超過65535行記錄的行數呢?因此得出結論:對於大數據的讀取,不能採用POI讀取EXCEL的方式。
不採用EXCEL做爲數據源文件,能夠採用CSV文件代之。CSV是EXCEL可另存爲的數據文件格式,其本質上是以逗號分隔的文本文件,所以,對於此類文件的讀取,咱們能夠採用傳統IO讀取文件的形式,經過字符串分割得到每一個單元格數據,拼接到SQL裏面,造成SQL的數組。
第二步,多線程執行數據導入。
第一步已經將SQL儲存到SQL的數組中,接下來就是利用多線程執行批量插入。此處實現須要將SQL執行類實現Runnable接口或者繼承Thread類,在常量中設置批量插入的記錄數,當加載的SQL數據大小達到該數目時,系統就自動啓動一個線程執行該數組的批量數據導入。爲了確保線程管理的安全,咱們須要定義一個最大線程數,當處於運行的線程數達到最大值時,新開啓的線程將利用wait()方法實現等待,等待有線程執行完畢後纔開始執行。如此調用,直到加載的數據記錄所有都已開始導入才中止線程的啓動,此時爲了監控數據導入是否所有完成,咱們會再新開啓一個後臺線程,設置爲守護進程setDaemon(true),該類線程特色是在全部線程結束後將自動結束,其用於收集每一個導入線程的執行狀態,當所分配的全部線程狀態都不是active時就表示數據導入完成。
利用該方案實現的數據導入較單線程執行的批量數據導入效率提升多倍,從測試導入40萬數據結果來看,單線程批量導入耗時19分鐘,而基於多線程的導入只用了5分鐘左右的時間。但從性能消耗上來看,多線程方案平均同時工做線程數爲15個左右,CPU利用率高達90%,內存消耗約500M,對於服務器自己已形成了必定的壓力,雖然在速度上提高了,其對於服務器的穩定性將形成安全隱患。
此外,對於多線程工做效率的探索上也有一點心得。多線程的出現更多的是迎合多核處理技術的革新,在單CPU工做的主機上,多線程看起來貌似是多個線程併發執行,但從操做系統的角度出發其仍然處於串行狀態,由於在同一時間,處理器只對一個任務進行調度,只不過是輪詢的時間間隙較短不容易發覺。若是在多核處理的主機上,就會有多個處理器同時處理併發的線程,這樣才能實現真正意義上的併發調度,因此多線程仍是依賴於硬件自己。爲了驗證效率,當咱們把執行導入的各個線程以webService的形式部署到不一樣的虛擬機中去執行時,效果就不同了,效率明顯還會提高。由此引出一個當今IT行業的一個熱點,虛擬化技術的實現與應用,有利於資源的優化配置,在有限的資源上實現更大的利用價值,該技術在雲計算領域也是頗受關注的。
(二)利用SQLLOADER執行大數據導入
SQLLOADER是oralce內置的一個命令工具,其可實現將CSV數據文件或者文本數據文件一次性導入數據庫中。它的使用須要有兩個文件,一個是須要導入的數據文件,另外一個是控制導入的控制文件。該方案的執行須要三個步驟,第一是格式化數據文件,第二是生成控制文件,最後是執行導入命令。
第一,格式化數據文件。
數據文件不是已經上傳了,爲何須要格式化呢?緣由在於SQLLOADER對於導入數據文件要求的嚴格性。利用SQLLOADER導入的數據文件有以下要求:
一、數據行之間要有合法換行標誌,數據與數據之間須要有合法分隔符,通常狀況下數據之間用逗號分隔,數據記錄之間用換行符分隔;
二、要導入的數據是直接導入到數據庫的內容,不能包含表頭信息,對於數據文件中有表頭的須要將其去除;
三、數據中不能包含既定的數據分隔符,好比逗號,由於其在導入時對於數據個數不匹配的將視爲錯誤數據被拒絕。
所以,爲了確保數據可以準確的被導入到數據庫,在數據導入以前咱們須要驗證原數據文件,並讀取其數據信息生成格式化的新數據文件提供導入。這也許是這種導入方式繁瑣的地方,但出於安全又不得不作。
第二,生成控制文件。
SQLLOADER執行數據導入並不是直接經過命題操縱數據文件,而是經過一個控制文件規定了導入數據的方式,包括數據文件路徑及導入數據字段等等,它經過執行該控制文件來實現數據的導入。下面來看一個控制文件內容。
load data
infile 'e:\test\艙位數據源(20120101-20120331).csv' (1)
append into table CZDS_CABIN (2)
fields terminated by ',' (3)
( CARRIER, (4)
ORGCITY,
ARRIVALCITY,
FLYDATE "to_date(:FLYDATE,'YYYY-MM-DD')",
FLIGHTNO,
FLIGHTSEG,
AIRLINE,
AIRLINETOANDFROM,
FLIGHTTYPE,
ORGUNITS,
MOTHERCABIN,
SONSPACE,
DISCOUNTFACTOR,
CLASSES,
FLIGHTNAVIGATIONSECTION,
BOARDINGTIMES,
GROUPTICKETRNUMBER,
REVENUEFORECASTING,
SEATINGSECTOR,
SEATINGNAVIGATIONSECTION,
SEATINGSONTNSECTION )
|
(1)定義須要導入的格式化數據文件路徑,能夠是CSV文件或者TXT文本文件;
(2)定義導入數據庫的方式爲append,須要插入數據的表名爲CZDS_CABIN;
(3)定義數據間的分隔符爲逗號,此處,還能夠定義更多的控制命令,好比是否容許插入空數據命令trailing nullcols等等。
(4)定義導入數據的對應的字段。這裏所列的字段要與數據文件中每行數據中數據對應的列一致,由於SQLLOADER是按照這個順序對數據進行插入的。其次,還有個值得注意的地方,在列名的後面能夠用雙引號定義導入後數據的格式。SQLLOADER導入數據時區分格式的,當插入數字和字符類型數據時能夠根據文本進行自動轉換,可是當插入數據是時間類型時,須要在此處定義格式轉換,利用to_date方法將字符轉化爲日期再執行插入。同時,利用這個控制,能夠實現對插入數據的格式化,好比利用lpad進行左填充等等,這也是SQLLOADER相對靈活的地方。
第三步,經過SQLLOADER命令執行數據導入。
這裏的SQLLOADER命令是一個命令行命令,須要在JAVA程序中執行CMD程序來調用。
Process process = null;
try {
process = java.lang.Runtime.getRuntime().exec(cmd);
} catch (Exception e) {
e.printStackTrace();
}
|
Cmd就是須要執行的SQLLOADER命令。SQLLOADER命令以下:
sqlldr userid=mrl/mrl123456 control=e:\\test\\control1.txt log=aa.log bad=bad.bad direct=true parallel=true
Userid指的是本地數據庫的用戶名和密碼,不正確將鏈接不上數據庫。
Control的值是咱們以前生成的控制文件路徑,命令將會執行該控制文件。
Log:這裏定義了導入的日誌文件路徑。
Bad:當導入失敗時,該文件裏存放了沒有成功導入的數據記錄。
Direct:開啓直接導入路徑模式。
Parallel:開啓併發導入模式。
在程序中執行此命令後,經過process.waitFor()方法來等待命令執行的返回,若返回值爲0則說明程序正常結束,數據導入成功;如返回1,則說明數據導入中出現異常,須要排查日誌文件,數據導入失敗。
利用SQLLOADER執行數據導入的缺點在於其對數據文件格式要求的嚴格以及其只能導入本地數據庫,而沒法執行遠程導入,且導入數據只能經過閃存回滾,存在風險性。但其優勢又很明顯,導入數據速度很是快,經過測試結果顯示,導入40萬數據僅用了7-8秒,較以前額方法可謂一個飛躍,這也許是其贏得你們一致承認的緣由所在。
篇末,經過此次對數據導入功能實現方案的探索,我學習到了不少知識和技巧,對於數據導入的實現原理也有了本身的認識,也但願能和你們探討交流。