用java搞數據庫

目的

  • 設計一個簡化,高效的KV存儲引擎。
  • 要求提供write,read,range搜索接口。

要求

  • 併發寫入數據性能。
  • 任意執行kill -9來模擬進程意外退出而數據不丟失。

IO

  • key固定爲8字節,能夠用long表示。
  • value爲4kb,4kb整數落盤是很是磁盤IO友好的。
  • 4kb能夠在內存中作索引,可使用int而不是long來記錄數據偏移,內存佔用會減小一半。

kill -9 數據不丟失

光使用內存作存儲很難知足這一點。可是沒有要求斷電不丟失,也就是說:可使用pageCache來作寫入緩存。 因此想到使用pageCache來充當數據和索引的寫入緩衝(二者策略不一樣)。 方案具體能夠參考ES的pageCache方案。數據庫

隨機讀寫

按照隨機寫,隨機讀,順序讀進行實驗。 隨機寫階段不須要在內存維護索引,能夠直接落盤。 隨機讀和順序讀,磁盤均存在數據,恢復索引能夠採用多線程併發恢復。數組

pageCache處理

因爲採用了pageCache,採用腳本方式清空pageCache會比較耗時。 因此不能無節制的使用pageCache,因此準備引入Direct IO。緩存

文件IO

因爲key能夠均勻分佈,採用數據分區方式,能夠大大減小順序讀寫的鎖衝突,key分佈均勻能夠按照key搞n位來作hash,能夠確保key兩個分區之間總體有序,因而能夠嘗試將數據分紅1024,2048個分區。多線程

架構設計

隨機寫入key時,能夠根據key進行hash將隨機寫轉換成對應分區的文件順序寫。 架構

內存維護有序的 key[1024][625000] 數組和 offset[1024][625000] 數組。併發

利用數據分佈均勻特性,將全局數據hash爲1024分區,每一個分區存放兩類文件:索引文件和數據文件(對kafka熟悉的同窗,是否是很親切)。性能

在隨機寫入階段,根據key得到該數據對應分區位置,按照時序,順序追加到文件尾部,將全局隨機轉換爲局部順序。優化

利用索引和數據一一對應特性,不須要將數據的邏輯偏移落盤,在恢復階段能夠按照恢復key的測序,反推value的邏輯偏移量。線程

因爲作了分區,在range查詢階段,partition(N)中任何一個數據必定大於partition(N-1)中任何一個數據,因而咱們能夠大塊的讀,將一個partition總體讀進內存,給64個線程消費。架構設計

讀盤線程負責按分區讀盤進入內存,64個消費線程消費內存,按照key順序訪問內存,進行回調。

優化

使用pageCache實現寫入緩衝區

磁盤IO類型的系統,第一步是測量磁盤IOPS及多少個線程一次讀寫多大的緩存可以打滿IO,在固定64線程寫入前提下,16kb,64kb都可達到理想IOPS,因此能夠爲每一個分區分配一個寫入緩存,湊齊4個value落盤。

因爲要求kill -9不丟失數據,不能簡單的在內存中分配一個ByteBuffer.allocate(4096*4);,能夠考慮mmap內存映射一片寫入緩衝,湊齊4個刷盤,這樣kill -9以後,pageCache不會丟失。

索引文件落盤比較簡單,key固定爲8b,因此mmap能夠發揮寫小數據的優點,將pageCache利用起來,mmap相比filechannel寫索引快3s左右。

隨機寫入後不會當即隨機讀,因此不須要在寫入時維護內存索引,只須要在恢復階段恢復索引順序,反推出數據的邏輯偏移,由於key和value在同一個分區的位置是一一對應的。

恢復階段

須要在數據庫引擎啓動時,將索引從數據文件恢復到內存中。

因爲有1024個分區,可使用64個線程併發恢復索引,使用快速排序對 key[1024][62500] 數組和 offset[1024][62500] 進行 sort,以後再 compact,對 key 進行去重。

數據隨機讀取

根據key定位到分區,以後在有序的key數據中進行二分查找key/offset,拿到數據的邏輯偏移和分區編號,能夠隨機讀取了。

數據順序讀取

順序讀取思路是生產者消費者模型,n個生產者從磁盤讀數據放入內存,64個消費線程消費同時判斷內存數據以驗證數據。

直接內存的使用和JVM調優

堆外內存的好處是大大減小了一分內存拷貝,而且對gc友好。

-
server 
-
Xms2560m
 
-
Xmx2560m
 
-
XX
:
MaxDirectMemorySize
=
1024m
 
-
XX
:
NewRatio
=
4
 
-
XX
:+
UseConcMarkSweepGC
 
-
XX
:+
UseParNewGC
 
-
XX
:-
UseBiasedLocking
  • young區過大,對象在年輕代呆的過久,屢次拷貝。
  • old區過小,會頻繁觸發old區的cms gc。

對於那些須要反覆new出來的東西,均可以池化,分配內存在回收也是不小的開銷,直接可使用threadlocal緩存搞定。

減小線程切換

io線程的切換成本很高,爲了減小io線程的時間片流失能夠考慮使用while(true)輪訓,也能夠採用sleep(1us)避免cpu空轉帶來的總體性能問題。

機器抖動在所不免,避免IO切換不能靠while(true),cpu級別的優化能夠專門騰出4個核心專門給IO線程使用,避免IO線程的時間片徵用(採用Affinity )。

相關文章
相關標籤/搜索