色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

nutsdb的研究以及一些心得體會分享

冬至子 ? 來源:陪計算機走過漫長歲月 ? 作者:陪計算機走過漫長 ? 2022-11-16 11:40 ? 次閱讀

背景

有一天nutsdb學習交流群中有一位老哥說nutsdb重新恢復的速度太慢了,他那邊大概有一個G左右的數據。我對這個問題還是比較感興趣的,當場就接下了這個任務。開啟了這段時間的優化之路,歷時還蠻長時間的,因為平時上班,還有其他的一些興趣愛好什么,所以開源上面的投入倒是有細水長流的感覺。好了,閑話不多說了,下面把這段時間在這個問題上面的研究以及一些心得體會分享給大家。

分析問題

俗語有云,能復現的問題都不是什么大問題。根據學習老哥的描述,我首先要做的就是準備1G的數據,然后benchmark+pprof測試重啟恢復db的cpu和內存情況。最終問題定位到了這段代碼上面:

// ReadAt returns entry at the given off(offset).
func (df *DataFile) ReadAt(off int) (e *Entry, err error) {

   // 讀取header
   buf := make([]byte, DataEntryHeaderSize)


   if _, err := df.rwManager.ReadAt(buf, int64(off)); err != nil {
      return nil, err
   }


   meta := readMetaData(buf)


   e = &Entry{
      crc:  binary.LittleEndian.Uint32(buf[0:4]),
      Meta: meta,
   }


   if e.IsZero() {
      return nil, nil
   }


   // read bucket
   off += DataEntryHeaderSize
   bucketBuf := make([]byte, meta.BucketSize)
   _, err = df.rwManager.ReadAt(bucketBuf, int64(off))
   if err != nil {
      return nil, err
   }


   e.Meta.Bucket = bucketBuf


   // read key
   off += int(meta.BucketSize)
   keyBuf := make([]byte, meta.KeySize)


   _, err = df.rwManager.ReadAt(keyBuf, int64(off))
   if err != nil {
      return nil, err
   }
   e.Key = keyBuf


   // read value
   off += int(meta.KeySize)
   valBuf := make([]byte, meta.ValueSize)
   _, err = df.rwManager.ReadAt(valBuf, int64(off))
   if err != nil {
      return nil, err
   }
   e.Value = valBuf


   crc := e.GetCrc(buf)
   if crc != e.crc {
      return nil, ErrCrc
   }


   return
}

這段代碼的作用是給定一個文件的偏移位置,從這個位置開始往后讀取一條數據。在db重啟恢復的過程中這個函數會被頻繁的調用,為什么呢?我們知道nutsdb是基于bitcask模型實現的,本質上來說是hash索引。在重啟恢復的過程中會加載所有db的數據來重新構建索引。所以理論上來說,重啟的時候當前db有多少數據,這個函數就會被調用多少次,由于這個函數性能不大行,所以這里成為了瓶頸。為什么說這個函數性能不太行呢?且看我娓娓道來。

一條nutsdb中的數據是以這樣的形式被存儲在磁盤中的:

圖片

首先我們需要獲取這條數據的元數據(header),也就是描述真實數據的數據,這里會記錄bucket,key,value的長度,我們拿到這些信息之后會再次發起系統調用去獲取真實bucket,key,value。由這段代碼我們可以看到,這個函數沒執行一次,就會發起四次系統調用,分別在磁盤中獲取meta,bucket,key,value。而且每次都需要申請與數據長度匹配字節數組去承接這些數據。這個現狀有兩個問題,其一是過多的系統調用會是性能的瓶頸,其次是過多的申請內存,在這些對象被使用完之后會在內存中堆積起來,觸發gc之后才會被清除回收。但是如果db數據過多,在這個時候也會觸發gc造成卡頓。

如何解決?

在定位到問題之后,就開始了解決問題的旅程,這段旅程異常的漫長,這段過程翻閱了很多資料,嘗試了很多方法。且看我一一道來。

1. 減少系統調用

在上面的分析中,我們知道讀一條數據需要發起四次系統調用。我覺得這里其實大可不必,兩次就足夠了。因為第一次讀取meta之后,我們就知道bucket,key,value這些數據的長度和位置了加上這些數據都是連續存儲的,直接一次全部拿出來然后分別解析就好。

所以在第一次優化中,將上面的四次系統調用優化成了2次,帶來了一倍速度上的提升。不僅僅是重啟速度上的,因為在運行中讀取數據用的也是這個函數。所以在數據的讀取上同樣也是優化了一倍的讀取速度。下圖是我提交的pr的截圖,我構造了大小分別是50B, 256B,1024B的數據,db總數據1G,做并發讀取性能測試,實驗結果表明此次優化在讀取速度上提升了接近一倍,但是在內存的使用上并沒有減少。

圖片

2. 批量讀取,然后解析數據

在做完系統調用的優化之后的一個星期的時間里,這項優化工作其實陷入了沉寂,有時候會腦爆一下這個該怎么做,翻越了bitcask論文和DDIA對hash索引恢復操作的描述,書中所說都是要將數據一條條的拿出來,沒有可參考的資料感覺有點寸步難行。不過天無絕人之路,有一天打游戲的時候突然想到了,為什么不一次性讀取文件的一部分,比如4KB,然后在這4KB中解析出里面的數據,解析到解析不下去的時候再拿下一個,直到文件解析完畢。下面讓我們展開一下這個思路。

我們知道磁盤底層會把自己存儲空間劃分成一個個扇區,在磁盤之上的文件系統會把磁盤的扇區組成一個個的block,我們的應用程序每次去讀取數據都是按照block去讀取的。打個比方,一個block是4KB,如果我要讀1KB的數據,那么會加載4KB的數據到pagecache,然后返回1KB給我們的應用程序。所以當我們一次次發起系統調用的去磁盤回去數據的時候,實際上數據已經從磁盤里拿出來并且放到pagecache中了。所以我們直接把整個block拿出來就完事了。

圖片

一開始想的是批量獲取一批完整的數據。這個時候其實不太好控制,因為我們不知道一條數據有多大,所以如果要要批量獲取數據還需要額外的數據結構去描述,我把這個東西叫做斷點,也就是說一個文件分成若干個斷點,斷點之間存儲的是一批數據。如果朝這個方向改會衍生出很多問題,其一是在什么樣的標準下面去寫這個斷點,比如按照數據量來記呢,還是按照一個固定的空間大小來記錄。其二是再增加這個邏輯去額外記錄還需要在數據寫入過程中做添加。一來二去感覺太復雜了。

那么我們按照一個個固定的塊來搞。由于我們不知道數據的大小是怎么樣的,所以按照固定塊讀取會出現以下的情況。

圖片

我們看上圖,上圖中的e1和e2代表著在一個存儲文件中一條條連續著存儲的數據,header是元數據,payload是對bucket,key,value這三個數據的整體描述,即有效數據部分。圖中的箭頭代表按塊讀取數據的時候有可能讀到的塊的邊界。其實也很好理解,在讀一個塊的時候可能會出現的情況是:

這個塊就包含了若干條完完整整的數據。這個時候不需要額外的處理。

這個塊前面包含了若干條完整的數據,但是最后的一條是殘缺的,缺失了header。這個時候我們需要讀入header殘余的數據,解析出一整個header。為什么不讀一整個block然后在在block中獲取解析header所需的剩余數據呢?因為我們不知道這條數據長什么樣子,如果他的大小超過了一個block的size,那么還需要在后續做額外的處理。

最后一種情況是缺失了payload,那么我們需要根據缺失數據的大小來判斷要讀多少個block進來。

經過這一分析之后,其實一整個解析數據的流程就是在這三個狀態之間來回轉換。解析的流程就是一個狀態機的算法。等到文件讀完,那么也就解析完了。

圖片

激動的心顫抖的手,代碼寫完之后做了一版benchmark。

BenchmarkRecovery-10           1 2288482750 ns/op 1156858488 B/op 14041579 allocs/op
BenchmarkRecovery-10           2 701933396 ns/op 600666340 B/op 3947879 allocs/op

我們分析一下這個思路下的優化,理論上來說db中一條數據越小,優化效果越明顯,因為這就意味著單次獲取的block中蘊含著更多的數據,就可以減少更多的系統調用。但是我們可以看到的是,雖然啟動的時間變短了,申請內存的次數變少了,但是申請的內存還是不少。

執行兩次啟動恢復1G左右的數據大概花了4G的內存。也就是說一次啟動花了2G,里面有1G消耗不知道是在哪里來的。一開始我以為是讀取數據的時候會在用戶空間往內核空間拷貝,會存在讀取放大的情況。不過轉念一想覺得不大可能,因為pagecache是類似LRU的緩存機制,不會緩存你所有的數據,他也有數據的淘汰策略,不太可能1比1放大的。

所以問題出在哪里呢?我百思不得其解。不過源碼之下無秘密,我反復一次又一次看我寫下的代碼,終于讓我在一個地方發現了貓膩。一個我意想不到的地方,那就是append操作。

我的文件數據恢復恢復操作所代表的結構體是這樣的:

FileRecovery struct {
   fd    *os.File
   rest      []byte
   state     state
   blockSize uint32
   finish    bool
   entry     *handlingEntry
   entries   []*Entry
   off       int64
}

我的解析流程是把文件讀完,把里面的數據一個個append進entries字段里面,文件解析完了再把entries返回。要是在平時我不會覺得這個操作是有什么問題的,不過我準備的數據量太大了(兩百萬條),append應該會觸發多次擴容操作,然后反復申請內存。去翻看了一下append操作的擴容機制,當數據量小于1024的時候,會擴成原來的2倍,數據量大于1024時,會變成原來的1.25倍。看到這里其實基本驗證了我的猜想,不過謹慎起見還是計算了一下, nutsdb數據有segment設置,當一個文件大于這個參數的大小的時候會保存成只讀文件。兩百萬條數據寫了兩個文件,這個結構體是解析單個文件的,我們按照每個文件100萬條數據算。100萬條數據大概要擴容幾次呢:

圖片

1024前面的過程就忽略不計了,直接從1024開始,掏出計算器,這里x的值是30次,那么擴容30次總共需要多少內存呢?

圖片

為什么是乘8呢,因為我是64位機器,entries里面的元素是指針,那么指針的長度和字長是一樣的,也就是8個byte。這里的結果大概是3000萬,3000萬個比特大概是220MB的內存,兩個文件加起來大概就是440MB的內存了。驚不驚喜,意不意外?

那么如何優化這里呢,其實一個個往外傳就可以了,不要等到解析完了堆在數組里再往外丟。如果要在原來的基礎上改的話,我需要每次處理完一條數據的時候記錄下當前這個block處理到哪里,也就是要把block改成ring buffer的形式,這個時候我隱隱約約想到了go的標準庫里有一個東西做了類似的事情,沒錯,就是bufio.Reader.他不僅僅會幫我們看到記錄ringbuffer的位置,還會預讀block。整體上看是完美契合這個需求的。

3. bufio.Reader

bufio.Reader會維護一個默認大小4KB的ring buffer,當讀取的內容大于它所能承載的緩存時,他會直接在讀取而不使用緩存,如果讀取的是比較小的數據,會在他的緩存中copy出來一份返回。這個就完美的契合了我們多次讀取的場景。看一下改成這樣子的性能提升吧:

1.jpg

這下無論是速度上,還是內存的使用上,都達到了一個比較理想的狀態。這次優化歷程就到此結束了。

圖片

總結

做性能優化的感覺就像和計算機對話,依照自己現有的知識去想方案, 然后寫出來之后做實驗求證。做這個的思路是讓他慢慢的變好,而不是上來就追求完美主義,完美主義是不靠譜的,反而會讓你陷入到糾結之中,能優化一點是一點,我們要看到一個變好的趨勢,然后在這個趨勢上面不斷的基于上一次的結果去猜想下一次怎么優化,也就是所謂的“小步快跑”。在做這個的過程中會往各個方向去腦爆,一些背景知識不是很清楚的時候需要翻越各種資料。整體來說是一次很不錯的成長體驗。

審核編輯:劉清

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 磁盤
    +關注

    關注

    1

    文章

    375

    瀏覽量

    25201
  • 狀態機
    +關注

    關注

    2

    文章

    492

    瀏覽量

    27529
  • Hash
    +關注

    關注

    0

    文章

    32

    瀏覽量

    13195
收藏 人收藏

    評論

    相關推薦

    VHDL編程心得體會o

    VHDL編程心得體會o
    發表于 08-20 19:04

    學習fpga心得體會

    學習fpga心得體會
    發表于 09-14 09:02

    Linux鏈表操作心得體會

    研究linux內核自帶的dmatest.c驅動程序過程中發現有部分的鏈接操作,非常迷惑,故在此記錄下來一些查閱資料后的心得體會
    發表于 07-26 08:15

    求大神分享學習51單片機的心得體會

    學習51單片機心得體會51單片機上拉電阻的心得體會
    發表于 03-10 07:32

    C語言編程的學習經驗和心得體會概括

    C語言編程的學習經驗和心得體會有哪些?
    發表于 11-03 06:03

    學習STM32控制蜂鳴器的心得體會分

    以下是學習STM32控制蜂鳴器時的一些心得體會,我也是綜合各種資料寫出來的。蜂鳴器是種很常見的電子元件,般也就發出滴滴的聲音。但自從在網上看到各種用蜂鳴器播放音樂的實例,我就對蜂鳴
    發表于 12-07 07:44

    Ucos2/3系統和FreeRtos系統心得體會記錄

    這段時間學習了Ucos2/3系統和FreeRtos系統,有一些心得體會,寫下來方面作為筆記,另方面作為小伙伴們學習的資料吧!
    發表于 12-22 08:08

    改造電烙鐵的心得體會

    改造電烙鐵的心得體會,30W的電烙鐵在焊接大元件的過程中就覺得要加熱好長時間,這樣就容易將焊接的元件損壞。
    發表于 02-09 11:17 ?9716次閱讀
    改造電烙鐵的<b class='flag-5'>心得體會</b>

    飛機電子狗的正確使用方法-心得體會

    飛機電子狗的正確使用方法-心得體會,感興趣的小伙伴可以看看。
    發表于 07-28 10:21 ?26次下載

    單片機應用研發暑期實習小結_第周焊接部分心得體會

    暑期實習小結,新手可以看看,這個是焊接部分,一些心得體會,給大家分享了4個資源,因第三周是公司的一些產品心得,所以不能上傳。
    發表于 08-17 11:54 ?0次下載

    單片機應用研發暑期實習小結_第二周PCB心得體會

    暑期實習小結,新手可以看看,這個是PCB部分,一些心得體會,給大家分享了4個資源,因第三周是公司的一些產品心得,所以不能上傳。
    發表于 08-17 11:54 ?1次下載

    單片機應用研發暑期實習小結_第四周STM32心得體會

    暑期實習小結,新手可以看看,這個是STM32部分,一些心得體會,給大家分享了4個資源,因第三周是公司的一些產品心得,所以不能上傳。
    發表于 08-17 11:54 ?1次下載

    VHDL編程心得體會

    VHDL編程心得體會,感興趣的小伙伴們可以瞧瞧。
    發表于 11-11 17:17 ?3次下載

    Linux內核閱讀心得體會

    Linux內核閱讀心得體會
    發表于 10-24 08:55 ?8次下載
    Linux內核閱讀<b class='flag-5'>心得體會</b>

    marantz7K?9K試聽會心得體會

    marantz7K?9K試聽會心得體會
    發表于 07-31 15:43 ?0次下載
    主站蜘蛛池模板: 亚洲性无码av在线| 女教师跟黑人男朋友激情过后| 九九热在线视频| 国产人成精品综合欧美成人| AV天堂午夜精品一区| 总攻催眠受的高h巨肉np| oldgrand欧洲老妇人| 亚洲精品久久无码AV片银杏| 色欲久久99精品久久久久久AV | 老阿姨儿子一二三区| 久久www免费人成高清| 肉色欧美久久久久久久蜜桃| 日韩一区二区三区四区区区| 欧美日韩在线成人看片a| 亚洲精品国产字幕久久vr| gogo免费在线观看| 久久是热这里只有精品| 午夜在线观看免费完整直播网页| 99国产强伦姧在线看RAPE| 韩国女人高潮嗷嗷叫视频| 乳欲性高清在线| 伊人久99久女女视频精品免| 妖精视频免费高清观看| 在线观看中文字幕国产| 最近中文字幕2018MV高清在线| 一本道高清码| 51久久成人国产精品麻豆| 最新毛片网| 国产99r视频精品免费观看| 动漫美女性侵| 中文字幕一区中文亚洲| 国产深夜福利视频在线| 日本2021免费一二三四区| 野花影院手机在线观看| 国产免费看片| 日本学生VIDEOVIDEOS更新| 91久久综合精品国产丝袜长腿| 国产在线一卡二卡| 天天色天天综合网| ZZoo兽2皇| 暖暖 免费 高清 日本视频5|