如何集成MongoDB驅(qū)動(dòng)包
推薦使用Maven管理包依賴關(guān)系:
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>2.13.2</version>
</dependency>
如何連接
主要有兩種連接方式
- 單機(jī)直連
- Replica Set連接,自動(dòng)發(fā)現(xiàn)Primary主機(jī),在多集群情況下強(qiáng)烈推薦使用這種連接方式
示例代碼
//單機(jī)直連
MongoClient mongoClient = new MongoClient( "localhost" , 27017 );
//Replica Set連接
MongoClientOptions options = MongoClientOptions.builder().autoConnectRetry(true).connectTimeout(60000).build();
MongoCredential credential = MongoCredential.createMongoCRCredential("username", "dbname", "password".toCharArray());
MongoClient mongoClient = new MongoClient(
Arrays.asList(
new ServerAddress("mongoserver1", 34001),
new ServerAddress("mongoserver2", 34001),
new ServerAddress("mongoserver3", 34001)
), Arrays.asList(credential), options);
//Replica Set連接 uri寫法
String connectionString = "mongodb://username:password@mongoserver1:34001,mongoserver2:34001,mongoserver3:34001/dbname?AutoConnectRetry=true";
MongoClientURI mongoClientURI = new MongoClientURI(connectionString);
MongoClient mongoClient = new MongoClient(mongoClientURI);
//spring boot 請(qǐng)?jiān)趐roperties里邊使用uri方式進(jìn)行連接
spring.data.mongodb.uri=mongodb://username:password@mongoserver1:34001,mongoserver2:34001,mongoserver3:34001/dbname?AutoConnectRetry=true
spring.data.mongodb.repositories.enabled=true
MongoDB Update的正確用法
通常一個(gè)文檔只會(huì)有一小部分需要更新,如果我們把新文檔做為update方法的參數(shù)顯得很啰嗦很麻煩,特別是文檔比較復(fù)雜的時(shí)候。而利用原子的更新修改器可以使得這種部分的更新極為方便高效。
更新修改器是種特殊的鍵,用來指定復(fù)雜的更新操作,比如調(diào)整,增加或者刪除鍵,還可能是操作數(shù)組或者內(nèi)嵌文檔
$set用來指定一個(gè)鍵的值.如果這個(gè)鍵存在,就修改它;不存在,就創(chuàng)建它。
> db.name.find()
{ "_id" : ObjectId("505a5925f67c1b9a341caefb"), "fname" : "jeff", "lname" : "jiang" }
> db.name.update({"_id" : ObjectId("505a5925f67c1b9a341caefb")},{$set:{"fname" : "jeffery"}})
> db.name.find()
{ "_id" : ObjectId("505a5925f67c1b9a341caefb"), "fname" : "jeffery", "lname" : "jiang" }
# 可以看到,原文檔的"fname"是存在的,所以$set修改器只修改了它的值("jeff"-->"jeffery")
> db.name.update({"_id" : ObjectId("505a5925f67c1b9a341caefb")},{$set:{age:23}})
> db.name.find()
{ "_id" : ObjectId("505a5925f67c1b9a341caefb"), "age" : 23, "fname" : "jeffery", "lname" : "jiang" }
示例代碼
db.getCollection("restaurants").updateOne(new Document("name", "Juni"), new Document("$set", new Document("cuisine", "American (New)")) .append("$currentDate", new Document("lastModified", true)))
如何開啟讀寫分離
默認(rèn)情況下驅(qū)動(dòng)是從Replica Set 集群中的 Primary 上進(jìn)行讀寫的,應(yīng)用可以在讀多寫少的場(chǎng)景下開啟讀寫分離,提高效率。
這里介紹下 readPreference 這個(gè)參數(shù):###
- primary
主節(jié)點(diǎn),默認(rèn)模式,讀操作只在主節(jié)點(diǎn),如果主節(jié)點(diǎn)不可用,報(bào)錯(cuò)或者拋出異常。 - primaryPreferred
首選主節(jié)點(diǎn),大多情況下讀操作在主節(jié)點(diǎn),如果主節(jié)點(diǎn)不可用,如故障轉(zhuǎn)移,讀操作在從節(jié)點(diǎn)。 - secondary
從節(jié)點(diǎn),讀操作只在從節(jié)點(diǎn), 如果從節(jié)點(diǎn)不可用,報(bào)錯(cuò)或者拋出異常。 - secondaryPreferred
首選從節(jié)點(diǎn),大多情況下讀操作在從節(jié)點(diǎn),特殊情況(如單主節(jié)點(diǎn)架構(gòu))讀操作在主節(jié)點(diǎn)。 - nearest
最鄰近節(jié)點(diǎn),讀操作在最鄰近的成員,可能是主節(jié)點(diǎn)或者從節(jié)點(diǎn)
示例代碼
//uri寫法
mongodb://username:password@mongoserver1:34001,mongoserver2:34001,mongoserver3:34001/dbname?AutoConnectRetry=true&readPreference=secondaryPreferred
//java寫法
MongoClientOptions options = MongoClientOptions.builder().readPreference(ReadPreference.secondaryPreferred()).build();
MongoClient mongoClient = new MongoClient(
Arrays.asList(
new ServerAddress("mongoserver1", 34001),
new ServerAddress("mongoserver2", 34001),
new ServerAddress("mongoserver3", 34001)
), Arrays.asList(credential), options);
在Mongodb中最多能創(chuàng)建多少集合?
默認(rèn)情況下,MongoDB 的每個(gè)數(shù)據(jù)庫的命名空間保存在一個(gè) 16MB 的 .ns 文件中,平均每個(gè)命名占用約 628 字節(jié),也即整個(gè)數(shù)據(jù)庫的命名空間的上限約為 24000。
每一個(gè)集合、索引都將占用一個(gè)命名空間。所以,如果每個(gè)集合有一個(gè)索引(比如默認(rèn)的 _id 索引),那么最多可以創(chuàng)建 12000 個(gè)集合。如果索引數(shù)更多,則可創(chuàng)建的集合數(shù)就更少了。同時(shí),如果集合數(shù)太多,一些操作也會(huì)變慢。甚至使得MongoDB集群無法服務(wù)的情況發(fā)生!
MongoDB有傳統(tǒng)數(shù)據(jù)庫的事務(wù)和事務(wù)回滾么?
沒有,請(qǐng)不要把它當(dāng)成關(guān)系型數(shù)據(jù)庫來使用,對(duì)于MongoDB集群來說,默認(rèn)情況下數(shù)據(jù)也不是強(qiáng)一致性的,而是最終一致性。如果對(duì)數(shù)據(jù)一致性比較敏感建議更改WriteConcern級(jí)別,但后果是降低了性能,請(qǐng)酌情考慮。
MongoDB有命名規(guī)范么?
- 不能是空字符串
- 不能含有.、''、*、/、\、<、>、:、?、$、\0。建議只使用ASCII碼中字母和數(shù)字
- 數(shù)據(jù)庫名區(qū)分大小寫
- 數(shù)據(jù)庫名長(zhǎng)度最多為64字節(jié)
- 集合名不能包含\0字符,這個(gè)字符表示集合名的結(jié)束
- 集合名不能是空字符串""
- 集合名不能使用系統(tǒng)集合的保留前綴"system."
- 集名名中不建議包含字符'$',雖然很多驅(qū)動(dòng)程序可以支持包含此字符的集合名
MongoDB有系統(tǒng)保留庫名么?
- admin
- local
- config
MongoDB有連接池么?
MongoDB驅(qū)動(dòng)中其實(shí)已經(jīng)是一個(gè)現(xiàn)成的連接池了,而且線程安全。這個(gè)內(nèi)置的連接池默認(rèn)初始了100個(gè)連接,每一個(gè)操作(增刪改查等)都會(huì)獲取一個(gè)連接,執(zhí)行操作后釋放連接。
【題外話】請(qǐng)務(wù)必記得關(guān)閉資源,并且設(shè)置合理的池子連接數(shù)和超時(shí)時(shí)間。
內(nèi)置連接池有多個(gè)重要參數(shù),分別是:###
- connectionsPerHost:每個(gè)主機(jī)答應(yīng)的連接數(shù)(每個(gè)主機(jī)的連接池大?。?dāng)連接池被用光時(shí),會(huì)被阻塞住,默認(rèn)值為100
- threadsAllowedToBlockForConnectionMultiplier:線程隊(duì)列數(shù),它和上面connectionsPerHost值相乘的結(jié)果就是線程隊(duì)列最大值。如果連接線程排滿了隊(duì)列就會(huì)拋出“Out of semaphores to get db”錯(cuò)誤,默認(rèn)值為5,則最多有500個(gè)線程可以等待獲取連接
- maxWaitTime: 被阻塞線程從連接池獲取連接的最長(zhǎng)等待時(shí)間(ms)。默認(rèn)值為120,000
- connectTimeout:在建立(打開)套接字連接時(shí)的超時(shí)時(shí)間(ms)。默認(rèn)值為10,000
- socketTimeout:套接字超時(shí)時(shí)間(ms)。默認(rèn)值為0,無限制(infinite)
- autoConnectRetry:這個(gè)控制是否在連接時(shí),會(huì)自動(dòng)重試,2.13驅(qū)動(dòng)已經(jīng)【廢棄】,請(qǐng)使用connectTimeout代替它
連接池的MaximumPoolSize要有個(gè)合理值,否則這個(gè)值數(shù)據(jù)量的連接都被占用,后面再有新的連接創(chuàng)建時(shí)就要等待了,而不能超出池上限新建連接。除此之外還要設(shè)置合理的連接等待,連接超時(shí)時(shí)間,以防止一個(gè)連接占用時(shí)間過長(zhǎng),影響其它連接請(qǐng)求。
connectTimeout 和 socketTimeout 的區(qū)別:###
一次完整的請(qǐng)求包括三個(gè)階段:
- 建立連接
- 數(shù)據(jù)傳輸
- 斷開連接
如果與服務(wù)器(這里指數(shù)據(jù)庫)請(qǐng)求建立連接的時(shí)間超過ConnectTimeout,就會(huì)拋 ConnectionTimeOutException,即服務(wù)器連接超時(shí),沒有在規(guī)定的時(shí)間內(nèi)建立連接。
如果與服務(wù)器連接成功,就開始數(shù)據(jù)傳輸了。
如果服務(wù)器處理數(shù)據(jù)用時(shí)過長(zhǎng),超過了SocketTimeOut,就會(huì)拋出SocketTimeOutExceptin,即服務(wù)器響應(yīng)超時(shí),服務(wù)器沒有在規(guī)定的時(shí)間內(nèi)返回給客戶端數(shù)據(jù)。
所以這該死的超時(shí)該怎么配?
這里有一份國(guó)外寫的關(guān)于超時(shí)的建議:
http://blog.mongolab.com/2013/10/do-you-want-a-timeout/
上文給出的通常情況下:connectTimeout=5000,socketTimeout=0
附錄
http://api.mongodb.org/java/2.13/com/mongodb/MongoClientOptions.Builder.html
http://api.mongodb.org/java/2.13/com/mongodb/MongoClientURI.html
關(guān)于WriteConcern
MongoDB提供了一個(gè)配置參數(shù):write concern 來讓用戶自己衡量性能和寫安全。分布式數(shù)據(jù)庫中這樣的參數(shù)比較常見,記得Cassandra中也有一個(gè)類似參數(shù),不過那個(gè)好像是要寫入幾個(gè)節(jié)點(diǎn)返回成功。其實(shí)道理都一樣分布式的集群環(huán)境考慮到性能因素不能確保每個(gè)成員都寫入后在返回成功,所以只能交給用戶根據(jù)實(shí)際場(chǎng)景衡量。
- Unacknowledged
這個(gè)級(jí)別也屬于比較低的級(jí)別,以前這個(gè)級(jí)別是驅(qū)動(dòng)配置的默認(rèn)級(jí)別,不過后來調(diào)整成Acknowledged級(jí)別。在這個(gè)級(jí)別下,這個(gè)驅(qū)動(dòng)會(huì)根據(jù)當(dāng)前系統(tǒng)的網(wǎng)絡(luò)配置進(jìn)行網(wǎng)絡(luò)問題的檢測(cè),不等待Mongd的返回。代碼測(cè)試:本地網(wǎng)絡(luò)問題是否有異常?本地網(wǎng)絡(luò)無問題是遠(yuǎn)程server問題是否異常? - Acknowledged
這個(gè)級(jí)別算是中等級(jí)別的配置,這個(gè)級(jí)別能夠拿到mongod的返回信息:dupkey Error,以及一些其他的問題。現(xiàn)在這個(gè)級(jí)別是驅(qū)動(dòng)的默認(rèn)級(jí)別,估計(jì)是10gen公司發(fā)現(xiàn)好多人評(píng)價(jià)Mongodb不靠譜后改的。一般系統(tǒng)這個(gè)級(jí)別也就夠用了。由于默認(rèn)級(jí)別是Acknowledged,內(nèi)部用getLastError方法檢查是否寫入成功的時(shí)候是也不用設(shè)置任何參數(shù),對(duì)與Replset來說可以在配置中進(jìn)行g(shù)etLastErrorDefaults的配置,如果沒有的話默認(rèn)則是Master收到就ok。 - Journaled
等到操作記錄到Journal Log中才返回操作結(jié)果,也就是下一次JournaledLog提交。這種情況可以容忍服務(wù)器突然宕機(jī),斷電等意外的恢復(fù)。出去上邊的配置還要在啟動(dòng)mongod的時(shí)候加上journaling 參數(shù)確??梢允褂谩ommitlog提交間隔時(shí)間是可以配置的,單磁盤設(shè)備(physical volume, RAID device, or LVM volume)每100ms提交一次,和數(shù)據(jù)文件刷出相同頻率,日志和數(shù)據(jù)分開磁盤設(shè)備的30ms提交一次。在插入數(shù)據(jù)是如果使用{j:true}則會(huì)縮短到已配置的默認(rèn)設(shè)置1/3的時(shí)間。 - Replica Acknowledged
在副本集中如果w設(shè)置為2的話則至少已經(jīng)吸入到一個(gè)secondary中,我猜測(cè)寫入secondary這個(gè)級(jí)別是Acknowledged級(jí)別,majority是多個(gè)secondary已經(jīng)寫入。如果手賤設(shè)置w參數(shù)大于replset中需要復(fù)制的secondarys的話,操作就一直等待直到達(dá)到已寫入數(shù)據(jù)的服務(wù)器數(shù)量符合要求,也可以設(shè)置timeout值來指明最長(zhǎng)等待時(shí)間。{ getLastError: 1, w: 2, wtimeout:5000 }
附錄
http://my.oschina.net/u/217548/blog/195995
http://docs.mongodb.org/manual/core/write-concern/
MongoDB的鎖機(jī)制
MongoDB的鎖機(jī)制和一般關(guān)系數(shù)據(jù)庫如 MySQL(InnoDB), Oracle 有很大的差異,InnoDB 和 Oracle 能提供行級(jí)粒度鎖,而 MongoDB v2 只能提供庫級(jí)粒度鎖,這意味著當(dāng) MongoDB 一個(gè)寫鎖處于占用狀態(tài)時(shí),其它的讀寫操作都得干等。
初看起來庫級(jí)鎖在大并發(fā)環(huán)境下有嚴(yán)重的問題,但是 MongoDB 依然能夠保持大并發(fā)量和高性能,這是因?yàn)?MongoDB 的鎖粒度雖然很粗放,但是在鎖處理機(jī)制和關(guān)系數(shù)據(jù)庫鎖有很大差異,主要表現(xiàn)在:
- MongoDB 沒有完整事務(wù)支持,操作原子性只到單個(gè) document 級(jí)別,所以通常操作粒度比較?。?/li>
- MongoDB 鎖實(shí)際占用時(shí)間是內(nèi)存數(shù)據(jù)計(jì)算和變更時(shí)間,通常很快;
- MongoDB 鎖有一種臨時(shí)放棄機(jī)制,當(dāng)出現(xiàn)需要等待慢速 IO 讀寫數(shù)據(jù)時(shí),可以先臨時(shí)放棄,等 IO 完成之后再重新獲取鎖。
通常不出問題不等于沒有問題,如果數(shù)據(jù)操作不當(dāng),依然會(huì)導(dǎo)致長(zhǎng)時(shí)間占用寫鎖,比如下面提到的前臺(tái)建索引操作,當(dāng)出現(xiàn)這種情況的時(shí)候,整個(gè)數(shù)據(jù)庫就處于完全阻塞狀態(tài),無法進(jìn)行任何讀寫操作,情況十分嚴(yán)重。
解決問題的方法,盡量避免長(zhǎng)時(shí)間占用寫鎖操作,如果有一些集合操作實(shí)在難以避免,可以考慮把這個(gè)集合放到一個(gè)單獨(dú)的 MongoDB 庫里,因?yàn)?MongoDB 不同庫鎖是相互隔離的,分離集合可以避免某一個(gè)集合操作引發(fā)全局阻塞問題。
建索引導(dǎo)致數(shù)據(jù)庫阻塞
上面提到了 MongoDB 庫級(jí)鎖的問題,建索引就是一個(gè)容易引起長(zhǎng)時(shí)間寫鎖的問題,MongoDB 在前臺(tái)建索引時(shí)需要占用一個(gè)寫鎖(而且不會(huì)臨時(shí)放棄),如果集合的數(shù)據(jù)量很大,建索引通常要花比較長(zhǎng)時(shí)間,特別容易引起問題。
解決的方法很簡(jiǎn)單,MongoDB 提供了兩種建索引的訪問,一種是 background 方式,不需要長(zhǎng)時(shí)間占用寫鎖,另一種是非 background 方式,需要長(zhǎng)時(shí)間占用鎖。使用 background 方式就可以解決問題。
例如,為超大表 posts 建立索引
//千萬不用使用
db.posts.ensureIndex({user_id: 1})
//而應(yīng)該使用
db.posts.ensureIndex({user_id: 1}, {background: 1})
哪些操作會(huì)對(duì)數(shù)據(jù)庫產(chǎn)生鎖
操作 | 鎖類型 |
---|---|
Issue a query | Read lock |
Get more data from a cursor | Read lock |
Insert data | Write lock |
Remove data | Write lock |
Update data | Write lock |
Map-reduce | Read lock and write lock, unless operations are specified as non-atomic. Portions of map-reduce jobs can run concurrently. |
Create an index | Building an index in the foreground, which is the default, locks the database for extended periods of time. |
db.eval() | Write lock. db.eval() blocks all other JavaScript processes. |
eval | Write lock. If used with the nolock lock option, the eval option does not take a write lock and cannot write data to the database. |
aggregate() | Read lock |
附錄
https://ruby-china.org/topics/20128
http://docs.mongodb.org/v2.6/faq/concurrency/
http://docs.mongodb.org/v2.6/reference/method/db.collection.ensureIndex/#db.collection.ensureIndex