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