共計 3269 個字符,預計需要花費 9 分鐘才能閱讀完成。
丸趣 TV 小編給大家分享一下如何解決 MongoDB 磁盤 IO 問題,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
IO 概念
在數據庫優化和存儲規劃過程中,總會提到 IO 的一些重要概念,在這里就詳細記錄一下,對這個概念的熟悉程度也決定了對數據庫與存儲優化的理解程度,以下這些概念并非權威文檔,權威程度肯定就不能說了。
讀 / 寫 IO,最為常見說法,讀 IO,就是發指令,從磁盤讀取某段扇區的內容。指令一般是通知磁盤開始扇區位置,然后給出需要從這個初始扇區往后讀取的連續扇區個數,同時給出動作是讀,還是寫。磁盤收到這條指令,就會按照指令的要求,讀或者寫數據。控制器發出的這種指令+數據,就是一次 IO,讀或者寫。
大 / 小塊 IO,指控制器的指令中給出的連續讀取扇區數目的多少,如果數目很大,比如 128,64 等等,就應該算是大塊 IO,如果很小,比如 1,4,8 等等,就應該算是小塊 IO,大塊和小塊之間,沒有明確的界限。
連續 / 隨機 IO,連續和隨機,是指本次 IO 給出的初始扇區地址,和上一次 IO 的結束扇區地址,是不是完全連續的,或者相隔不多的,如果是,則本次 IO 應該算是一個連續 IO,如果相差太大,則算一次隨機 IO。連續 IO,因為本次初始扇區和上次結束扇區相隔很近,則磁頭幾乎不用換道或換道時間極短;如果相差太大,則磁頭需要很長的換道時間,如果隨機 IO 很多,導致磁頭不停換道,效率大大降底。
順序 / 并發 IO,這個的意思是,磁盤控制器每一次對磁盤組發出的指令套(指完成一個事物所需要的指令或者數據),是一條還是多條。如果是一條,則控制器緩存中的 IO 隊列,只能一個一個的來,此時是順序 IO;如果控制器可以同時對磁盤組中的多塊磁盤,同時發出指令套,則每次就可以執行多個 IO,此時就是并發 IO 模式。并發 IO 模式提高了效率和速度。
IO 并發幾率。單盤,IO 并發幾率為 0,因為一塊磁盤同時只可以進行一次 IO。對于 raid0,2 塊盤情況下,條帶深度比較大的時候(條帶太小不能并發 IO,下面會講到),并發 2 個 IO 的幾率為 1 /2。其他情況請自行運算。
IOPS。一個 IO 所用的時間=尋道時間+數據傳輸時間。IOPS=IO 并發系數 /(尋道時間+數據傳輸時間),由于尋道時間相對傳輸時間,大幾個數量級,所以影響 IOPS 的關鍵因素,就是降底尋道時間,而在連續 IO 的情況下,尋道時間很短,僅在換磁道時候需要尋道。在這個前提下,傳輸時間越少,IOPS 就越高。
每秒 IO 吞吐量。顯然,每秒 IO 吞吐量=IOPS 乘以平均 IO SIZE。Io size 越大,IOPS 越高,每秒 IO 吞吐量就越高。設磁頭每秒讀寫數據速度為 V,V 為定值。則 IOPS=IO 并發系數 /(尋道時間+IO SIZE/V),代入,得每秒 IO 吞吐量=IO 并發系數乘 IO SIZE 乘 V /(V 乘尋道時間+IO SIZE)。我們可以看出影響每秒 IO 吞吐量的最大因素,就是 IO SIZE 和尋道時間,IO SIZE 越大,尋道時間越小,吞吐量越高。相比能顯著影響 IOPS 的因素,只有一個,就是尋道時間。
MongoDB 磁盤 IO 問題的 3 種解決方法
1. 使用組合式的大文檔
我們知道 MongoDB 是一個文檔數據庫,其每一條記錄都是一個 JSON 格式的文檔。比如像下面的例子,每一天會生成一條這樣的統計數據:
{metric: content_count, client: 5, value: 51, date: ISODate(2012-04-01 13:00) }
{metric: content_count, client: 5, value: 49, date: ISODate(2012-04-02 13:00) }
而如果采用組合式大文檔的話,就可以這樣將一個月的數據全部存到一條記錄里:
{metric: content_count, client: 5, month: 2012-04, 1: 51, 2: 49, …}
通過上面兩種方式存儲,預先一共存儲大約 7GB 的數據 (機器只有 1.7GB 的內存),測試讀取一年信息,這二者的讀性能差別很明顯:
第一種: 1.6 秒
第二種: 0.3 秒
那么問題在哪里呢?
實際上原因是組合式的存儲在讀取數據的時候,可以讀取更少的文檔數量。而讀取文檔如果不能完全在內存中的話,其代價主要是被花在磁盤 seek 上,第一種存儲方式在獲取一年數據時,需要讀取的文檔數更多,所以磁盤 seek 的數量也越多。所以更慢。
實際上 MongoDB 的知名使用者 foursquare 就大量采用這種方式來提升讀性能。
2. 采用特殊的索引結構
我們知道,MongoDB 和傳統數據庫一樣,都是采用 B 樹作為索引的數據結構。對于樹形的索引來說,保存熱數據使用到的索引在存儲上越集中,索引浪費掉的內存也越小。所以我們對比下面兩種索引結構:
db.metrics.ensureIndex({ metric: 1, client: 1, date: 1}) 與 db.metrics.ensureIndex({ date: 1, metric: 1, client: 1 })
采用這兩種不同的結構,在插入性能上的差別也很明顯。
當采用第一種結構時,數據量在 2 千萬以下時,能夠基本保持 10k/s 的插入速度,而當數據量再增大,其插入速度就會慢慢降低到 2.5k/s,當數據量再增大時,其性能可能會更低。
而采用第二種結構時,插入速度能夠基本穩定在 10k/s。
其原因是第二種結構將 date 字段放在了索引的第一位,這樣在構建索引時,新數據更新索引時,不是在中間去更新的,只是在索引的尾巴處進行修改。那些插入時間過早的索引在后續的插入操作中幾乎不需要進行修改。而第一種情況下,由于 date 字段不在最前面,所以其索引更新經常是發生在樹結構的中間,導致索引結構會經常進行大規模的變化。
3. 預留空間
與第 1 點相同,這一點同樣是考慮到傳統機械硬盤的主要操作時間是花在磁盤 seek 操作上。
比如還是拿第 1 點中的例子來說,我們在插入數據的時候,預先將這一年的數據需要的空間都一次性插入。這能保證我們這一年 12 個月的數據是在一條記錄中,是順序存儲在磁盤上的,那么在讀取的時候,我們可能只需要一次對磁盤的順序讀操作就能夠讀到一年的數據,相比前面的 12 次讀取來說,磁盤 seek 也只有一次。
db.metrics.insert([{ metric: content_count, client: 3, date: 2012-01, 0: 0, 1: 0, 2: 0, ... }
{ .................................., date:
{ .................................., date:
{ .................................., date:
{ .................................., date:
{ .................................., date:
{ .................................., date:
{ .................................., date:
{ .................................., date:
{ .................................., date:
{ .................................., date:
{ .................................., date:])
結果:
如果不采用預留空間的方式,讀取一年的記錄需要 62ms
如果采用預留空間的方式,讀取一年的記錄只需要 6.6ms
以上是“如何解決 MongoDB 磁盤 IO 問題”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注丸趣 TV 行業資訊頻道!