標籤:mongodb hidden member tag sets
MongoDB報表執行個體方案選型
背景介紹
在我們的生產環境使用的是複製集,為了將資料庫伺服器的業務壓力分攤,我們將資料庫拆分到了不同的複製集上運行。
我們在MongoDB複製集上運行應用程式,有時候有報表需求,常規用途是獲得使用者行為的分析,還有其他商業定製指標資料;有搜尋引擎的查詢需求,使用Solr從oplog.rs擷取增量資料更新產品資訊的索引。
這些報表查詢和搜尋引擎的查詢需求,盡量不能影響到線上的業務正常運行,因此不能直接在生產資料庫上運行報表。經過開發和營運討論之後,在項目成立之初,計劃隔斷報表任務以致不會影響到生產任務。
來談談混淆生產和報表的問題
工作集(workingset),是MongoDB在任何的時間間隔讀取和寫入的整個資料庫的一個子集。生產環境中的活躍使用者操作文檔資料,作業系統將它們保持在實體記憶體中。
注意:不要讓你的工作集增長大過記憶體!可以使用MongoDB的監控服務Cloud Manager監控你的執行個體。如果確實出現這個問題,你需要分區,因此容量規劃是很重要的,可以作為獨立的專題來講。即使資料庫大小是可用記憶體的數百或數千倍,如果你在前期合理規划了架構並最佳化了索引,MongoDB同樣也能高效運行。在工作集外的資料會保持和磁碟上一致,當使用者空閑時,他們操作的文檔將會不再使用,所佔用的記憶體用於新的活躍使用者的記憶體請求。
報表應用會查詢大量的資料,一般不會重複訪問相同的資料,每個報表應用可能完全訪問不同的資料集合。這意味著需要持續提供記憶體給新的文檔讀取請求。如果你將報表應用和生產應用放在相同的執行個體上運行,報表應用將會與你的生產應用爭奪記憶體,持續不斷地請求活躍使用者的資料,而你的生產應用持續不斷的載入它(getmore)。那麼資料庫伺服器效能將發生波動。
報表應用,將會有大量count、aggregate、mapReduce等彙總操作,這些操作對於MongoDB來說效率不高,因此將它與生產任務分開是一個好的做法。
使用專屬報表執行個體的複製集
MongoDB複製集具有線上持久性,通過複製資料到一個集合中的所有節點,並對用戶端提供無縫的容錯移轉。包含一個主節點提供寫,而剩下的是唯讀副本。當條件需要的時候選舉決定哪個節點是主。複製集應該包含一個奇數成員協助快速選舉。
判斷不可達的機器是否宕機基本上無法判斷,有可能被網路被分區了。因此如果複製集中的大多數節點下線了(也就是說,3個成員中的2個下線),即使一個健康的主節點保留,它會降級為一個唯讀副本。不這麼做可能導致多個機器在一個網路磁碟分割的情況下定義它們自己為主節點,出現多個主節點,導致可怕的資料不一致。
因此一個複製集包含至少3個成員,提供一個機器失敗的錯誤容忍。
在MongoDB官方的文檔中,推薦限制報表查詢到專屬節點。報表基本不需要寫操作,而是統計最終一致性資料。如果提取的資料有秒級或分級延時,每日的報表是不允許的。如果你的計數統計丟失了一些操作,這將導致報表資料不準確。
你可以在MongoDB複製集環境構建專屬的報表節點,方案有隱藏的複製整合員hidden member或者讀偏好read preference設定相關的標籤集合tag sets。第一種方法更簡單,第二種方法更靈活。
是使用專屬節點提供報表需求的架構圖:
650) this.width=650;" title="clip_image002" style="border-top:0px;border-right:0px;background-image:none;border-bottom:0px;padding-top:0px;padding-left:0px;border-left:0px;margin:0px;padding-right:0px;" alt="clip_image002" src="http://s3.51cto.com/wyfs02/M00/89/F0/wKioL1gikuyRDjsbAACI5-Tai8g364.jpg" border="0" height="429" />
隱藏成員方案
參考:https://docs.mongodb.com/manual/tutorial/configure-a-hidden-replica-set-member/
隱藏成員是複製集的一部分,但是不能成為主,並且對用戶端應用程式不可見。隱藏成員可以在選舉中投票。
一個複製集的隱藏成員被配置為priority: 0,是為了阻止它們被選舉為主。設定hidden: true,即使他們指定了一個讀偏好為secondary,也會阻止用戶端串連到複製集路由讀操作到它。
從一個隱藏成員讀資料,你只能通過直連該隱藏成員訪問,並指定slave_ok,而不能通過MongoReplicaSetClient類。
隱藏成員設定
你可以使用mongo shell來隱藏一個存在複製集的成員:
$ mongo admin -uxucy -pPRIMARY> conf = rs.config(){ "_id" : "test", "version" : 21, "members" : [ { "_id" : 0, "host" : "xucy.local:27017", }, { "_id" : 1, "host" : "xucy.local:28017", }, { "_id" : 2, "host" : "xucy.local:29017", } ] }PRIMARY> conf.members[1].priority = 0PRIMARY> conf.members[1].hidden = truePRIMARY> conf.version += 1PRIMARY> rs.reconfig(conf)
xucy.local:28017現在隱藏了,它將繼續複製操作和像往常一樣在選舉中投票,但是串連到複製集的用戶端將不會從它讀取,即使xucy.local:29017下線。
Ruby版的報表應用串連程式碼範例:
require ‘mongo‘reporting = Mongo::MongoClient.new("xucy.local", "28017", slave_ok: true)reporting[‘my_application‘][‘users‘].aggregate(...)
限制說明
使用隱藏的成員是一個最簡單的方式,配置執行個體用於專屬的工作負載,像報表和搜尋引擎訪問,然而使用上有一些限制需要說明的。
隱藏成員不能在緊急情況下讀取
帶有2個普通和1個隱藏成員在一個複製集中,對於寫的錯誤容忍等價於一個常規的3個成員的集合。然而,你失去兩個節點,你的生產應用將不能優雅的降級到唯讀模式,因為你的隱藏成員將不允許複製集用戶端讀取。如果你只是喜歡隱藏成員訪問簡單,土豪方案是使用一個5成員(帶有一個隱藏成員)的複製集。
對於複製集的封裝代碼不能被使用
很多團隊建立應用定製的封裝代碼時,使用MongoDB驅動提供的複製集串連存取方法,添加複製集串連的基本資料給用戶端。因為你需要使用獨立串連到你的報表執行個體,你不能重用它。
標籤成員方案
參考:https://docs.mongodb.com/manual/tutorial/configure-replica-set-tag-sets/
標籤成員,更加複雜,但是,是更靈活的方法,用於路由報表查詢到一個專屬節點去使用標籤和讀偏好。
設定一個成員為priority: 0,阻止它被選舉為主,但是不設定它為隱藏,分配一個標籤use: reporting:
PRIMARY> conf = rs.config(){ "_id" : "test", "version" : 21, "members" : [ { "_id" : 0, "host" : "xucy.local:27017", }, { "_id" : 1, "host" : "xucy.local:28017", }, { "_id" : 2, "host" : "xucy.local:29017", } ] }PRIMARY> conf.members[1].priority = 0PRIMARY> conf.members[1].tags = { "use": "reporting" }PRIMARY> conf.version += 1PRIMARY> rs.reconfig(conf)
在這種情況下,xucy.local:28017絕不會成為主。然而,當其他兩個機器變得不可達,你的應用還能處理讀請求到報表伺服器。它會繼續運行,不會導致你的報表應用在這樣一個事件期間暫停。
Python版報表應用串連程式碼範例:
from pymongo import MongoReplicaSetClientfrom pymongo.read_preferences import ReadPreferencerep_set = MongoReplicaSetClient( ‘xucy.local:27017,xucy.local:28017,xucy.local:29017‘, replicaSet = ‘test‘, read_preference = ReadPreference.SECONDARY, tag_sets = [{‘use‘:‘reporting‘}] )rep_set.my_application.users.aggregate(...)
對於報表應用來說,在主可用的情況下,確保盡量不要在剩下的唯一的輔助成員上運行報表應用,因為這樣將報表和生產混合在一起了。
以上只發送報表查詢到標記有use: reporting的輔助成員,並且如果沒有可用的主,我們應該從根本上阻止繼續運行。在實踐中,如果你發現沒有主,你應該拋出異常並在你的擴充代碼中處理它們。還要做好狀態的監控,如:reporting_system.ok()。當發現異常時進行分支處理。
益處和考慮
使用標籤和讀偏好相對隱藏成員來說,帶來一定的靈活性。
容易添加報表執行個體
因為你的串連代碼是可定義的,而不是指定到一個專門的主機。你可以添加更多節點為報表執行個體,只需要添加並標記他們,像這樣:
PRIMARY> rs.add({_id:3, host:"xucy.local:30017", priority:0, tags:{‘use‘:‘reporting‘}})
你原來的代碼將會利用到新的報表執行個體,並且複製集將繼續運行,不用觸發選舉和從用戶端中斷連線。
報表執行個體可以被跳過或刪除
當你想將目前使用的報表執行個體提供給其他應用時,報表標記在必要時可以被移動,或者移除。像這樣的一個重新設定將會觸發選舉,並重連所有用戶端,這是可以接受的。注意:這是一個逆向的方法,通過增加常用生產可用執行個體,分發生產讀到副本成員。
一些驅動需要手工同步
檢查你的驅動文檔,例如,Ruby驅動(像1.9.2),不會重新整理複本集的視圖,除非用戶端像這樣使用refresh_mode: :sync顯式初始化。
Solr產生全文索引
MongoDB複製集配置簡單、容易上手是我喜歡MongoDB的原因之一。對於MongoDB報表執行個體,無論你使用隱藏成員還是標籤成員,開發和部署都非常簡單。我們在生產環境也將報表執行個體用於Solr產生全文索引。
Solr是一個獨立的企業級搜尋應用伺服器,它對外提供類似於Web-service的API介面。使用者可以通過http請求,向搜尋引擎伺服器提交一定格式的XML檔案,產生索引;也可以通過Http Get操作提出尋找請求,並得到XML格式的返回結果。
讀取報表執行個體的方案選型
期初的方案是使用mongo-connector整合MongoDB到Solr實現增量索引。(http://ultrasql.blog.51cto.com/9591438/1696083/)
mongo-connctor是一款用於同步MongoDB資料到其他系統組件,比如它能同步資料到Solr、Elasticsearch或者其他MongoDB叢集中去。它的實現原理是依據MongoDB的Replica Set複製模式,通過分析oplog記錄檔達到最終的同步目的。安裝配置啟動過程可參考 官方文檔 。
由於是單進程版本的,效率對我們當時來說不高,如果能改成利用多核效能的話可能好些。
MongoDB Tailable Cursors
MongoDB 有一個叫 Tailable Cursors的特性,它類似於tail -f 命令,你在一個Capped Collection上面執行查詢操作,當操作完成後,你可以不關閉返回的資料Cursor,並持續地從中讀出新加入的資料。
在高寫入的Capped Collection上,索引不可用時,可使用Tailable Cursors。例如,MongoDB複製使用了Tailable Cursors來擷取Primary的尾oplog日誌。
考慮以下與Tailable Cursors相關的行為:
Tailable Cursors不使用索引,並以自然排序返迴文檔。
因為Tailable Cursors不使用索引,查詢的初始掃描非常耗效能;但是,遊標初始化完後,隨後擷取到的新增加的文檔是很快速的。
Tailable Cursors如果遇到以下情況之一將會僵死或無效:
僵死的遊標id為0。
DBQuery.Option.awaitData
在使用TailableCursor時,此參數會在資料讀盡時先阻塞一小段時間後再讀取一次並進行返回。
跟蹤oplog的樣本:
use localvar cursor = db.oplog.rs.find({"op" : "u", "ns" : "MyDB.Product"},{"ts": 1, "o2._id": 1}).addOption(DBQuery.Option.tailable).addOption(DBQuery.Option.awaitData);while(cursor.hasNext()){var doc = cursor.next();printjson(doc);};
2.6版的遊標方法:
cursor.addOption()
https://docs.mongodb.com/v2.6/reference/method/cursor.addOption/
3.2版的遊標方法:
cursor.tailable()
https://docs.mongodb.com/manual/reference/method/cursor.tailable/
我們根據該特性,開發了Java應用程式,先初始化全量同步資料到Solr產生索引、記錄同步時間。然後通過Tailable Cursors讀取oplog.rs對比上次記錄的同步時間,如果是新的變更,通過新的進程非同步擷取日誌裡記錄的最新資料更新到Solr的文檔裡。
650) this.width=650;" title="clip_image004" style="border-top:0px;border-right:0px;background-image:none;border-bottom:0px;padding-top:0px;padding-left:0px;border-left:0px;padding-right:0px;" alt="clip_image004" src="http://s3.51cto.com/wyfs02/M00/89/F0/wKioL1giku3RtFrtAACpmMHkl7k565.jpg" border="0" height="419" />
本文出自 “SQL Server Deep Dive” 部落格,請務必保留此出處http://ultrasql.blog.51cto.com/9591438/1870982
MongoDB報表執行個體方案選型