1. 程式人生 > >MongoDB全量遷移斷點續傳功能學習與實現

MongoDB全量遷移斷點續傳功能學習與實現

1.    背景

 MongoDB是一個基於分散式檔案儲存的開源資料庫系統,使用者儲存文字資料。MongoDB提供了一個面向文件的儲存,操作起來比較簡單容易,查詢速度快等特點。資料遷移是資料庫工作中經常見到的一個場景,比如擴容、備份、上雲等需求,資料遷移包括全量遷移和增量遷移。在全量遷移的過程中,為了追求速度,提高遷移效率,通常使用高併發的方式,這種併發通常是行級別,而在遷移的過程中,由於經常因為某種原因導致遷移失敗,比如網路斷了,資料庫連線滿了,db宕機等等,當再次啟動遷移任務的時候,通常希望從上一次失敗的地方進行斷點續傳,這就對記錄斷點位置和處理斷點的方式提出了一定的要求。本文中主要介紹如何支援mongodb的斷點續傳。由於本人對mongo也是小白,初次接觸mongo資料庫,如果文中有布正確的地方,歡迎大家指正。

2. 基礎知識點

    Mongo中表稱之為集合(collection),集中的一條記錄稱之為文件(document),一個文件中可以有多種資料型別(BOSN),其中文件是以key-value的形式存儲存。故mongo的儲存形式非常的靈活,在mongo也提供非常豐富的查詢方式和表示式。在實際場景中,mongodb通常非常大量的資料和資訊,因此對於mongo的擴容和遷移造成了一定的困難。尤其是在進行全量遷移的過程中,通常遇到一些非常棘手的問題,比如說如何對mongo的分片,併發遷移等等。本文主要針對mongo的分片和斷點續傳做一個詳細的研究和提出一個可行的方案。

   在mongo遷移的過程,最原始的做法是利用一個查詢db.collections.find(),把一個collection放在一個查詢裡面,然後通過流式的方式逐漸的查出資料,然後寫入到目標庫中。由於mongo本身就具備高效的查詢效率,因此在使用過程中,對於幾千萬、幾億條資料的單個集合,如果不存在網路的問題的,速度會非常的快,通常能達到3wTPS/s的速度。但是如果遇到幾十億、上百億、甚至更大的集合就會遇到一個很嚴重的問題,遷移到一半的時候,網路中斷了,查詢超時等種種原因,導致這個查詢返回失敗。那這個時候,我們就得重新開始遷移,沒法斷點續傳,因為我們查詢的順序是mongo最原始的寫入順序,如果中間發生delete、update、insert等操作,那麼就會丟失一部分資料。同時,這麼大體量的資料放在一個分片裡面,遷移的時間也會非常的漫長,那麼對於後面的增量資料來說,可能就是個災難。在這裡我們經過研究,提出一個可行性的方案,支援mongodb的斷點續傳功能

2.1   mongo的主鍵欄位_id特點

  mongodb資料庫不像mysql、oracle等資料庫,擁有表結構,欄位、主鍵等約束資訊。Mongodb的文件是靈活的,你可以無限制的新增key-value鍵值對,每個value的型別你也可以隨意定義。因此在同一個key-value中,可以會存在多種多型別,這就導致了分片出現的困難。Mongo雖然沒有主鍵的概念,但是擁有天然的主鍵欄位_id,每插入一條document的時候,mongodb會自動新增一個欄位_id, 這個欄位全域性是唯一的,因此可以當做主鍵來使用。該欄位預設的型別的是ObjectId型別。但是在寫入資料的時候,如果不指明_id欄位,會預設是objectId,如果自己指定了這個值,則可以是任意型別,可以是int,long,double,string等各種型別。因此如果我們希望通過這個欄位來進行分片,那麼該欄位的各種型別就是一個極大的麻煩,因為我們不知道一個collection中_id欄位有多少中型別,如下根據mongo官方文件給出的型別。

型別

編號

別名

備註

double

1

double

string

2

string

object

3

object

array

4

array

binary data

5

bindata

undefined

6

undefined

過期

objected

7

objectId

Boolean

8

bool

date

9

date

Null

10

null

regular expression

11

regex

DBpointer

12

dbpointer

過期

javascrip

13

javascript

Symbol

14

symbol

過期

javaScript(with scope)

15

javascriptWithScope

32-bit integer

16

int

timestamp

17

timestamp

64-bit integer

18

long

decimal 128

19

decimal

3.4以上

min key

-1

minkey

max key

127

maxkey


這裡我們最常用的型別是ObjectId(預設),integer和string型別,通過如下語句,可以查詢出對應型別的值。



對collection中插入幾條資料,_id包含各種型別,integer,objectId,string。


通過語句db.user.find({"_id":{"$type":2}});可以指定具體的型別,查出相應的資料。

2.2  分片的基本原理和方法

根據上面mongo的_id的特點,該欄位可以是21種類型中的任何型別,也可以在一個collection中,同時存在多種型別,因此我們要先把每個型別單獨拆分出來,作為多個分片,並且取得每個分片的最小值,通過如下語句。


對於每個型別都可以作為一個單獨的分片,然後獲取的每個分片的最小值,後面會用到這個最小值。

2.3 mongoDB的基本分片邏輯

       根據在mysql,oracle、sqlserver等資料庫中得知,每個資料庫都支援分頁查詢,比如mysql 中的limit函式、oracle中的rownum關鍵字,還有sqlserver中的top關鍵字等。當然mongo中也提供了相應的分頁查詢的函式,skip和limit的函式。利用這兩個函式的結合可以得到你想要的一部分資料。比如,要得到第10到100條資料,那麼可以:db.collection.find().skip(10).limit(100); 通過這種方式得到每個分片的資料。在研究的過程中發現,當資料量很大的時候,利用skip()和limit函式進行分頁查詢的時候發現速度非常的慢,sql執行時間越來越長,曾經做過統計統計一張80億條資料的集合,利用find(_id>xxx).skip(200w).limit(1)返回的時候需要60s,而find(_id>xxx).skip(50w).limit(1)返回的時候則需要20s。如果把id>xxx的條件去掉,直接使用skip(m*n).limit(1)的話,則查詢基本時候卡死。通過研究mongo的執行計劃和查閱mongodb的官方文件發現,使用skip()函式,會進行全集合掃描,約到後面效能越來越差,隨著資料量的增加也會越來越差,這就是一開始我們使用這個函式進行分片,在一些小的表上發現遷移效能越來越慢,而大表基本就卡死的原因。



針對上面這個問題,我們去研究了mongo的欄位_id,發現該欄位擁有天然的索引結構,並且如果我們只是使用find(_id>xxx)的話,速度非常的快速,因此,我們採用了一種多執行緒的模式,不適用skip函式,直接使用_id預設的索引順序。在測試的過程中發現,利用find(_id>xxx)的返回速度非常的快。而我們唯一要做的就是,一開始得到整個表的最小的_id,就是通過上面的語句db.user.find({"_id":{"$type":2}})拿到最小值,通常通過排序的方式find().sort(_id).limit(1)的方式得到最小值。通過執行計劃發現,db.user.find({"_id":{"$type":2}}).limit(1)走的是索引結構,因此這個速度也是非常的快。

我們通過執行計劃對比了skip函式的效能,skip()函式會跳過相應的條數,執行一下兩條sql得到的結果是一樣的


接下來,我們看下一下兩條sql的執行計劃


從上圖中可以看到,sql跳過了1010條語句才能找到對應的值,接下來我們執行另外一條sql,看下起查詢計劃,可以看出第二條語句走了_id的索引。



       從這個查詢計劃中可以看出,該sql直接定位到對應的ObjectId=595b5b122a95218172eb57df 然後從該objectId的值開始跳過對應的10條記錄,找到對應的值。由此可見,skip()函式是從當前的位置的起,開始跳過一定數量的資料,因此skip的方式不可取。


3. 實現原理

  通過對比上面的分析發現,如果使用skip和limit的函式,先對一張表進行分片,得到每個分片的最小值_id,然後多執行緒併發的去執行,這樣的好處是,得到分片之後遷移速度會非常快,缺點是需要得到所有分片之後才能執行,但是獲取分片的最小值是一個非常耗時的過程,通過對比發現,一張80億條是資料表,每個分片200w和50w,每次執行返回的時間是60s和20s,這樣的得到分片就是67個小時和88個小時,這是使用者不能接受的。

針對上面的情況我們採用另外一種方式,單個執行緒的讀+多個執行緒寫的方式,那麼得到整個collection的最小值的時間是1s左右,就可以開始遷移。這樣雖然單個分片,但是整體的效果要好於先得到所有的分片在進行遷移。



      針對以上兩種情況,本文做了一個整合,兩種方式併發同時執行,一個執行緒不停的去切分,得到分片,然後將得到分片丟到queue裡面,然後多個執行緒從queue裡面取得到的相應的分片,這樣的方式是極大的提升了查詢效率。



4.總結

   通過這次對mongo的分片和斷點續傳的瞭解,也學習到了mongo的非常的多知識,當然這並不是最好的方式,但是目前可以解決大部分mongo遷移和支援斷點續傳的功能,今後將繼續深入學習mongodb,進步的對分片和斷點續傳的方式進行優化。以上是這次學習對mongo的一點總結和經驗,如果文中有不對的地方歡迎廣大網友指正。