分片
1. 分片(sharding)是指將數據拆分,將其分散存放在不同的機器上的過程。有時也用分區(partitioning)來表示這個概念。將數據分散到不同的機器上,不需要功能強大的大型計算機就可以
存儲更多的數據,處理更大的負載。
2. MongoDB支持自動分片(autosharding),可以使數據庫架構對應用程序不可見,也可以簡化系統管理。對應用程序而言,好像始終在使用一個單機的MongoDB服務器一樣。另一方面,
mongoDB自動處理數據在分片上的分布,也更容易添加和刪除分片技術。
3. 復制與分片的區別:復制時讓多台服務器都擁有同樣的數據副本,每一台服務器都是其他服務器的鏡像,而每一個分片都和其他分片擁有不同的數據子集。
4. 路由服務器:為了對應用程序隱藏數據庫架構的細節,在分片之前要先執行mongos進行一次路由過程。這個路由服務器維護這一個"內容列表",指明了每個分片包含什么數據內容。應用
程序只需要連接路由服務器,就可以像使用單機一樣進行正常的請求了。
5. 運行sh.status()可以看到集群的狀態:分片摘要信心、數據庫摘要信息、集合摘要信息。
6. 要對一個集合分片,首先你要對這個集合的數據庫啟用分片,執行如下命令:sh.enableSharding("test")
7. 片鍵:片鍵是集合的一個鍵,MongoDB根據這個鍵拆分數據。例如:username 。在啟用分片之前,先在希望作為片鍵的鍵上創建索引:db.users.ensureIndex({"username":1})
8. 對集合分片:sh.shardCollection("test.users",{"username":1})
9. 集合被拆分為多個數據塊,每個數據塊都是集合的一個數據子集。這是按照片鍵的范圍排列的({"username":minValue}-->>{"username":maxValue}指出了每個數據塊的范圍)。
10. 包含片鍵的查詢能夠直接被發送到目標分片或者是集群分片的一個子集。這樣的查詢叫做定向查詢(targetd query)。有些查詢必須被發送到所有分片,這樣的查詢叫做分散-聚合查詢(
scatter-gather query);mongos將查詢分散到所有的分片上,然后經各個分片的查詢結果聚集起來。
11. cluster.stop() 關閉整個集群。
BSON類型
配置分片:
1. 何時進行分片:決定何時分片是一個值得權衡的問題。通常不必太早分片,因為分片不僅會增加部署的操作復雜度,還要求做出設計決策,而改決策以后很難再改。另外最好也不要在系統
運行太久之后再分片,因為在一個過載的系統上不停機進行分配是很困難的。
2. 分片的目的:增加可用的RAM,增加可用磁盤空間,減輕單台服務器的負載,處理單個mongod無法承受的吞吐量。
3. 一般情況下至少應該創建3個或者以上的分片。
4. 啟動服務器:
1). 配置服務器:配置服務器相當於集群的大腦,保存着集群和分片的元數據,即各分片包含哪些數據的信息。因此,應該首先建立配置服務器,鑒於它所包含的的數據極端重要性,必須啟用
其日志功能,並確保其數據保存在非易失性驅動器上。每個配置服務器都應該位於單獨的物理機上,最好是分布在不同地址位置的機器上。
a. 啟動配置服務器:mongod --configsvr --dbpath /var/lib/mongodb -f /var/lib/config/mognd.conf 。需要啟動三台配置服務器,且都是可寫的。
為什么是3台配置服務器?因為我們需要考慮不時之需。但是,也不需要過多的配置服務器,因為配置服務器上的確認操作比較耗時。另外,如果有服務器宕機了,集群源數據就會變成只讀的。
--configsvr 選項指定mongod為新配置服務器。該選項並非必選項,因為它所做的不過是將mongod的默認監聽端口改為27019,並大默認的數據目錄改為/data/configdb而已(可以使用
--port 和 --dbpath 選項修改這兩項配置)。但建議使用--configsvr選項,因為它比價直白地說明了這些配置服務器的用途。
配置服務器的1KB相當於200MB知識數據,它保存的真實數據的分布表。由於配置服務器並不需要太多的資源,因此可以將其部署在運行着其他程序的服務器上。
2). mongos進程:三個配置服務器均處於運行狀態后,啟動一個mongos進程供應用程序連接。mongos進程需要配置服務器的地址,所以必須使用--configdb選項啟動mongos:
mongos --configdb config-1:27019,config-2:27019,config-3:27019 -f /var/lib/mongos.conf
默認情況下,mongos運行在27017端口。mongos本身不保存數據,它會在啟動時從配置服務器加載集群數據。
可以啟動任意數量的mongos進程。通常的設置時每個應用程序服務器使用一個mongos進程(與應用服務器運行在同一台機器上)
每個mongos進程必須按照列表排序,使用相同的配置服務器列表。
3). 將副本集轉換為分片:有兩種可能性:已經有一個副本集,或者從零開始建立集群。下例假設我們已經擁有了一個副本集。如果是從零開始的話,可先初始化一個空的副本集,然后按照本例操作。
a. 告知mongos副本集名稱和副本集成員列表:sh.addShard("spock/server-1:27017,server-2:27017,server-4:27017") mongos能夠自動檢測到沒有包含在副本集成員表中的成員。
b. 副本集作為分片添加到集群后,就可以將應用程序設置從連接到副本集改為連接到mongos。
c. 副本集名稱spokc被作為分片名稱。如果之后希望移除這個分片或者是向這個分片遷移數據,可以使用spock來標志這個分片。
d. 配置完分片后,必須將客戶端設置為將所有請求發送到mongos,而不是副本集。同時配置防火牆規則,以確保客戶端不能直接將請求發送到分片。
e. 有一個--shardsvr選項,與前面介紹的--configsvr選項類似,它也沒什么實用性(只是將默認端口改為27018),但在操作中建議使用該選項。
f. 不建議創建單mongod服務器分片(而不是副本集分片),將單一服務器分片轉換為副本集需要停機操作。
4). 增加集群容量:通過增加分片來增加集群容量。
5). 數據分片:除非明確指定規則,否則MongoDB不會自動對數據進行拆分。如果有必要,必須明確告知數據庫和集合。加入對music數據庫中的artists集合按照name進行分片,
db.enableSharding("music") 對數據庫分片是對集合分片的先決條件
sh.shardCollection("music.artists",{"name":1}) 對集合分片,集合會按照name鍵進行分片。如果是對已存在的集合分片,那么name鍵上必須有索引,否則會返回錯誤。
shardCollection()命令會經集合拆分為多個數據塊,這是MongoDB遷移數據的基本單元。命令執行后,MongoDB會均衡的將數據分散到集群的分片上。
5. MongoDB如何追蹤集群數據
1). MongoDB將文檔分組為塊(chunk),每個塊由給定片鍵特定范圍內的文檔組成。一個塊只存在於一個分片上,所以MongoDB用一個比較小的表就能夠維護跟分片的映射。
2). 當一個塊增長到特定大小時,MongoDB會自動將其拆分為兩個較小的塊。
3). 一個常見的誤解釋同一個塊內的數據保存在磁盤的同一片區域。這是不正確的,塊並不影響mongod保存集合數據的方式。
4). 塊信息保存在config.chunks集合中。左閉右開。
5). 可以使用復合片鍵,工作方式與使用復合索引進行排序一樣。
6). 拆分塊:mongos會記錄在每個塊中插入了多少數據,一旦達到某個閾值,就會檢查是否需要對塊進行拆分。mongos就會在配置服務器更新這個塊的源信息。塊拆分中只需要改變塊源數據即可,
而無需進行數據移動。進行拆分時,配置服務器會創建新的塊文檔,同時修改舊的塊范圍,拆分完成以后,mongos會重置對原始塊的追蹤器,同時為新的塊創建新的追蹤器。
7). 分片有時可能會找不到任何可用的拆分點,因為合法拆分塊方法有限。具有相同片鍵的文檔必須保存在相同的塊中。
8). 如果mongos試圖進行拆分時有一個服務器掛了,那么mongos就無法更新源數據。mongos不斷重復發起拆分請求卻無法進行拆分的過程,叫做拆分風暴。防止拆分風暴的唯一方法是盡可能保證
配置服務器的可用和健康。也可以重啟mongos,重置引入計數器,這樣他就不會再處於拆分閾值點了。
9). 如果mongos進程不斷重啟,它們的計數器可能永遠也不會到達閾值點,因此塊的增加不存在最大值,也就無法到達閾值點。
10). 防止無法拆分的兩種方法:一是減少mongos進程的波動,二是使塊的大小比實際預期小一些,這樣就更容易達到拆分閾值點。
11). 可以在mongos啟動時指定--nosplit選項,從而關閉塊的拆分。
6. 均衡器:均衡器負責數據的遷移。它會周期性地檢查分片間是否存在不均衡,如果存在,則會開始塊的遷移。雖然均衡器通常被看成單一的實體,但每個mongos有時也會扮演均衡器的角色。
每隔幾秒,mongos就會嘗試變身均衡器。如果沒有其他可用的均衡器,mongos就會對整個集群加鎖,以防止配置服務器對整個集群進行修改,然后做一次均衡。
mongos成為均衡器后,就會檢查每個集合的分塊表,從而查看是否有分片達到了均衡閾值。
選擇片鍵
1. 對集合進行分片時,要選擇一或兩個字段用於拆分數據,這個鍵就叫做片鍵。
2. 拆分數據最常用的數據分發方式有三種:升序片鍵、隨機分發的片鍵和基於位置的片鍵。
1). 升序片鍵:升序片鍵通常有點類似於"date"字段或者是ObjectId,是一種隨着時間穩定增長的字段。缺點:例如ObjectId可能會導致接下來的所有的寫入操作都在同一塊分片上。
2). 隨機分發的片鍵:隨機分發的片鍵可以是用戶名,郵件地址,UDID,MD5散列值或者數據集中其他一些沒有規律的鍵。缺點:MongoDB在隨機訪問超出RAM大小的數據時效率不高。
3). 基於位置的片鍵:基於位置的片鍵可以是用戶的IP、經緯度、或者地址。這里的"位置"比較抽象,不必與實際的物理位置字段相關。
如果希望特定范圍內的塊出現在特定的分片中,可以為分片添加tag,然后為塊指定相應的tag
3. 片鍵策略:
1). 散列片鍵:如果追求的是數據加載速度的極致,那么散列片鍵是最佳選擇。散列片鍵可使其他任何鍵隨機分發,因此,如果打算在大量查詢中使用使用升序鍵,但同時又希望寫入數據隨機分發的話,
散列片鍵會是一個非常好的選擇。缺點:無法使用散列片鍵做指定目標的范圍查找。
創建步驟: db.users.ensureIndex({"username":"hashed"}) , sh.shardCollection("app.users",{"username":"hashed"})
2). GridFS的散列片鍵
3). 流水策略:如果有一些服務器比其他服務器更強大,我們可能希望讓這些強大的服務器處理更多的負載。比如說:加入有一個使用SSD的分片能夠處理10倍於其他機器的負載。我們可以強制將所有新數據
插入到SSD,然后讓均衡器將舊的塊移動到其他分片上。
a. 為SSD指定一個標簽:sh.addShardTag("shard-name","ssd")
b. 將升序鍵的當前值一直到正無窮范圍的塊都指定分布在SSD分片上:sh.addTagRange("dbName.collName",{"_id":ObjectId()},...{"_id":MaxKey},"ssd")
所有插入請求均會路由到這個塊上,這個塊始終位於標簽的ssd的分片上。
c. 除非修改標簽范圍,否則從升序鍵的當前值一直到正無窮都被固定在這個分片上。可以創建一個定時任務每天更新一次標簽范圍:
use config
var tag =db.tags.findOne({"ns":"dbName.collName",..."max":{"shardKey":MaxKey}})
tag.min.shardKey = ObjectId()
db.tags.save(tag)
這樣前一天的數據就會被移動到其他分片上了。
此策略的另一個缺點:需要修改才能進行擴展。如果寫請求超出了SSD的處理能力,無法進行負載均衡。
4). 多熱點:寫請求分布在集群中時,分片是最高效的。這種技術會創建多個熱點(最好在每個分片上都創建幾個熱點),寫請求於是會均衡地分布在集群內,而在單個分片上則是以升序分布的。
為了實現這種方式,需使用復合片鍵。復合片鍵中的第一個值只是比較粗略的隨機值,勢也比較低。
4. 片鍵規則和指導方針:
1). 片鍵限制:片鍵不可以是數組。文檔一旦插入,其片鍵就無法修改了。要修改文檔的片鍵值,就必須先刪除文檔。
2). 片鍵的勢:選擇一個值會變化的的鍵非常重要,即值很多,隨着數據量的增大可以分出更多的片鍵。分片在勢比較高的字段上性能更佳。
5. 控制數據分發
1). 對多個數據庫和集合使用一個集群:通過tag標記,將重要的數據放到性能更好的服務器上,將不重要的數據放在性能一般的服務器上。
2). 手動分片:如果不希望數據被自動分發,可以關閉均衡器,使用moveChunk命令手動對數據進行遷移。
分片管理
1. 檢查集群狀態:
1). 使用sh.status查看集群摘要信息: 塊的數量比較多時,sh.status()命令會概述塊的狀態,而非打印出每個塊的相關信息。如需查看所有的塊,可使用sh.status(true)命令。
sh.status()顯示的所有信息都來自config數據庫。運行sh.status()命令,使用MapReduce獲取這一數據。因此,如果啟動數據庫時指定--noscripting選項,則無法運行sh.status()命令。
2). 檢查配置信息:
a. 集群相關的所有配置信息都保存在配置服務器上config數據庫的集合中。可以直接訪問該數據庫,不過shell提供了一些輔助函數。
b. 永遠不要直接連接到配置服務器,以防止配置服務器數據被不小心修改或刪除。應該先連接到mongos,然后通過config數據庫來查詢相關信息:use config
如果通過mongos操作配置數據,mongos會保證將修改同步到所有配置服務器,也會防止危險的操作發生,如意外刪除config數據庫等。
c. 總的來說,不應直接修改config數據庫中的任何數據。如果確實修改了某些數據,通常需要重啟所有的mongos服務器,才能看到效果。
d. config中幾個關鍵集合:
shards : 跟蹤記錄集群中所有分片的信息。
databases: 跟蹤記錄集群中所有數據庫的信息,不管數據庫有沒有分片。
collections: 跟蹤記錄所有分片集合的信息(非分片集合信息除外)
chunks: 記錄集合中所有塊的信息。
changelog: 跟蹤記錄集群的操作,因為該集合會記錄所有拆分和遷移的操作。
tags: 該集合的創建是在為系統配置分片標簽時發生的。每個標簽都與一個塊范圍相關聯。
settings: 該集合含有當前的均衡器設置和塊大小的文檔信息。通過修改該集合的文檔,可開啟和關閉均衡器,也可以修改塊的大小。注意,應總是連接到mongos修改該集合的值。
2. 查看網絡連接:
1). 查看連接統計:可以使用connPoolStats命令,查看mongos和mongod之間的連接信息:db.adminCommand({"connPoolStats":1})
在一個分片上執行connPoolStats,輸出信息中可以看到該分片與其他分片間的連接,包括連接到其他分片做數據遷移的連接。
2). 限制連接數量: 可在mongos的命令行配置中使用maxConns選項,這樣可以限制mongos能夠創建的連接數量。可以使用下面公式計算分片能夠處理的來自單一mongos連接數量:
maxConns = 20000 - (mongos進程的數量 * 3 ) - (每個副本集的成員數量 * 3 ) - (其他/mongos進程的數量)
MongoDB如果沒有安全退出,那些已經打開的套接字很可能沒有被關閉。
在出現大量重新連接時,除了重啟進程,沒有其他特殊有效的方法。
3. 服務器管理
1). 添加服務器:使用addShard命令,向集群中添加新的分片
2). 修改分片的服務器:要修改分片的成員,需直接連接到分片的主服務器上,然后對副本集進行重新配置。集群配置會自動檢測更改,並將其更新到config.shards上。
3). 通常來說,不應從集群中刪除分片。執行removeShard命令排除數據和查看排出進度。
4). 修改配置服務器:修改配置服務器非常困難,而且有風險,通常還需要停機。注意,修改配置服務器前,應做好備份。
首先必須關閉所有mongos進程,然后使用新的--configdb參數重啟所有mongos進程。
4. 數據均衡:
1). 均衡器:均衡器只使用塊的數量,而非數據大小,作為衡量分片間是否均衡的指標。自動均衡總是根據數據集的當前狀態來決定數據遷移,而不考慮數據集歷史狀態。我們可以手動均衡數據集塊的數量。
2). 修改塊的大小:塊的大小默認為64M,這個大小的塊既易於遷移,又不至於導致過多的流失。使用shell連接到mongos,修改config.setting集合,從而完成塊大小的修改。
該設置的有效范圍是整個集群:它會影響所有集合的數據庫。因此,如需對一個集合使用較小的塊,而對另一個集合使用較大的塊,比較好的解決方式是取一個折中值(或者將這兩個值放到不同的集合中)。
如果MongoDB頻繁進行數據遷移或文檔增大,則可能需要增加塊的大小。
3). 遷移塊:同一塊內的所有數據都位於同一分片上。如該分片的塊數量比其他分片多,則MongoDB會將其中的一部分塊遷移到其他塊數量較少的分片上。移動快的過程叫遷移,MongoDB就是這樣在集群中
實現數據均衡的。可在shell中使用moveChunk輔助函數,手動移動塊。
如果某個塊的大小超出了系統指定的最大值,mongos則會拒絕移動這個塊。移動之前必須先手動拆分這個塊,可以使用splitAt命令對塊進行拆分。特大塊,無法被拆分。
4). 特大塊:某些片鍵,值比較少,例如:日期等。可能會形成超出設置的最大塊大小的塊,這種塊成為特大塊.
出現特大塊的表現之一是,某個分片的大小增長速度要比其他分片塊的多。也可使用sh.status()來檢查是否出現了特大塊;特大塊會存在一個jumbo屬性。
a. 分發特大塊,一個復雜的過程
b. 防止特大塊的出現:修改片鍵,細化片鍵的粒度
5). mongos有時無法從配置服務器正確更新配置。如果發現配置有誤,mongos的配置過舊或無法找到應有的數據,可以使用flushRouterConfig命令手動刷新所有緩存:db.adminCommand({"flushRouterConfig":1})
如flushRouterConfig命令沒能解決問題,則應重啟所有的mongos或者mongod進程,以便清除所有可能的緩存。