標籤:global 查詢 turn hot cut ons 功能 資源 group
這裡總結下這段時間使用mongo的心得,列出了幾個需要注意的地方。
1. 系統參數及mongo參數設定
mongo參數主要是storageEngine和directoryperdb,這兩個參數一開始不選定後續就無法再更改。
directoryperdb主要是將資料庫分檔案夾存放,方便後續的備份及資料移轉。
storageEngine(儲存引擎)預設使用的是MMAPv1,推薦使用3.0新加入的引擎wiredTiger。經實際使用wiredTiger佔用的磁碟空間是MMAP的1/5,索引大小是其1/2,查詢速度也提高很多,更重要的是該引擎提供了document層級的鎖,當集合插入或更新資料時不需要阻塞讀操作了。唯一的問題是市面上支援該引擎查詢的工具不多,MongoVUE無法查到該引擎儲存的集合,NosqlManager-mongo可以查到但需要.net環境支援。個人覺得熟悉下mongo command用mongo shell就足夠了,所以還是強烈推薦使用wiredTiger引擎。
2. 無需對集合進行水平切分
由於之前一直使用關係型資料庫,關係型資料庫當單表資料量超大時經常使用的一直方法是對資料表進行分表。在使用mongo時便很自然的覺得這招仍然有用。由於該系統的分表都是動態產生的,做到後面發現這招對mongo帶來的效能提升遠遠抵不過維護成本的增加。
分析一下關係型資料庫分表會提高效能的最大原因是很多關係型資料庫一張表是一個檔案,分表可以避免一個檔案過大所造成資料提取速度變慢。但是mongo並不是這樣儲存的,所以這條並不成立了。
用過的都知道mongo對索引的依賴非常大,如果集合不能一開始就設計好,那後續索引就得寫指令碼來建立。這裡貢獻個給mongo大表動態建立索引的指令碼:
//找出所有資料量超過100w的資訊集合info,並為其建立索引eval(function () { var infos = []; var collNames = db.getCollectionNames(); for (var i = 0; i < collNames.length; i++) { var collName = collNames[i]; var collSize = db.getCollection(collName).count(); if (collSize > 1000000 && collName.indexOf("info_")==0) { db.getCollection(collName).ensureIndex({publishDate:-1,blendedScore:-1,publishTime:-1,isRubbish:1},{name:"ScoreSortIdx",background:true}); db.getCollection(collName).ensureIndex({similarNum:-1,publishTime:-1,isRubbish:1},{name:"HotSortIdx",background:true}); db.getCollection(collName).ensureIndex({publishTime:-1,isRubbish:1},{name:"TimeSortIdx",background:true}); infos.push("name:" + collName + "索引建立成功"); } } return infos;}());
這麼看動態建立索引勉強還是可以解決的,但是最坑的一個地方是sharding完全沒辦法做了。shard需要指定要shard的集合和分區鍵,這個就沒法提前動態指定了。所以mongo集合不需要做水平切分(至少千萬級不需要了,更大直接shard掉),只需要按業務分開就可以了。
3. 使用Capped Collection
有人使用mongo做資料緩衝,而且是緩衝固定數量的資料,仍然用正常的集合,然後定期清理資料。其實這時用capped collection效能會好很多。
4. 生產環境一定要用複本集
很多人線上環境還是用單機版,雖然部署快但是很多mongo自然提供的功能都沒有用到像自動容錯移轉、讀寫分離,這些對後續系統擴容及效能最佳化太重要了。我想會使用mongo的應該是資料量達到一定層級,查詢效能會非常重要,所以強烈建議上線時直接使用複本集。
5. 學會使用explain
之前一直習慣用工具來查詢,現在發現應該多使用mongo shell命令來查詢,並使用explain查看查詢計劃。另外在尋找最優索引的時候hint命令也是非常有用的。
db.info.find({publishDate:{$gte:20160310,$lte:20160320},isRubbish:{$in:[0,1]},title:{$regex:".*test.*"},$or:[{useId:10},{groupId:20}]}).explain("executionStats");
6. 寫操作頻繁無法使用讀寫分離
由於系統寫操作較多,造成各種w層級鎖經常出現(這種鎖一般是block read的)而且系統對於資料一致性要求不會太多(大多是後台寫入,前台讀取,因此允許有一定延遲)所以想用複本集來做讀寫分離。當真正測試後發現複本集上的讀取也經常出現阻塞的情況。通過db.currentOp()發現經常出現一個op:none的操作在申請global write lock,這時所有操作的狀態都是在waitingForLock:true,這個問題google了很久都沒找到解決方案。後面在官方文檔有關並發的FAQ中發現下面這個大坑:
How does concurrency affect secondaries?
In replication, MongoDB does not apply writes serially to secondaries.
Secondaries collect oplog entries in batches and then apply those
batches in parallel. Secondaries do not allow reads while applying the
write operations, and apply write operations in the order that they
appear in the oplog.
原來mongodb的副本在複製主節點資料執行oplog的時候,讀取是被阻塞的,這基本宣告無法在副本上去讀取資料了,白白耗費了幾天精力。所以mongo官方不推薦做讀寫分離,原來坑是在這裡。。。其實寫多讀少的情況做讀寫分離作用也不大,因為效能瓶頸主要是在寫入,讀取一般不消耗多少資源(另外wiredTiger引擎的鎖做到了doc層級,所以鎖的情況相對較少)。官方推薦的做法是shard,可以有效將寫入分配到多台伺服器提高寫入速度,使系統實現水平擴容。
7、千萬不要讓磁碟滿了
80%的時候就要開始注意從集拆分區,如果你的資料增長特別快,很可能你還沒有拆分磁碟就滿了導致MongoDB掛掉了。如果資料量很大,盡量使用分區,不要使用複本集,做好磁碟容量規劃,就是使用分區了也提前擴容,畢竟chunk遷移還是那麼的慢。
8、安全風險
MongoDB是預設不提示使用者佈建密碼的,所以,如果你沒有配置密碼又把MongoDB放在公網上面了,那麼「恭喜」,你可能已經成為了肉雞
9、
資料庫級鎖
MongoDB的鎖機制和一般關聯式資料庫如 MySQL(InnoDB), Oracle 有很大的差異,InnoDB 和 Oracle 能提供行級粒度鎖,而 MongoDB 只能提供 庫級粒度鎖,這意味著當 MongoDB 一個寫鎖處於佔用狀態時,其它的讀寫操作都得乾等。
初看起來庫級鎖在大並發環境下有嚴重的問題,但是 MongoDB 依然能夠保持大並發量和高效能,這是因為 MongoDB 的鎖粒度雖然很粗放,但是在鎖處理機制和關聯式資料庫鎖有很大差異,主要表現在:
MongoDB 沒有完整事務支援,操作原子性只到單個 document 層級,所以通常操作粒度比較小;
MongoDB 鎖實際佔用時間是記憶體資料計算和變更時間,通常很快;
MongoDB 鎖有一種臨時放棄機制,當出現需要等待慢速 IO 讀寫資料時,可以先臨時放棄,等 IO 完成之後再重新擷取鎖。
通常不出問題不等於沒有問題,如果資料操作不當,依然會導致長時間佔用寫鎖,比如下面提到的前台建索引操作,當出現這種情況的時候,整個資料庫就處於完全阻塞狀態,無法進行任何讀寫操作,情況十分嚴重。
解決問題的方法,盡量避免長時間佔用寫鎖操作,如果有一些集合操作實在難以避免,可以考慮把這個集合放到一個單獨的 MongoDB 庫裡,因為 MongoDB 不同庫鎖是相互隔離的,分離集合可以避免某一個集合操作引發全域阻塞問題。
MongoDB最佳化與一些需要注意的細節