本文更新于2022-01-23,使用MongoDB 4.4.5,
目錄- 與關系資料庫比較
- 組成
- 資料庫
- 集合
- 檔案
- 資料型別
- 填充因子
- 寫入安全
- 自然排序
- 固定集合
- 回圈游標
- 索引
- 典型場景
- GridFS
- 聚合
- MapReduce
- 副本集
- 同步
- 心跳
- 驅動程式
- 分片
- 連接
- 身份驗證
- 系統分析器
- 診斷日志
- 日記系統
- BSON
- 線路協議
- 資料檔案
- 記憶體映射存盤引擎
- MMS
- 作業系統
與關系資料庫比較
- 檔案資料庫,而不是關系資料庫,不采用關系模型主要是為了獲得更好的擴展性,以及還有其他的好處,
- 使用更靈活的檔案,而不是關系資料庫的行,檔案中可再嵌入檔案,用一條資料來表現復雜的層次關系,
- 沒有預定義的模式,鍵值不再是固定的型別和大小,添加、洗掉、修改欄位更容易,
- 采用橫向擴展,面向檔案的資料模型更容易在多臺服務器之間進行資料分割,
- 不具備一些關系資料庫中普遍的功能,如連接和復雜的多行事務,因為在分布式系統中這兩個功能難以高效地實作,
組成
資料庫
- 由多個集合組成,
- 每個資料庫都有獨立的權限,保存在不同的檔案中(通常也在單獨的檔案夾中),資料庫名就是檔案名,
- 資料庫名只能使用ASCII字母和數字,
- 資料庫名區分大小寫,即使在不區分大小寫的檔案系統中,為簡單起見,資料庫名應全部小寫,
- 有一些保留的資料庫名,如:admin、local、config,
- 將資料庫名使用“.”連接集合名,得到集合的完全限定名,即命名空間,如z資料庫中的a.b集合命名空間為z.a.b,
集合
- 類似擁有動態模式的關系資料庫的表,由多個檔案組成,
- 集合名不能含有“\0”,其表示集合名的結尾;不能含有“$”,因某些系統生成的集合包含此字符,
- 不能以“system.”開頭,其為保留的系統集合前綴,
- 慣例使用“.”分隔不同的子集合,集合可跟其子集合無任何關系,甚至其不需存在,如a集合與a.b集合,
檔案
- 類似但不同于關系資料庫的行,資料的基本單元,
- 鍵值對是一個有序集,雖然通常順序并不重要,
- 鍵不能含有“\0”,其表示鍵的結尾;不能含有“.”和“$”,其有特殊意義,
- 每個檔案都必須有_id鍵且在集合中唯一,
資料型別
null:空值或不存在的欄位,- 布林值,
- 數值:64位浮點數(默認)、32位有符號整數(
NumberInt)、64位有符號整數(NumberLong), - 字串,
- 時間:UTC時間,
- 正則運算式,
- 陣列,
- 檔案,
- 物件ID(
ObjectId), - 二進制資料:不能直接在JavaScript shell中使用,
- 代碼:JavaScript代碼,
不會自動進行資料型別轉換,
排序時各型別比較從小到大依次為:
- 最小值
MinKey null- 數值
- 字串
- 檔案
- 陣列
- 二進制資料
- 物件ID
- 布林值
- 時間
- 正則運算式
- 最大值
MaxKey
填充因子
(注:因不再使用MMAPv1存盤引擎,此部分表述可能不再準確,)
空間間隔(padding),
更新檔案時,若檔案變大導致原先的位置空間不夠,該檔案會被移動到另一個位置,此時會修改集合的填充因子(padding factor),之后插入的檔案都擁有填充因子指定的空間,
寫入安全
寫入安全(Write Concern)用于控制寫入的安全級別,是否等待資料庫回應(寫入是否成功)才繼續執行,兩種最基本的寫入安全機制是應答式寫入(acknowledged write)和非應答式寫入(unacknowledged write),
自然排序
自然排序(natural sort)即檔案在磁盤中的順序,
固定集合
固定集合(capped collection)的大小是固定的,其行為類似回圈佇列,如果沒有空間,最老的檔案會被洗掉,新插入的檔案會占據這塊空間,固定集合中的檔案不能被洗掉,也不能通過更新改變其大小,固定集合必須在使用之前顯式創建,且不能被分片,
回圈游標
回圈游標是一種特殊的游標,當其結果集被取光后游標不會被馬上關閉,若有新檔案插入時,回圈游標會繼續取得新檔案,如果超過10分鐘沒有新檔案,回圈游標就會被釋放,由于普通集合不維護檔案的插入順序,回圈游標只能用在固定集合上,不能在Mongo Shell中使用回圈游標,
索引
索引桶(bucket)大小為8KB,
索引的本質是一棵樹,通常索引比存盤的資料量大得多,有很多空閑空間,以便在增加新入口(entry)時進行優化,右平衡索引(right-balanced index)可將空閑空間減至最少,且只需保留樹最右側的分支在記憶體中,_id索引就是一個典型的右平衡索引,隨機分布的索引通常有50%左右的空閑空間,升序索引(ascending-order index)有10%的空閑空間,
索引中,不存在的欄位和null值的欄位的存盤方式是一樣的,
可以對嵌套檔案本身建立索引,也可以對嵌套檔案的欄位建立索引,
對陣列建立索引,實際上是對陣列的每個元素建立一個索引條目,無法將陣列作為一個整體建立索引,一個索引中的陣列欄位最多只能有一個,這是為了避免在復合索引中索引條目的爆炸性增長,
使用索引需要進行兩次查找:一次是查找索引條目,一次是根據索引指標查找相應檔案,
可能有幾個索引匹配查詢,查詢優化器從這些索引中選擇一個查詢計劃,所有查詢計劃是并行執行的,最早回傳一定數量結果的是勝者,其他查詢計劃就會被終止,這個查詢計劃會被快取,此查詢接下來都使用此查詢計劃,直到集合資料發生較大變動,建立索引,或執行一定次數的查詢后,查詢優化器會重新評估查詢計劃,
默認在前臺創建索引,會阻塞所有讀寫請求直到索引創建完成,在后臺創建索引會定期釋放寫鎖,創建索引的程序暫停,因而能同時處理讀寫請求,但仍會對應用程式性能有較大影響,后臺創建索引比前臺創建索引慢得多,
索引基數(cardinality),即勢,是集合中欄位擁有的不同值的數量,索引基數越高,該欄位上的索引就越有用,
建立在多個欄位上的索引叫復合索引,相互反轉(每個欄位的方向都乘以-1)的索引是等價的,如果有一個N個欄位的復合索引,則同時得到N個由最左前綴的欄位組成的隱式索引,
當一個索引包含查詢需要的所有欄位,則沒必要獲取實際的檔案,這個索引覆寫了本次查詢,即使用覆寫索引(covered index),如果在一個含有陣列的欄位上做索引,這個索引永遠無法覆寫查詢,即使將陣列欄位從回傳的欄位中剔除,
如果索引的欄位在某個檔案中是一個陣列,則這個索引被標記為多鍵索引(multikey index),索引被標記為多鍵索引后,無法再變成非多鍵索引,即使這個欄位為陣列的所有檔案都從集合中洗掉,多鍵索引可能有多個索引條目指向同一個檔案,因此在回傳結果集時需要先去除重復的檔案,
唯一索引可確保集合中每個檔案的指定欄位都有不同的值,對復合索引,單個欄位的值可以相同,但所有欄位的組合值必須不同,
稀疏索引只對欄位存在的檔案創建索引條目,欄位不存在的檔案不創建索引條目,同一個查詢,使用稀疏索引與不使用稀疏索引的回傳結果可能會不同,
TTL索引(time-to-live index),即具有生命周期的索引,如檔案此欄位為時間型別,則超時后檔案會被洗掉,TTL索引不能是復合索引,TTL索引每隔一分鐘定期清理,
全文本索引只會對字串資料進行索引,匹配到的檔案按照相關性降序排列,一個集合最多只能有一個全文本索引,但是可以包含多個欄位,欄位的順序不重要,會被同等對待,全文本索引可以和非全文本索引組成復合索引,
地理空間索引包括2dsphere索引(用于球面地圖)和2d索引(用于平面地圖和時間連續的地圖),地理空間索引可以和非地理空間索引組成復合索引,2d索引只能對點進行索引,
典型場景
- 因為索引中不存在的欄位和
null值的欄位的存盤方式是一樣的,因此:對于非稀疏索引,{KEY: {$exists: true}}無法使用索引,{KEY: {$exists: false}}可以使用索引但還需遍歷對應的所有檔案;對于稀疏索引,{KEY: {$exists: true}}和{KEY: {$exists: false}}都無法使用索引,(注,本人看法:對于稀疏索引,{KEY: {$exists: false}}無法使用索引;其他情況下,$exists可能使用索引,) {KEY: {$ne: VALUE}}可以使用索引,但需掃描整個索引,但并不是很有效,{KEY: {$nin: [VALUE <, ...>]}}無法使用索引,(注:本人看法,可以使用索引,但需掃描整個索引,){KEY: {$not: CONDITION_DOC}}大多數都無法使用索引,但能夠對{KEY: {$not: {$lt: VALUE}}}等范圍和正則運算式進行反轉來使用索引,{$or: [CONDITION_DOC <, ...>]}可以使用索引,實際是執行多次查詢后將結果集合并并移除重復的檔案,{$where: FUNCTION}無法使用索引,- 對陣列建立的索引不包含下標資訊,
{"KEY.INDEX": VALUE}無法使用索引(注,本人看法:不確定是否可以使用索引,);但若創建了{"KEY.INDEX": 1}的索引,則使用{"KEY.INDEX": VALUE}查詢相同下標的元素時可使用此索引,
GridFS
GridFS將大檔案分割為多個比較大的塊,每個塊作為獨立的檔案進行存盤,此外還有一個檔案用于將這些塊組織在一起并存盤檔案的元資訊,如果要修改GridFS上的檔案,只能先將檔案洗掉再重新保存,無法在同一時間對檔案的所有塊加鎖,
聚合
聚合使用多個管道運算子創建一個管道(pipeline),每個管道運算子接受一連串的檔案,對這些檔案做一些轉換,然后將轉換后的檔案傳遞給下一個運算子(對最后一個管道運算子,就是回傳結果),這些管道運算子包括匹配(match)、投射(project)、分組(group)、拆分(unwind)、排序(sort)、限制(limit)、跳過(skip),
管道如果不是直接從原先的集合中使用資料(投射、分組、拆分可能產生這種情況),就無法在匹配和排序中使用索引,
應盡可能將匹配放在管道的前面,一是可以將不需要的檔案過濾掉來減少管道的作業量,二是可以使用索引,
聚合不會記錄欄位的歷史名稱,應在使用投射來重命名欄位前使用索引,
分組不能使用流式作業方式對檔案進行處理,必需要等收到所有檔案后,才能對檔案分組,然后才能將分組結果發送給管道下一個運算子,這意味著,在分片的情況下,分組會先在每個分片上執行,然后各個分片上的分組結果會發送到mongos再進行最后的統一分組,剩余的管道作業也是在mongos而不是分片上執行,
排序不能使用流式作業方式對檔案進行處理,必需要等收到所有檔案后,才能對檔案排序,然后才能將排序結果發送給管道下一個運算子,這意味著,在分片的情況下,排序會先在每個分片上執行,然后各個分片上的排序結果會發送到mongos再進行最后的統一排序,剩余的管道作業也是在mongos而不是分片上執行,建議在管道的前面進行排序,這時可使用索引,否則可能比較慢并占用大量記憶體,
如果需要跳過大量的檔案,效率會很低,因為必需要先匹配到所有需要跳過的檔案,然后才能將其丟棄,
MongoDB不允許單一聚合操作占用過多的系統記憶體,如果占用了20%以上的記憶體,就會直接輸出錯誤,
MapReduce
MapReduce需要幾個步驟:最開始是映射(map),將操作映射到集合中的每個檔案,這個操作要么無作為,要么產生一些鍵和值;然后洗牌(shuffle),按照鍵分組,將產生的值組成串列放到對應的鍵中,化簡(reduce)則把串列中的值化簡為一個值,這個值被回傳,然后接著進行洗牌,直到每個鍵的串列只有一個值為止,這也就是最終結果,
MapReduce非常慢,不應該用在實時資料分析中,每個傳遞給map的檔案都要先反序列化,從BSON物件轉換為JavaScript物件,這個程序非常耗時,
副本集
副本集(replica set)是一組服務器,其中有一個主節點(primary),還有多個備份節點(secondary),副本集最多只能有12個節點,其中只有7個節點有投票權,這是為了減少心跳請求的網路流量和選舉花費的時間,即使只有一個服務器,最好也將其設定為只有一個節點的副本集,
備份節點只通過復制功能寫入資料,不接受客戶端的寫入請求,備份節點可能落后于主節點,默認拒絕讀請求,防止拿到過期的資料,
許多維護作業不能在備份節點上進行(因為要執行寫操作),也不能在主節點上進行,要對該節點進行維護,可以單機模式(standalone mode)啟動服務,
修改副本集配置時,只能在新配置下可能成為主節點的節點上執行,副本集當前主節點需要先退化為備份節點,關閉所有連接,也即副本集中會暫時沒有主節點,配置修改成功后恢復正常,但也可以在新配置下可能成為備份節點的節點上強制重新配置(force reconfigure)副本集,
自動故障轉移(automatic failover):如果主節點崩潰,備份節點會自動將其中一個節點選舉為新的主節點,
大多數(majority)即副本集中一半以上的節點,選舉主節點時需要由大多數決定;主節點無法得到大多數支持時就會退位成備份節點;寫操作被復制到大多數時就是安全的,
選舉機制:當備份節點無法與主節點連通時,就會請求其他副本集節點將自己選舉為主節點,其他節點會做幾項檢查:其他節點自身能否與主節點連通;候選節點的資料是否最新;有沒有其他更高優先級的節點可以被選舉為主節點,如果其他節點發現任何原因表明候選節點不應該成為主節點,就會否決此次選舉,1張否決票相當于10000張贊成票,如果候選節點得到大多數贊成票,就會成為主節點,如果選舉打成平局,每個節點都需要等待30秒才能開始下一次選舉,
仲裁者(arbiter)的唯一作用是參與選舉,其并不保存資料,也不會為客戶端提供服務,這是一個永久選項,無法將仲裁者重新配置為非仲裁者,反之亦然,如果可能,盡量使用奇數個資料節點,不要使用仲裁者,
擁有高優先級的節點會優先選舉為主節點,優先級范圍是0至100,默認為1,優先級為0的節點永遠不能成為主節點,這樣的節點成為被動成員(passive member),
客戶端不會向隱藏成員發送請求,隱藏成員也不會作為同步源(盡管當其他同步源不可用時隱藏成員也會被使用),只有優先級為0的節點才能被隱藏,
延遲備份節點的資料會比主節點延遲指定的時間,要求優先級是0,如果應用會將讀請求路由到備份節點,應該將延遲備份節點設為隱藏成員,以免讀請求被路由到延遲備份節點,
備份節點并不需要與主機點擁有相同的索引(甚至可以沒有索引),這是一個永久選項,要求優先級為0,
同步
復制用于在多臺服務器間備份資料,復制使用操作日志(operation log)oplog實作,每個節點都可以作為同步源(sync source)提供給其他節點使用,oplog是一個固定集合,如果單個動作會影響多個檔案,那么每個受影響的檔案都會對應oplog中的一條操作,由于復制的程序是先復制資料再寫入oplog,所以備份節點可能會在已經同步過的資料上再次執行操作,MongoDB在設計之處就考慮到這種情況:將oplog中的同一個操作執行多次,與只執行一次的效果是一樣的,
復制圖譜(replication graph),
當一個備份節點從另一個備份節點而不是主節點處復制資料時,就會形成復制鏈,
如果形成復制鏈,每個備份節點都會將收到的復制請求轉發給其同步源,這些轉發的請求并不會要求進行資料同步,但主節點就能知道每個備份節點的同步源,這成為影同步(ghost syncs),
自動復制鏈(automatic replication chaining)的缺點:復制鏈越長,將寫操作復制到所有節點所花費的時間就越長,
如果復制鏈中出現了環,則稱為發生復制回圈,MongoDB根據ping時間選擇同步源,會選擇離自己比較近而且資料比自己新的節點,因此如果每個節點都自動選取同步源,就不可能出現復制回圈,
備份節點從副本集的另一個節點那進行完整的資料復制,這個程序就是初始化同步(initial sync),包括以下步驟:
- 先做一些準備作業:選擇一個節點作為同步源,在local資料庫的me集合中為自己創建一個識別符號,洗掉所有已存在的資料庫,以全新的狀態開始同步,
- 然后是克隆,將同步源的所有資料復制到本地,這通常是整個程序最耗時的部分,
- 然后進入oplog同步的第一步,克隆程序中所有操作都會被記錄到oplog中,
- 接下來是oplog同步的第二步,將oplog同步的第一步中的操作應用下來,
- 到目前為止,本地資料應該與同步源某個時間點的資料完全一致,可以開始創建索引,這個程序可能也會很耗時,
- 如果此時的資料仍然遠遠落后于同步源,那么oplog同步的第三步就是將創建索引期間的所有操作全部同步過來,
- 現在,此節點完成初始化同步,切換到普通同步,可以成為備份節點,
初始化同步程序中,如果第2步(克隆)或第5步(創建索引)耗費了太長時間,導致此節點遠遠落后于同步源,那么這個節點就是陳舊的(stale),當一個節點陳舊之后,它會查看副本集中其他的節點,如果某個節點的oplog足夠詳盡,可以用于處理那些落下的操作,就從這個節點進行同步,如果任何一個節點的oplog都沒有參考價值,那么此節點的復制就會終止,需要重新進行完全同步或者從備份中恢復,
執行初始化同步時,會強制將當前成員的所有資料分頁加載到記憶體中,這回導致需頻繁放文的熱資料不能常駐記憶體,會導致請求變慢,
節點從同步源進行同步,但發現無法找到自己的最后一次操作,這時它就會進行回滾(rollback),在二者oplog中找到共同點,然后將本地共同點之后的oplog操作撤銷,再進行正常同步,如果要回滾的資料量大于300MB或者要回滾30分鐘以上的操作,回滾就會失敗,對于回滾失敗的節點,必需重新同步,
滯后(lag)是指備份節點相對于主節點的落后程度,是主節點最后一次操作的時間戳與備份節點最后一次操作的時間戳的差,
心跳
每個節點每隔2秒會向其他所有節點發送一個心跳請求(heartbeat request),心跳會在最多20秒之后超時,
節點的狀態包括:
- STARTUP:節點剛啟動時處于這個狀態,在這個狀態下,會嘗試加載副本集配置,加載成功后就進入STARTUP2狀態,
- STARTUP2:整個初始化同步程序都處于這個狀態,在這個狀態下,會創建幾個執行緒,用于處理復制和選舉,然后切換到RECOVERING狀態,
- RECOVERING:維護模式(maintenance mode),這個狀態表明節點運轉正常,但暫時還不能處理請求,在啟動程序中成為備份節點之前,每個節點都要經歷此狀態,在處理非常耗時的操作時,也可能進入此狀態,當一個節點與其他節點脫節時,也會進入此狀態(通常來說,這個節點需要重新同步,但節點這時并沒有進入錯誤狀態,因為它期望發現一個擁有足夠詳盡oplog的節點,然后繼續同步oplog并回到正常狀態),
- SECONDARY:備份節點,
- PRIMARY:主節點,
- ARBITER:在正常的操作中,仲裁者始終處于此狀態,
- DOWN:如果節點變得不可達,就處于此狀態,節點可能仍處于正常運行狀態,不可達的原因可能是網路問題,
- UNKNOWN:如果一個節點無法到達任何其他節點,其他節點也就無法知道它處于什么狀態,會將其報告為此狀態,通常表明這個節點掛掉了,或者存在網路訪問問題,
- REMOVED:當節點被移出副本集時,就處于此狀態,
- ROLLBACK:如果節點正在進行資料回滾,就處于此狀態,回滾程序結束時,節點會轉換為RECOVERING狀態,然后成為備份節點,
- FATAL:如果節點發生了不可換回的錯誤,也不再嘗試恢復正常,它就處于此狀態,
驅動程式
連接到副本集時,驅動程式使用希望連接到的副本集種子(seed)串列,種子是副本集節點,并不需要將所有節點都列出來(雖然可以這樣做),驅動程式連接到某個種子服務器后,就能夠得到其他節點的地址,
可以設定驅動程式的讀取首選項(read preferences):
- Nearest:將讀請求路由到延遲最低的節點(根據驅動程式到副本集節點的ping時間),
- Primary:始終將讀請求發送到主節點,如果沒有主節點,請求會出錯,默認設定,
- Primary preferred:主節點優先,失去主節點時,應用程式進入只讀狀態,將讀請求發送至備份節點,
- Secondary:始終將讀請求發送至備份節點,如果沒有可用的備份節點,請求會出錯,
- Secondary preferred:優先將讀請求路由到可用的備份節點,如果備份節點都不可用,請求會被發送到主節點,
將讀請求發送到備份節點通常不是一個好主意:對一致性要求非常高的應用程式不應該從備份節點讀取資料;更好的選擇是使用分片做分布式負載,
分片
分片(sharding)是指將資料拆分,分散存盤在不同機器上的程序,有時也用磁區(partitioning)來表示,
手動分片(manual sharding):應用程式需要維護與若干不同資料庫服務器的連接,每個連接是完全獨立的,應用程式管理不同服務器上不同資料的存盤,還管理在合適的資料庫上查詢資料的作業,非常難以維護,
MongoDB支持自動分片(auto sharding),可以使資料庫架構對應用程式不可見,應用程式只需要連接到路由服務器(mongos),就可以像使用單機服務器一樣進行請求,每個分片對請求的回應都會發送給路由服務器,路由服務器將所有回應合并在一起,回傳給應用程式,
主分片(primary shard)保存資料庫中沒有被分片的集合的資料,
片鍵(shard key)是集合的一個鍵,根據這個鍵拆分資料,只有被索引過的鍵才能作為片鍵,
使用復合片鍵(compound shard key)可實作多熱點技術,
包含片鍵的查詢能直接被發送到目標分片或者是集群分片的一個子集,這樣的查詢叫做定向查詢(targeted query),如果查詢必需被發送到所有分片,這樣的查詢叫做分散-聚集查詢(scatter-gather query),
拆分資料常用的資料分發方式有三種:
- 升序片鍵(ascending key):片鍵會隨著時間穩定增長,會導致所有的寫入、拆分、均衡都是在同一個分片,如果沒有高性能服務器處理插入流水,活沒有使用標簽,那么不要使用升序片鍵,
- 隨機分發的片鍵(hashed shard key):片鍵沒有固定的規律,隨機訪問資料時效率不高,無法使用片鍵做范圍查詢,不能使用unique選項,浮點型會先取整然后再散列,使用隨機片鍵在集群上分配負載是非常好的,
- 基于位置的片鍵(location-based key):位置不必與實際的物理位置相關,資料會根據位置分組,位置比較接近的檔案會被保存在同一個塊中,
片鍵不可以是陣列,檔案一旦插入,其片鍵的值就無法修改,大多數特殊型別的索引都不能用作片鍵,特別是不能在地理空間索引上進行分片,
配置服務器保存集群的配置資訊:集群中有哪些分片、分片的是哪些集合、資料塊的分布,配置服務器的1KB空間約可表示200MB資料,
MongoDB將檔案分組為塊(chunk),每個塊由片鍵特定范圍內的檔案組成,具有相同片鍵的檔案保存在相同的塊中,
塊范圍是一個左閉右開的區間,含有$maxKey的塊叫最大塊(max chunk),塊大小的最大值默認為64MB,
塊與塊之間叫拆分點(split point),當塊大小增長到某個閾值,就會檢查是否需要對塊進行拆分,如需拆分,則在配置服務器更新塊的元資訊,不用進行資料移動,即使塊較大,有時也可能找不到可用的拆分點,
mongos不斷重復發起拆分請求卻無法拆分的程序叫做拆分風暴(split storm),防止拆分風暴的唯一方法是保證配置服務器可用,
每個mongos各自維護寫入計數器計算自身收到的寫請求是否到達拆分閾值,盡可能保證mongos可用,而不是在需要時開啟不需要時停止,
每個mongos都有可能周期性地變身為均衡器(balancer),檢查分塊表查看是否有分片到達了均衡閾值(balancing threshold),即一個分片明顯比其他分片有更多的塊(只使用塊數量,而非資料大小),如有則對整個集群加鎖,防止配置服務器對集群進行修改,然后進行塊遷移(migration);如無則不再充當均衡器,均衡并不會影響路由,
當from分片收到mongos發來的遷移命令時,會執行以下步驟:
- 檢查命令的引數,
- 向配置服務器申請獲得一個分布式鎖,以便進入遷移程序,
- 嘗試連接到to分片,
- 資料復制,這是整個程序的臨界區,from分片與to分片是直接通信的,
- 與to分片和配置服務器一起確認遷移是否成功完成,
當to分片收到from分片發來的遷移命令時,會執行以下步驟:
- 遷移索引,如to分片已有遷移集合的塊,則忽略此步驟,
- to分片洗掉塊范圍內已存在的所有檔案,
- 將from分片的塊中所有檔案復制到to分片,
- 復制期間,在to分片上重新執行曾在這些檔案上執行過的操作,
- 等待to分片將遷移過來的資料復制到集群的大多數節點上,
- 修改塊的元資料以完成遷移程序,表明資料已被成功遷移至to分片,
在執行幾乎所有資料庫管理操作之前,都應先關閉均衡器,
不可拆分(因所有檔案的片鍵值一樣)和不可遷移(因塊大小超出最大值)的塊叫特大塊(jumbo chunk),
必需明確指定資料庫和集合,不會自動對資料進行拆分,通常不必太早分片,因為分片不僅會增加部署復雜度,還要求做出設計決策,而該決策以后很難再改變,另外,最好也不要在系統運行太久之后再分片,因為在一個過載的系統上不停機進行分片是非常困難的,
至少應該創建3個或以上的分片,隨著分片數量不斷增加,系統性能大致會呈線性增長,但是如果從一個未分片的系統轉換為只有幾個分片的系統,性能通常會有所下降,
通常來說,不應從集群中洗掉分片,洗掉分片時,在排出資料(draining)的程序中,均衡器會負責將待洗掉分片的資料遷移至其他分片,塊在移動前可能被拆分,
分片時,admin資料庫會被保存在配置服務器,因此分片中的節點并不知道admin資料庫的存在,
當使用副本集作為分片時,要修改副本集,需直接連接到副本集的主節點而不是通過mongos,然后對副本集進行重新配置,配置服務器會自動檢測更改,
可以啟動任意數量的mongos行程,通常是每個應用服務器使用一個mongos行程,可與應用服務器運行在同一臺機器上,
連接
分片創建的連接數量有:
- mongos行程數量*3:每個mongos會為每個mongod創建3個連接,一個用于轉發客戶端請求;一個用于追蹤錯誤資訊,即寫回監聽器(writeback listener);一個用于監控副本集狀態,
- 每個副本集的節點數量*3:主節點會與每個備份節點創建1個連接,每個備份節點會與主節點創建2個連接,
- 其他行程數量:指其他連接到mongod的行程數量,包括MMS代理、shell連接、遷移時連接到其他分片的連接,
網路的連接情況如下:
- 單機服務器:
- 客戶端需與mongod連接,
- 副本集:
- 客戶端需與所有可見的非仲裁者節點連接,
- 副本集節點需與其他所有節點連接,
- 分片:
- 客戶端需與mongos連接,
- mongos需與所有分片及配置服務器連接,
- 分片需與其他所有分片和配置服務器連接,
身份驗證
每個資料庫都可以擁有任意個用戶,admin和local是兩個特殊的資料庫,當中的用戶為超級用戶,可對所有資料庫進行操作,并能執行管理員命令,
驅動程式如使用連接池或需切換到另一個節點,則需在新連接重新進行身份驗證,這一操作應由驅動程式自動處理,
系統分析器
系統分析器(system profiler)會導致mongod的整體性能有所下降,系統分析器默認處于關閉狀態,
診斷日志
診斷日志(diaglog,diagnostic log)用來記錄和回放操作流水,啟用診斷日志會造成性能損失,
日記系統
日記系統(journaling):MongoDB會在進行寫入時建立一條日記(journal),日記中包含此次寫入操作更改的磁盤地址和位元組,因此,一旦服務器突然停機,可在啟動時對日記進行重放,重新執行那些停機前沒能重繪到磁盤的寫入操作,
臟頁默認每60秒重繪到磁盤一次,因此日記檔案只需記錄約60秒的寫入資料,
默認每隔100毫秒,或寫入資料到達若干位元組時,便會將這些操作寫入日記檔案,
日記系統會影響寫入速度,對于所有生產環境都推薦使用日記系統,
BSON
BSON(Binary JSON),即二進制JSON,是一種輕量的二進制格式,用一串位元組來描述任何MongoDB檔案,是檔案存放于磁盤中的格式,也是服務器與客戶端間傳輸的格式,
BSON的優點有:
- 高效:無需占用過多額外空間,最壞的情況下效率比JSON低一點,最好的情況下(如存盤二進制資訊)效率比JSON高很多,
- 可遍歷性:BSON以空間效率為代價(如添加前綴表示字串長度而不是依賴末尾的終止符),使自身更容易被遍歷,這一特性在對檔案進行內省(introspect)時很實用,
- 高性能:可快速進行編碼和解碼,
線路協議
驅動程式使用輕量的TCP/IP線路協議(wire protocol)來訪問服務器,其基本上就是對BSON資料進行簡單的包裝,
資料檔案
預分配(preallocated)檔案:每個資料庫內應一致存在一個填充為0的空檔案,該檔案被寫入資料后,下一個空檔案會被預分配,
在資料檔案中,資料庫被按照命名空間(namespace)進行組織,每個命名空間中存放有特定集合的資料,每個命名空間中的資料在磁盤上被分為幾組資料,即區段(extent),每個命名空間可擁有幾個不同的區段,這幾個區段在磁盤上不一定是連續的,
修復實質上是洗掉所有受損資料,修復程序會將所有輸出都寫入新檔案中,不會對原有檔案進行修改,
記憶體映射存盤引擎
MongoDB使用記憶體映射存盤引擎,服務器啟動時其記憶體對所有資料檔案進行映射,接下來就由作業系統負責將資料重繪到磁盤,以及管理記憶體中的資料頁交換,因此在32位系統最多只能存盤2GB資料,
作業集(working set)是能覆寫大多數請求的核心資料集,
MongoDB使用的記憶體型別有:
- 常駐記憶體(resident memory):MongoDB在物理記憶體中明確擁有的記憶體部分,
- 映射記憶體(mapped memory):MongoDB賦予了地址的資料頁面,通常約等于整個作業集的大小,MongoDB賦予頁面一個地址,此地址并非物理記憶體中的真實地址,而是一個虛擬地址,MongoDB向作業系統內核請求記憶體,內核會在它的頁快取(page cache)中進行查找,查找失敗會產生一個缺頁中斷(page fault),將此頁資料從磁盤復制至記憶體,
- 虛擬記憶體:MongoDB為映射記憶體的每個頁面,都額外維護一個虛擬地址,以供日記系統使用(這不意味記憶體中有兩份同樣的資料,有的只是兩個地址),所以MongoDB使用的虛擬記憶體總量,約等于映射記憶體的兩倍,
訪問不在記憶體中的索引條目通常造成兩次缺頁中斷,分別發生在將索引條目和檔案加載如記憶體時,查詢索引造成缺頁中斷稱為索引樹脫靶(btree miss),無需訪問磁盤即可查詢索引成為索引樹中靶(btree hits),
IO延遲指CPU閑置等待磁盤回應的時間,通常于缺頁中斷密切相關,
后臺重繪平均時間(background flush average)是指將臟頁(dirty page)寫入磁盤所花費的時間,通常反映寫入負載的大小,因此如果寫入負載很低,則可能無法表現出磁盤的壓力大小,
鎖比例(lock percdntage)指MongoDB處于鎖定中的時間,鎖比例過高的最普遍原因之一是缺少合適的索引,
MMS
MMS(Mongo Monitoring Service)即Mongo監控服務,
作業系統
標準的部署方案是使用較少的記憶體空間和較大的機械磁盤空間,但需注意記憶體空間應大于作業集大小,
如副本集中存在機械硬碟,通常不能添加固態硬碟,如使用固態硬碟的節點成為主節點,并接管它能處理的一切作業,其他節點受速度所限無法及時復制資料,從而被落后,因此如要引入固態硬碟,向集群中添加一個新的分片是更好的方法,
用機械硬碟記錄日記,用固態硬碟記錄資料,這樣既能節省固態硬碟的空間,也不會影響性能,
不要使用RAID5,MongoDB進行的是多次少量的寫入作業,會非常慢,
如需在記憶體和CPU間選擇一個進行硬體投資,一定要選擇記憶體,如需在CPU速度和核數間做出選擇,應選擇速度,
64位Linux是運行MongoDB的最好選擇,64位Windows也能很好地運行,
MongoDB只支持小端,
應分配一小塊交換空間,以防系統記憶體使用過多而導致內核的記憶體溢位殺手(OOM killer,out-of-memory killer)終止MongoDB的運行,MongoDB通常不會使用交換空間,但在需要對資料進行排序,即建立索引或進行排序操作時,會使用交換空間,
在Linux上,推薦使用ext4或XFS檔案系統,具有一個能在備份時進行檔案系統快照(filesystem snapshot)的檔案系統是不錯的,但是會影響到性能,不推薦使用ext3檔案系統,因為它在預分配資料檔案時耗時過長,在Windows上使用NTFS和FAT檔案系統都是可以的,
虛擬化(virtualization)存在缺點,尤其是無法預知的網路和磁盤IO狀況,
NUMA(Non-uniform Memory Architecture),即非一致記憶體結構,面對資料庫尤其是MongoDB時,表現非常糟糕,
預讀(readahead)即作業系統從磁盤中讀取筆實際請求更多的資料,推薦預讀16至256個扇區,
作業系統以頁面為單位在磁盤和記憶體間轉移資料(x86架構默認為4096位元組),啟用大記憶體頁面(hugepage)導致的問題和預讀過多內容導致的問題類似,一般不要啟用這一特性,
磁盤控制器從作業系統收到請求后,會使用一種磁盤調度演算法來決定處理這些請求的順序,截止時間(deadline)調度演算法和完全公平佇列(CFQ,completely fair queueing)是不錯的選擇,有時noop(這是最簡單的調度演算法)調度演算法是最好的選擇,如在虛擬化環境中、在固態硬碟上、使用RAID控制器進行快取時,
由于MongoDB訪問資料檔案十分頻繁,如禁止記錄最后訪問時間(atime)則會得到性能的提升,
MongoDB可能會受到兩個限制的影響,通常應設為無限制(包括軟限制和硬限制):
- 行程可建立執行緒的數量,
- 行程能夠打開檔案描述符(file descriptor)的數量
一般各節點間的時鐘誤差不超過1秒是最安全的,副本集能處理幾乎所有的始終偏移(clock skew),分片則能處理一部分時鐘偏移,
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/419951.html
標籤:其他
上一篇:Druid連接SQL Server報錯:The TCP IP connection to the host 127.0.0.1, port 1433 has failed.
下一篇:Druid連接SQL Server報錯:The TCP IP connection to the host 127.0.0.1, port 1433 has failed.
