Java的MongoDB驅動及讀寫策略


網上看見一篇博文,詳細講了MongoDB讀寫策略,將來生產會遇到類似的問題,轉來備查。
指定新mongo實例:
Mongo m = new Mongo();
Mongo m = new Mongo( "localhost" );
Mongo m = new Mongo( "localhost" , 27017 );
// or, to connect to a replica set, supply a seed list of members
Mongo m = new Mongo(Arrays.asList(new ServerAddress("localhost", 27017),
                                      new ServerAddress("localhost", 27018),
                                      new ServerAddress("localhost", 27019)));
然后發起連接(必須指定數據庫名,可以不存在)
DB db = m.getDB( "mydb" );
 
注意Mongo已經實現了連接池,並且是線程安全的。
 
大部分用戶使用mongodb都在安全內網下,但如果將mongodb設為安全驗證模式,就需要在客戶端提供用戶名和密碼:
boolean auth = db.authenticate(myUserName, myPassword);
 
獲取集合(collection)的名稱列表(類似show databases):
Set<String> colls = db.getCollectionNames();
 
獲取一個集合(以便增刪改查操作):
DBCollection coll = db.getCollection("testCollection")
 
-------------------------------------------------------------
先假設要插入的json數據如下:
{
   "name" : "MongoDB",
   "type" : "database",
   "count" : 1,
   "info" : {
               x : 203,
               y : 102
             }
}
 
將其插入數據庫:
        BasicDBObject doc = new BasicDBObject();
 
        doc.put("name", "MongoDB");
        doc.put("type", "database");
        doc.put("count", 1);
 
        BasicDBObject info = new BasicDBObject();
 
        info.put("x", 203);
        info.put("y", 102);
 
        doc.put("info", info);
 
        coll.insert(doc);
 
-------------------------------------------------------------
設定write concern,以便操作失敗時得到提示:
m.setWriteConcern(WriteConcern.SAFE);
-------------------------------------------------------------
查找一個/第一個記錄:
DBObject myDoc = coll.findOne();
System.out.println(myDoc);
 
注意:屬性名不能以下划線或者美元符號開始,mongodb自己保留。
 
獲取總記錄數:
System.out.println(coll.getCount());
 
使用游標操作查詢結果:
        DBCursor cursor = coll.find();
        try {
            while(cursor.hasNext()) {
                System.out.println(cursor.next());
            }
        } finally {
            cursor.close();
        }
 
條件查詢:
        BasicDBObject query = new BasicDBObject();
 
        query.put("i", 71);
 
        cursor = coll.find(query);
 
        try {
            while(cursor.hasNext()) {
                System.out.println(cursor.next());
            }
        } finally {
            cursor.close();
        }
 
如果想使用shell里的類似以下語句的功能:
db.things.find({j: {$ne: 3}, k: {$gt: 10} });
在java驅動里,{$ne: 3}也是一個普通的DBObject:
        BasicDBObject query = new BasicDBObject();
 
        query.put("j", new BasicDBObject("$ne", 3));
        query.put("k", new BasicDBObject("$gt", 10));
 
        cursor = coll.find(query);
 
        try {
            while(cursor.hasNext()) {
                System.out.println(cursor.next());
            }
        } finally {
            cursor.close();
        }
 
條件查詢一批數據:
以下是查詢i > 50的記錄:
        query = new BasicDBObject();
 
        query.put("i", new BasicDBObject("$gt", 50));  // e.g. find all where i > 50
 
        cursor = coll.find(query);
 
        try {
            while(cursor.hasNext()) {
                System.out.println(cursor.next());
            }
        } finally {
            cursor.close();
        }         
以下是查詢20 < i <= 30的記錄:
        query = new BasicDBObject();
 
        query.put("i", new BasicDBObject("$gt", 20).append("$lte", 30));  // i.e.   20 < i <= 30
 
        cursor = coll.find(query);
 
        try {
            while(cursor.hasNext()) {
                System.out.println(cursor.next());
            }
        } finally {
            cursor.close();
        }
 
-------------------------------------------------------------
創建索引:
指定collection和要index的列即可,1代表升序,-1代表降序。
coll.createIndex(new BasicDBObject("i", 1));  // create index on "i", ascending
 
獲取索引列表:
List<DBObject> list = coll.getIndexInfo();
          for (DBObject o : list) {
            System.out.println(o);
        }
 
-------------------------------------------------------------
數據庫管理相關的命令:
 
獲取數據庫名稱列表:
m.getDatabaseNames()
 
刪除數據庫:
m.dropDatabase("my_new_db");
 
-------------------------------------------------------------
聚合函數:使用DBCollection.aggregate()產生聚合任務:
http://www.mongodb.org/display/DOCS/Using+The+Aggregation+Framework+with+The+Java+Driver
 
// create our pipeline operations, first with the $match
DBObject match = new BasicDBObject("$match", new BasicDBObject("type", "airfare") );
 
// build the $projection operation
DBObject fields = new BasicDBObject("department", 1);
fields.put("amount", 1);
fields.put("_id", 0);
DBObject project = new BasicDBObject("$project", fields );
 
// Now the $group operation
DBObject groupFields = new BasicDBObject( "_id", "$department");
groupFields.put("average", new BasicDBObject( "$avg", "$amount"));
DBObject group = new BasicDBObject("$group", groupFields);
 
// run aggregation
AggregationOutput output = collection.aggregate( match, project, group );
 
返回結果是一個AggregationOutput對象,可以用以下方式獲取其中信息:
public Iterable<DBObject> results()
public CommandResult getCommandResult
public DBObject getCommand()
示例:
System.out.println(output.getCommandResult());
 
{
"serverUsed" : "/127.0.0.1:27017" ,
"result" : [
{"_id" : "Human Resources" , "average" : 74.91735537190083} ,
{"_id" : "Sales" , "average" : 72.30275229357798} ,
{"_id" : "Engineering" , "average" : 74.1}
] ,
"ok" : 1.0
}
-------------------------------------------------------------
使用DBObject存取對象:
http://www.mongodb.org/display/DOCS/Java+-+Saving+Objects+Using+DBObject
 
假設一個類叫做twitter,存儲前必須實現DBObject接口:
public class Tweet implements DBObject {
    /* ... */
}
 
然后就可以存了:
Tweet myTweet = new Tweet();
myTweet.put("user", userId);
myTweet.put("message", msg);
myTweet.put("date", new Date());
collection.insert(myTweet);
 
一個文檔從數據庫取出時,自動被轉為DBObject。如果想取出時轉為需要的類,可以先調用DBCollection.setObjectClass(),然后再進行類型強制轉換:
collection.setObjectClass(Tweet.class);
Tweet myTweet = (Tweet)collection.findOne();
 
如果想要改變Tweet類的屬性,可以改完后再存入:
Tweet myTweet = (Tweet)collection.findOne();
myTweet.put("message", newMsg);
collection.save(myTweet);
 
-------------------------------------------------------------
JAVA驅動的並發:
http://www.mongodb.org/display/DOCS/Java+Driver+Concurrency
 
mongodb的java驅動是線程安全的,如果是在web服務環境下,應該創建單例的mongo對象,用這個對象處理每一個請求。mongo對象維護一個內部連接池(默認大小為10),對於每個請求(查找插入等),java線程會從線程池取一個連接,執行操作,然后釋放連接——這意味着每次操作所使用的連接很有可能是不同的。
------------------------------
在復制(replica)模式下,如果設置slaveOK選項為on,那么讀操作會被均勻的分布到各個slave上。這意味着對於同一個線程,一個寫操作后緊跟着的一個讀操作,有可能被發送到不同的服務器上(寫操作發送到master上,讀操作發送到slave上),這樣讀操作有可能不會立刻反映出上一個寫操作的數據(因為主從的異步性)。
如果你想要確保在一個session中完整的一致性(例如在一個http請求中),你可能希望java驅動是用同一個socket連接,這時你可以通過使用"consistent request"來達到目的——在操作前后分別調用requestStart()和requestDone()。
 
DB db...;
db.requestStart();
try {
   //Ensures that a connection exists for the "consistent request"
   db.requestEnsureConnection();
 
   code....
} finally {
   db.requestDone();
}
------------------------------
在單獨寫操作上的WriteConcern選項:
默認情況下,每次寫操作后,連接就被釋放回連接池——此時你調用getLastError()是沒用的。
所以可以采用兩種方式:
1,使用類似WriteConcern.SAFE這樣的寫策略來代替默認策略,這樣java驅動會自動首先調用getLastError(),然后才將連接放回連接池。
 
DBCollection coll...;
coll.insert(..., WriteConcern.SAFE);
 
2,采用上述的requestStart()和requestDone()方式來維持連接不被釋放,中間調用getLastError()獲取錯誤信息。
 
DB db...;
DBCollection coll...;
db.requestStart();
try {
   coll.insert(...);
   DBObject err = db.getLastError();
} finally {
   db.requestDone();
}
 
這兩種方式等價。
 
-------------------------------------------------------------
java類型:
http://www.mongodb.org/display/DOCS/Java+Types
----------------------------
Object id被用來自動生成唯一的ID:
ObjectId id = new ObjectId();
ObjectId copy = new ObjectId(id);
 
----------------------------
正則表達式:
java驅動使用JDK的java.util.regex.Pattern表達正則:
Pattern john = Pattern.compile("joh?n", CASE_INSENSITIVE);
BasicDBObject query = new BasicDBObject("name", john);
// finds all people with "name" matching /joh?n/i
DBCursor cursor = collection.find(query);
 
----------------------------
java驅動使用JDK的 java.util.Date表示日期時間:
Date now = new Date();
BasicDBObject time = new BasicDBObject("ts", now);
collection.save(time);
 
----------------------------
com.mongodb.DBRef被用來表示數據庫引用(官方一般建議手動引用):
DBRef addressRef = new DBRef(db, "foo.bar", address_id);
DBObject address = addressRef.fetch();
 
DBObject person = BasicDBObjectBuilder.start()
    .add("name", "Fred")
    .add("address", addressRef)
    .get();
collection.save(person);
 
DBObject fred = collection.findOne();
DBRef addressObj = (DBRef)fred.get("address");
addressObj.fetch()
 
----------------------------
二進制數據:
java中的字節數組會自動被包裝成二進制數據存入數據庫。
二進制類可以被用來表示二進制對象,這可以用來取一個自定義類型的字節。
 
----------------------------
時間戳數據(BSONTimestamp對象):
時間戳數據是一個mongoDB使用的特殊對象,表示一個(以秒為單位的時間,自增ID)的鍵值對,這被用於主從復制的操作日志中。
 
----------------------------
代碼對象:
用於表示javascript代碼,例如保存可執行的方法到system.js集合中。
Code和CodeWScope類用來表示這種數據。
注意有些方法(比如map/reduce)接收字符串,但是在驅動中將其包裝為代碼對象。
 
----------------------------
嵌套對象:
在javascript里面這樣一個json文檔:
{
    "x" : {
        "y" : 3
    }
}
在java中對應的形式是:
BasicDBObject y = new BasicDBObject("y", 3);
BasicDBObject x = new BasicDBObject("x", y);
 
----------------------------
數組:
java中所有實現List接口的都被以數組形式在數據庫中保存:
所以如果想保存這樣一個json:
{
    "x" : [
        1,
        2,
        {"foo" : "bar"},
        4
    ]
}
在java中可以這么做:
ArrayList x = new ArrayList();
x.add(1);
x.add(2);
x.add(new BasicDBObject("foo", "bar"));
x.add(4);
BasicDBObject doc = new BasicDBObject("x", x);
-------------------------------------------------------------
在java驅動中的優先讀取策略(Read Preferences)和節點標記(Tagging)
http://www.mongodb.org/display/DOCS/Read+Preferences+and+Tagging+in+The+Java+Driver
用來允許應用程序開發者能將讀寫操作指定在某個主從集合的成員節點上。
2.2版本在節點標記(node tagging)帶來些新的東西讓你能更好的控制你的數據的讀寫。
java驅動的2.9.0集成了mongodb2.2提供的所有新特性。
----------------------------
優先讀取策略(Read Preferences):
用於提供客戶端程序選擇那個節點來讀取,有以下五個選擇:
PRIMARY : 默認方式,從主節點讀取,如果主節點不可用則拋出異常,無法與標簽(tags)一起使用。
PRIMARY PREFERRED : 優先讀取主節點,失敗的話從副節點讀取。
SECONDARY : 從副節點讀取,失敗則拋異常。
SECONDARY PREFERRED : 優先讀取副節點,失敗則從主節點讀取。
NEAREST : 從最近的節點讀取,“最近”的定義為ping的響應時間最短(也得滿足15毫秒以內)。
 
java實現:類ReadPreference的工廠模式可以創建對應上述五種策略的對象:
ReadPreference.primary();
ReadPreference.primaryPreferred();
ReadPreference.secondary();
ReadPreference.secondaryPreferred();
ReadPreference.nearest();
 
例如:假設我們的應用需要保持強一致性(寫入的數據立即可以被讀出),但是我們又希望萬一主庫掛掉以后,從庫依然可以讀取。這種情況下,我們需要選擇PRIMARY PREFERRED模式:
ReadPreference preference = ReadPreference.primaryPreferred();
DBCursor cur = new DBCursor(collection, query, null, preference);
 
java驅動保持着各個節點的狀態信息(每隔一段時間ping一次所有節點),在這個例子中,java驅動會檢測到主庫的掛掉,從而將讀操作指向從節點。
----------------------------
節點標記(Tags):
在mongoDB2.0之后,主從集群中的每一個節點都可以被標記一個描述,稱為tags。可以用來標記一個節點的位置,和在集群中的從屬關系或者特性。這可以讓你的應用程序從指定節點中讀寫。
 
例子:
假設我們要運行一個三節點的主從集群,三個節點分別在三個不同地理位置的數據中心當中。我們希望確保我們的數據在災難中也可以恢復,所以我們對每一個節點標記其地理位置,配置文件樣例如下:
foo:SECONDARY> rs.conf()
{
    "_id":"foo",
    "version":103132,
     "members":[
         {
              "_id":0,
               "host":"localhost:27017",
               "priority":10,
               "tags":{
                   "datacenter":"Los Angeles",
                    "region":"US_West"
               }
          },
          {
              "_id":1,
               "host":"localhost:27018",
               "tags":{
                   "datacenter":"San Jose",
                    "region":"US_West"
               }
          },
          {
              "_id":2,
               "host":"localhost:27019",
               "tags":{
                   "datacenter":"Richmond",
                    "region":"US_Eest"
               }
          }
     ],
     "settings":{
         "getLastErrorModes":{
              "DRSafe":{
                   "region":2
               }
          }
     }
}
foo:SECONDARY>
 
注意上面settings字段,我們定義了一個新的getLastErrorModes對象,鍵為DRSafe。當我們客戶端采用此錯誤模式作為WriteConcern的時候,它會使寫操作在完成前復制到至少兩個節點上。下面是使用的例子:
//使用自定義的getLastErrorMode創建WriteConcern
WriteConcern concern = new WriteConcern("DRSafe");
//使用自定義的WriteConcern進行寫操作
coll.insert(new BasicDBObject("name", "simple doc"), concern);
 
----------------------------
在優先讀取策略(Read Preferences)中使用節點標記(Tags):
 
假如我們想要將讀請求發送到最近的節點上以便減少請求延時:
DBObject query = new BasicDBObject("name", "simple doc")
DBObject result = coll.findOne(query, null, ReadPreference.nearest());
這樣java驅動會自動將讀請求發送到ping值最小的節點(也有可能是主節點)。
 
但是,如果我們的java驅動可以確定自己的請求發送源位置,那么就可以明確指定將讀請求發送到距離最近的數據中心。再看上面的例子,假如這個讀請求來自南加利福尼亞,我們就明確指定這個讀請求到Los Angeles數據中心:
// initialize a properly tagged read preference
ReadPreference tagged_pref = ReadPreference.secondaryPreferred(new BasicDBObject("datacenter", "Los Angeles"));
// include the tagged read preference in this request}}
DBObject result = coll.findOne(}}
new BasicDBObject("name", "simple doc"), null, tagged_pref);
 
下面的例子指定多個tag,如果讀請求在Los Angeles失敗,則發送到"US_West"區域的某個節點:
// read from either LA or US_West
DBObject tagSetOne = new BasicDBObject("datacenter", "Los Angeles"):
DBObject tagSetTwo = new BasicDBObject("region", "US_West");
ReadPreference pref = ReadPreference.primaryPreferred(tagSetOne, tagSetTwo);
 
下面的例子同樣指定多個tag,首先請求"datacenter=Los Angeles"且"rack=1"的節點,如果失敗則查找"region=US_West"的節點
// read from either LA or US_West
DBObject tagSetOne = new BasicDBObject("datacenter", "Los Angeles");
tagSetOne.put("rack", "1");
DBObject tagSetTwo = new BasicDBObject("region", "US_West");
ReadPreference pref = ReadPreference.primaryPreferred(tagSetOne, tagSetTwo);
 
優先讀取策略(Read Preferences)可以在operation, collection, DB, Mongo, MongoOptions, MongoURI各個級別來設置,而且設置會以slaveOK和WriteConcer類似的方式來繼承。
優先讀取策略(Read Preferences)在支持主從復制的服務器上(1.6+)都可以使用。
在優先讀取策略(Read Preferences)中使用節點標記(Tags)在所有支持節點標記的服務器(2.0+)都可以使用。
在分片(shard)上使用節點標記(Tags),必須是2.2+版本的服務器才可以。
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM