目錄
案例背景
目前影響文件讀寫性能的點在於文件碎片,大量小文件,文件刷盤方式,隨即讀寫等。基於上述幾類瓶頸點給於解決方案。以便解決業務線現場大量的語音文件接入。
磁盤結構
硬盤在邏輯上被劃分爲磁道、柱面以及扇區.如下圖所示:
數據的讀/寫按柱面進行,而不按盤面進行。也就是說,一個磁道寫滿數據後,就在同一柱面的下一個盤面來寫,一個柱面寫滿後,才移到下一個扇區開始寫數據。讀數據也按照這種方式進行,這樣就提高了硬盤的讀/寫效率。
盤面
硬盤的盤片一般用鋁合金材料做基片,高速硬盤也可能用玻璃做基片。硬盤的每一個盤片都有兩個盤面(Side),即上、下盤面,一般每個盤面都會利用,都可以存儲數據,成爲有效盤片,也有極個別的硬盤盤面數爲單數。每一個這樣的有效盤面都有一個盤面號,按順序從上至下從“0”開始依次編號。在硬盤系統中,盤面號又叫磁頭號,因爲每一個有效盤面都有一個對應的讀寫磁頭。硬盤的盤片組在2~14片不等,通常有2~3個盤片,故盤面號(磁頭號)爲0~3或 0~5。
磁道
磁盤在格式化時被劃分成許多同心圓,這些同心圓軌跡叫做磁道(Track)。磁道從外向內從0開始順序編號。硬盤的每一個盤面有300~1 024個磁道,新式大容量硬盤每面的磁道數更多。信息以脈衝串的形式記錄在這些軌跡中,這些同心圓不是連續記錄數據,而是被劃分成一段段的圓弧,這些圓弧的角速度一樣。由於徑向長度不一樣,所以,線速度也不一樣,外圈的線速度較內圈的線速度大,即同樣的轉速下,外圈在同樣時間段裏,劃過的圓弧長度要比內圈 劃過的圓弧長度大。每段圓弧叫做一個扇區,扇區從“1”開始編號,每個扇區中的數據作爲一個單元同時讀出或寫入。一個標準的3.5寸硬盤盤面通常有幾百到幾千條磁道。磁道是“看”不見的,只是盤面上以特殊形式磁化了的一些磁化區,在磁盤格式化時就已規劃完畢。
柱面
所有盤面上的同一磁道構成一個圓柱,通常稱做柱面(Cylinder),每個圓柱上的磁頭由上而下從“0”開始編號。數據的讀/寫按柱面進行,即磁 頭讀/寫數據時首先在同一柱面內從“0”磁頭開始進行操作,依次向下在同一柱面的不同盤面即磁頭上進行操作,只在同一柱面所有的磁頭全部讀/寫完畢後磁頭 才轉移到下一柱面(同心圓的再往裏的柱面),因爲選取磁頭只需通過電子切換即可,而選取柱面則必須通過機械切換。電子切換相當快,比在機械上磁頭向鄰近磁道移動快得多,所以,數據的讀/寫按柱面進行,而不按盤面進行。也就是說,一個磁道寫滿數據後,就在同一柱面的下一個盤面來寫,一個柱面寫滿後,才移到下一個扇區開始寫數據。讀數據也按照這種方式進行,這樣就提高了硬盤的讀/寫效率。一塊硬盤驅動器的圓柱數(或每個盤面的磁道數)既取決於每條磁道的寬窄(同樣,也與磁頭的大小有關),也取決於定位機構所決定的磁道間步距的大小。
啓停區或着陸區(LandingZone)
磁頭靠近主軸接觸的表面,即線速度最小的地方,是一個特殊的區域,它不存放任何數據
扇區
操作系統以扇區(Sector)形式將信息存儲在硬盤上,每個扇區包括512個字節的數據和一些其他信息。一個扇區有兩個主要部分:存儲數據地點的標識符和存儲數據的數據段。
讀寫過程
即一次訪盤請求(讀/寫)完成過程由三個動作組成:
1)尋道(時間):磁頭移動定位到指定磁道 ,經驗值在3~15ms
2)旋轉延遲(時間):等待指定扇區從磁頭下旋轉經過 ,60*1000/7200/2 = 4.17ms(7200rpm硬盤)
3)數據傳輸(時間):數據在磁盤與內存之間的實際傳輸(200M/S,希捷4TB硬盤)
因此在磁盤上讀取扇區數據(一塊數據)所需時間:
Ti/o=tseek +tla + n *twm
其中:
tseek 爲尋道時間
tla爲旋轉時間
twm 爲傳輸時間
磁盤碎片的產生(1)
俗話說一圖勝千言,先用一張ACSII碼圖來解釋爲什麼會產生磁盤碎片。
這裏所說的方法二就像是我們的windows系統的存儲方式,每個文件都是緊挨着的,但如果其中某個文件要更改的話,那麼就意味着接下來的數據將會被放在磁盤其他的空餘的地方。
如果這個文件被刪除了,那麼就會在系統中留下空格,久而久之,我們的文件系統就會變得支離破碎,碎片就是這麼產生的。
Linux文件碎片基本很少,Linux的ext2,ext3,ext4文件系統——ext4是Ubuntu和目前大多發行版所採用的文件系統——會以一種更加智能的方式來放置文件。Linux的文件系統會將文件分散在整個磁盤,在文件之間留有大量的自由空間,而不是像Windows那樣將文件一個接一個的放置。當一個文件被編輯了並且變大了,一般都會有足夠的自由空間來保存文件。如果碎片真的產生了,文件系統就會嘗試在日常使用中將文件移動來減少碎片,所以不需要專門的碎片整理程序。
希捷硬盤讀寫性能測試報告
硬盤規格:希捷新酷魚2TB硬盤(3碟版)的HD Tune讀取
上述出現原因,從磁盤結構上看,角速度一致,線速度由於半徑越來越小,導致讀寫速度下降。
文件碎片
文件碎片定義
文件碎片是因爲文件被分散保存到整個磁盤的不同地方,而不是連續地保存在磁盤連續的簇中形成的
產生 原因
(1)在文件操作過程中,Windows系統可能會調用虛擬內存來同步管理程序,這樣就會導致各個程序對硬盤頻繁讀寫,從而產生文件碎片。
(2)還有一種情況就是當中間的一個扇區內容被刪除後,新寫入一個較小的文件,這樣在這個文件兩邊就會出現一些空間,這時候再寫入一個文件,兩段空間的任意一部分都不能容納該文件,這時候就需要將文件分割成兩個部分,碎片再次產生了。
(3)最常見的就是下載電影之類的大文件,這期間大家一般都會處理一下其它事情,而下載下來的電影文件被迫分割成若干個碎片存儲於硬盤中。因此下載是產生碎片的一個重要源頭。還有就是經常刪除、添加文件,這時候如果文件空間不夠大,就會產生大量的文件碎片,隨着文件的刪改頻繁,這種情況會日益嚴重。
文件碎片和連續文件讀取性能對比(100M相同內容的文件)
從上圖看出,在採用nio讀取文件的時候,buffer的大小設置爲512kb,讀取性能最高,文件碎片讀取爲80ms,連續文件讀取60ms,得知,文件碎片嚴重影響讀寫性能。
解決方案
在我們目前開發的文件存儲系統,由於各種原因,很難避免文件碎片的產生。基於迅雷下載思想的啓發,我們在從小文件讀取數據寫入大文件的時候,可以事先分配好大文件的尺寸,佔據固定大小的連續的磁盤分區。這樣在採用修改的方式,把每個字節修改成指定的數據,會得到連續完整的文件,避免磁盤碎片的產生。
分配尺寸方式:RandomAccessFile raf = new RandomAccessFile(file, “rw”);
Raf.setLength(1024*1024*1024);
這樣可以分配1G大小的文件,經測試1ms左右的時間可以完成上述分配。
避免隨機讀 寫
隨機讀寫危害
由磁盤結構一節可知,數據寫入和讀取時間由尋址,磁頭旋轉,數據傳輸組成,如果隨即讀寫的話,每一次數據讀寫時間=尋址(12ms)+磁頭旋轉(3ms)+數據傳輸(1ms,小文件),如果是順序讀寫時間=數據傳輸。
隨機讀寫解決方案
磁盤分區循環寫入數據,先將一塊磁盤分成3個分區,1分區寫完,寫2分區,2分區寫完,寫3分區,3分區寫完,將1分區快速格式化,然後將數據寫入1分區。
異步刷盤和同步刷盤
同步刷盤
採用零拷貝的方式,採用nio的強制刷盤策略,等待文件真正落盤後,纔會繼續代碼流程,小文件性能大概在2~3M/s左右。
異步刷盤
javaIo的普通刷盤方式,就是一種異步刷盤,當調用文件IO的flush的方式時,其實是向內核發送一條刷盤指令,直接返回。小文件性能在13M/S左右。
小文件性能影響及解決方案
小文件性能影響
文件讀寫時間=尋址+旋轉延遲+數據傳輸,小文件讀寫的性能瓶頸在尋址上。
小文件200kb:尋址時間=12ms
旋轉延遲=3ms
數據傳輸=1ms
上述讀寫性能在0.2M * (1/0.016)=12.5M/S,IOPS=62次/s
大文件 20GB: 尋址時間=12ms
旋轉延遲=3ms
數據傳輸=20*1024/200=102400ms
上述讀寫性能在 20*1024M *(1/102415) = 200M/s,基本可以發揮文件讀寫極致,瓶頸在數據傳輸上,這是我們想要的效果。
小文件解決方案
將小文件合併成大文件進行存儲,這樣讀取性能就會大大提高,目前kafka的log存儲和讀取原理採用的就是此種方式。
數據塊定義:起始字符,版本標識,chunkSize,subChunkId, subChunkSize,Data,結束標記。
存儲:存儲小文件的時候,首先抽取一個可存入大文件路徑, 封裝小文件的元數據和數據信息,追加到大文件的尾部。返回一個加密後的ID,這個id包含小文件的位置和基本信息。
讀取:讀取根據id進行讀取,也可遍歷讀取。
刪除:在元數據中加標記,不真正刪除文件,避免文件碎片, 後期統一格式化處理。
併發寫提高數據入盤性能
廣義併發寫
一塊磁盤啓用多個線程進行寫數據,根據硬盤知識可知,一塊硬盤有很多磁頭,但是隻有一個有效磁頭在運作,多個線程會造成磁頭爭奪,導致寫入性能下降。
狹義併發寫
一臺linux機器可以多塊硬盤,比如4塊,爲了避免爭奪磁頭,每塊硬盤最多隻能由一個線程操作,這樣就可以同時寫入4個數據分別落到不同的硬盤中,實現方式架構如下圖:
Queue:數據硬盤目錄,四塊硬盤,四個目錄,阻塞隊列
線程池:根據硬盤數量分配線程池線程數量,四塊硬盤,四個線程。
流程:當數據準備入庫,獲取線程,線程從隊列中請求入庫硬盤,根據請求地址入庫。
讀寫分離
讀寫分離的必要性
根據硬盤知識可知,一塊硬盤有很多磁頭,但是隻有一個有效磁頭在運作,如果同時有數據進行讀和寫操作,就會造成磁頭爭奪,導致性能下降。
讀寫分離的解決方案
目前同一進程讀寫分離可以實現,不同進程的讀寫分離無法實現。
業界優秀案例
K afka的log文件系統
topic中partition存儲分佈
Kafka集羣只有一個broker,xxx/message-folder爲數據文件存儲根目錄,在Kafka broker中server.properties文件配置(參數log.dirs=xxx/message-folder),例如創建2個topic名 稱分別爲report_push、launch_info, partitions數量都爲partitions=4
存儲路徑和目錄規則爲:
xxx/message-folder
|--report_push-0
|--report_push-1
|--report_push-2
|--report_push-3
|--launch_info-0
|--launch_info-1
|--launch_info-2
|--launch_info-3
在Kafka文件存儲中,同一個topic下有多個不同partition,每個partition爲一個目錄,partiton命名規則爲topic名稱+有序序號,第一個partiton序號從0開始,序號最大值爲partitions數量減1。
採用優化策略
kafka是一個高吞吐量分佈式消息系統,並且提供了持久化。其高性能的有兩個重要特點:1、併發,2、連續讀寫性能遠高於隨機讀寫。
優化着重於併發策略,下面舉兩個案例說明:
-
一個topic分成多個patition,磁盤總數是一塊,這樣多個分區會往同一塊磁盤寫入數據,造成廣義併發寫,磁頭爭奪,性能急劇下降。
-
一個topic分成多個patition,磁盤總數是多塊,這樣每一個分區會寫一塊硬盤,造成狹義併發寫,性能得到保障。
數據過期刪除及log大小固定策略
Kafka並未提供刪除數據的api,可以設置過期時間,kafka根據過期時間自動刪除。
Kafka的log大小固定,所以一塊盤中的log數據基本上都是一樣大,根據文件寫入規則,基本上不會產生文件碎片。
小文件合併增大吞吐量
衆所周知,kafka 是一個消息中間件,存儲採用固定大小的log系統,當我們將小文件數據或者是一條條日誌存儲到kafka中,都會被寫到指定的log文件,這樣小文件就會被組合成大文件。
每個log entry格式爲"4個字節的數字N表示消息的長度" + "N個字節的消息內容";每個日誌都有一個offset來唯一的標記一條消息,offset的值爲8個字節的數字,表示此消息在此partition中所處的起始位置..每個partition在物理存儲層面,有多個log file組成(稱爲segment).segment file的命名爲"最小offset".kafka.例如"00000000000.kafka";其中"最小offset"表示此segment中起始消息的offset.
獲取消息時,需要指定offset和最大chunk尺寸,offset用來表示消息的起始位置,chunk size用來表示最大獲取消息的總長度(間接的表示消息的條數).根據offset,可以找到此消息所在segment文件,然後根據segment的最小offset取差值,得到它在file中的相對位置,直接讀取輸出即可。