閱讀545 返回首頁    go 阿裏雲 go 技術社區[雲棲]


利用MongoDB的SplitVector命令實現並發數據遷移

背景

數據遷移是數據庫運維中一個很常見的場景。數據遷移分為全量和增量。為了追求速度,通常我們會采用並發的方式對數據進行全量遷移。在全量導出數據時,通常都會選擇做到記錄級的並發,因此通常會涉及到對需要導出的某個表(集合)按照並發度進行切分(分區)的過程。現有常用做法是通過若幹個skip加limit來找到一些分區點,然後就可以並發同時導出多個分區。事實上MongoDB還有一個SplitVector命令特別適合用來做集合的分區。本文將介紹一下如何利用這個命令來對集合做分區,實現並發數據遷移。

命令簡介

SplitVector命令原是在sharding中chunk分裂時需要用的一個內部命令,是mongos在準備分裂某個chunk前發給這個chunk所在shard以計算分裂點(SplitPoint)時使用的。但是這個命令也可以用於普通的副本集,我們可以把副本集中的集合看作一個唯一的chunk,利用這個命令來為這個chunk計算分裂點,從而達到為某個集合進行分區的目的。

SplitVector命令的使用在官方文檔中沒有介紹,隻說明了其實一個內部命令,但是使用命令的Help卻可以看到:

db.runCommand({splitVector:"test.test", help:1})
{
 "help" : "help for: splitVector Internal command.\nexamples:\n  { splitVector : \"blog.post\" , keyPattern:{x:1} , min:{x:10} , max:{x:20}, maxChunkSize:200 }\n  maxChunkSize unit in MBs\n  May optionally specify 'maxSplitPoints' and 'maxChunkObjects' to avoid traversing the whole chunk\n  \n  { splitVector : \"blog.post\" , keyPattern:{x:1} , min:{x:10} , max:{x:20}, force: true }\n  'force' will produce one split point even if data is small; defaults to false\nNOTE: This command may take a while to run",
 "lockType" : 0,
 "ok" : 1
}

從幫助文檔中可以大致看到,這個命令大致是這麼使用的:

db.runCommand({splitVector:"blog.post", keyPattern:{x:1}, min{x:10}, max:{x:20}, maxChunkSize:200})

接下來介紹一下各個參數及其含義。

字段 類型 描述
splitVector string splitVector的操作對象集合名
keyPattern document chunk分裂使用的分區鍵,必須擁有索引,在sharding中就是shard key,在副本集中通常就指定成主鍵_id索引
min document 可選參數,分區數據集的最小值,如果沒有指定,那麼使用MinKey
max document 可選參數,分區數據集的最大值,如果沒有指定,那麼使用MaxKey
maxChunkSize integer 可選參數,和『force』參數二者必須指定一個。分區後每個chunk的最大大小
maxSplitPoints integer 可選參數,分裂點個數上限
maxChunkObjects integer 可選參數,分區後每個chunk最大包含的記錄數,默認為250000
force boolean 可選參數,和『maxChunkSize』參數二者必須指定一個。默認情況下如果當前chunk的數據大小小於maxChunkSize則不會進行分裂。如果指定了『force』為true,那麼會強製在當前chunk的中位點進行分裂,返回一個分裂點。默認為false。

這麼多參數到底怎麼用呢?我怎麼知道出來的結果是怎樣的?沒有更詳細的文檔,隻有啃源碼了。​

原理

SplitVector的原理是遍曆指定的『keyPattern』索引,根據指定的『maxChunkSize』找到滿足以下條件的n個分裂點:分裂後的每個新的chunk的大小約為『maxChunkSize』的一半。如果集合當前大小比『maxChunkSize』小或者集合記錄數為空,那麼返回一個空的分裂點集合。如果指定了『force: true』,那麼會忽略傳入的『maxChunkSize』參數,並強製在集合的中位點進行分片,這時候隻會產生一個分裂點。
在尋找分裂點時首先會根據集合的平均文檔大小計算一個分裂後每個chunk所包含的文檔數:

​​keyCount = maxChunkSize / (2 * avgObjSize)

​​如果指定了『maxChunkObjects』參數,並且『maxChunkObjects』比keyCount小,會使用『maxChunkObjects』作為keyCount。接下來就是遍曆索引,每遍曆keyCount個key,就得到一個分裂點(第keyCount+1個key),直到達到『maxSplitPoints』(若有指定)或遍曆結束。因此最終得到的分裂點個數:

​​splitPointCount = keyTotalCount / (keyCount + 1)

​​其中keyTotalCount為索引的key總數。

使用案例

知道了原理後,就知道如何去傳參數了,如果要精確控製得到的分裂點個數(以便控製並發數),這裏可以給出一個公式及推導過程。現在我們有以下公式:

​1. splitPointCount = partitionCount - 1
2. splitPointCount = keyTotalCount / (keyCount + 1)
3. keyCount = maxChunkSize / (2 * avgObjSize)

由上述公式可以推導出

maxChunkSize = (keyTotalCount / (partionCount - 1) - 1) * 2 * avgObjSize

由於所有集合都有_id字段上的唯一索引,並且每個文檔都有_id字段,因此我們可以直接利用集合文檔的個數docCount作為索引key的個數。文檔個數和avgObjSize都可以通過collStats命令得到。注意參數中的『maxChunkSize』是以MB為單位的,最終傳到命令的時候需要轉換一下,並且在服務端中事實上會將『maxChunkSize』做個向下取整,因此最終計算出來的keyCount可能比我們設想的要小,這樣就會導致最終得到的分裂點個數比我們想要的多。為了達到我們的需求,最好加上『maxSplitPoints』這個可選參數對分裂點進行限製,這樣我們允許最後一個分區比其他分區包含更多的文檔數。

接下來舉個具體的例子,假設現在需要將某個集合分成10個分區以支持10個並發同時對外導出數據,這個集合共有10240條文檔,avgObjSize是1024,那麼根據上述公式可以計算得到:

​maxChunkSize = (10240 / (10 - 1) - 1) * 2 * 1024 = 2MB

這樣我們執行如下命令:

db.runCommand({splitVector:"test.test", keyPattern:{_id:1}, maxChunkSize:2, maxSplitPoints:9})

​這樣就會隻得到9個分裂點。

最後更新:2017-07-07 17:32:24

  上一篇:go  遇到問題如何解決
  下一篇:go  阿裏巴巴-商家事業部-數據技術團隊招聘火熱進行中,綠色通道直達offer!