Go語言mgo


本文重點介紹mgo使用,僅簡單介紹mongodb。

mongodb特性

 

 
mongdb簡單介紹

注意: 上圖已經告知我們mongo不支持事務,在開發項目應用時,想要保證數據的完整性請考慮關系型數據庫(經典例子銀行轉賬)。 mongo提供了許多原子操作,比如文檔的保存,修改,刪除等,都是原子操作。所謂原子操作就是要么這個文檔保存到mongodb,要么沒有保存到mongodb,不會出現查詢到的文檔不完整的情況。

 

mgo簡介

mgo 是 mongodb 的 GO 語言驅動包。
mgo官網:http://labix.org/mgo

mgo使用

mgo方案一
package mgo import ( "flag" "gopkg.in/mgo.v2" "log" "study/conf" ) var session *mgo.Session var database *mgo.Database func init() { /*配置mongodb的josn文件,配置內容如下: { "hosts": "localhost", "database": "user" }*/ filename := flag.String("config", "./conf/config.json", "Path to configuration file") flag.Parse() config := &conf.ConfigurationDatabase{} config.Load(*filename) var err error dialInfo := &mgo.DialInfo{ Addrs: []string{config.Hosts}, Direct: false, Timeout: time.Second * 1, PoolLimit: 4096, // Session.SetPoolLimit } //創建一個維護套接字池的session session, err = mgo.DialWithInfo(dialInfo) if err != nil { log.Println(err.Error()) } session.SetMode(mgo.Monotonic, true) //使用指定數據庫 database = session.DB(config.Database) } func GetMgo() *mgo.Session { return session } func GetDataBase() *mgo.Database { return database } func GetErrNotFound() error { return mgo.ErrNotFound } 

這里的 session 能夠和 mongodb 集群中的所有Server通訊。

session設置的模式分別為:
  • Strong
    session 的讀寫一直向主服務器發起並使用一個唯一的連接,因此所有的讀寫操作完全的一致。
  • Monotonic
    session 的讀操作開始是向其他服務器發起(且通過一個唯一的連接),只要出現了一次寫操作,session 的連接就會切換至主服務器。由此可見此模式下,能夠分散一些讀操作到其他服務器,但是讀操作不一定能夠獲得最新的數據。
  • Eventual
    session 的讀操作會向任意的其他服務器發起,多次讀操作並不一定使用相同的連接,也就是讀操作不一定有序。session 的寫操作總是向主服務器發起,但是可能使用不同的連接,也就是寫操作也不一定有序。
//個人項目部分代碼 type User struct { ID bson.ObjectId `bson:"_id"` UserName string `bson:"username"` Summary string `bson:"summary"` Age int `bson:"age"` Phone int `bson:"phone"` PassWord string `bson:"password"` Sex int `bson:"sex"` Name string `bson:"name"` Email string `bson:"email"` } func Register(password string, username string) (err error) { con := mgo.GetDataBase().C("user") //可以添加一個或多個文檔 /* 對應mongo命令行 db.user.insert({username:"13888888888",summary:"code", age:20,phone:"13888888888"})*/ err = con.Insert(&User{ID: bson.NewObjectId(), UserName: username, PassWord: password}) return } func FindUser(username string) (User, error) { var user User con := mgo.GetDataBase().C("user") //通過bson.M(是一個map[string]interface{}類型)進行 //條件篩選,達到文檔查詢的目的 /* 對應mongo命令行 db.user.find({username:"13888888888"})*/ if err := con.Find(bson.M{"username": username}).One(&user); err != nil { if err.Error() != mgo.GetErrNotFound().Error() { return user, err } } return user, nil } 

通過find()可以進行單個或者全部的查詢,並且可以進行分頁處理。下面為簡單代碼展示:
con.Find(nil).Limit(5).Skip(0).All(&user)

package models

import ( "gopkg.in/mgo.v2/bson" "study/library/mgo" "time" ) type Diary struct { Uid bson.ObjectId `bson:"uid"` ID bson.ObjectId `bson:"_id"` CreatTime time.Time `bson:"creattime"` UpdateTime time.Time `bson:"updatetime"` Title string `bson:"title"` Content string `bson:"content"` Mood int `bson:'Mood"` Pic []string `bson:'pic'` } //通過uid查找本作者文章,並且顯示文章作者名字 func FindDiary(uid string) ([]interface{}, error) { con := mgo.GetDataBase().C("diary") // 其中的lookup功能可以實現類似於mysql中的join操作,方便於關聯查詢。 /*對應mongo命令行 db.diary.aggregate([{$match:{uid: ObjectId("58e7a1b89b5099fdc585d370")}}, {$lookup{from:"user",localField:"uid",foreignField:"_id",as:"user"}}, {$project:{"user.name":1,title:1,content:1,mood:1}}]).pretty() */ pipeline := []bson.M{ bson.M{"$match": bson.M{"uid": bson.ObjectIdHex(uid)}}, bson.M{"$lookup": bson.M{"from": "user", "localField": "uid", "foreignField": "_id", "as": "user"}}, bson.M{"$project": bson.M{"user.name": 1, "title": 1, "content": 1, "mood": 1, "creattime": 1}}, } pipe := con.Pipe(pipeline) var data []interface{} err := pipe.All(&data) if err != nil { return nil, err } return data, nil } func ModifyDiary(id, title, content string) (err error) { con := mgo.GetDataBase().C("diary") //更新 /*對應mongo命令行 db.diary.update({_id:ObjectId("58e7a1b89b5099fdc585d370")}, {$set:{title:"modify title",content:"modify content", updatetime:new Date()})*/ err = con.Update(bson.M{"_id": id}, bson.M{"$set": bson.M{"title": title, "content": content, "updatetime": time.Now().Add(8 * time.Hour)}}) return } 

mgo更新方法很多,如批量更新con.UpdateAll(selector, update),更新或插入數據con.Upsert(selector, update)

 
思路一會兒

 

mgo方案二

思考: session 會被全局使用,當在實際的程序中,我們可以開啟goroutine 來處理每個連接,多個goroutine 可以通過 session.Clone() 來創建或復用連接,使用完成之后通過 session.Close() 來關閉這個連接。當並發很高時,看起來可以提高效率。

下面部分代碼修改 :

import ( "flag" "gopkg.in/mgo.v2" "log" "study/conf" ) var session *mgo.Session var config *conf.ConfigurationDatabase func init() { filename := flag.String("config", "./conf/config.json", "Path to configuration file") flag.Parse() config = &conf.ConfigurationDatabase{} config.Load(*filename) var err error dialInfo := &mgo.DialInfo{ Addrs: []string{config.Hosts}, Direct: false, Timeout: time.Second * 1, PoolLimit: 4096, // Session.SetPoolLimit } session, err = mgo.DialWithInfo(dialInfo) if err != nil { log.Println(err.Error()) } session.SetMode(mgo.Monotonic, true) } type SessionStore struct { session *mgo.Session } //獲取數據庫的collection func (d * SessionStore) C(name string) *mgo.Collection { return d.session.DB(config.Database).C(name) } //為每一HTTP請求創建新的DataStore對象 func New SessionStore() * SessionStore { ds := & SessionStore{ session: session.Copy(), } return ds } func (d * SessionStore) Close() { d.session.Close() } func GetErrNotFound() error { return mgo.ErrNotFound } 

對查找進行了修改

func FindUser(username string) (User, error) { var user User ds := mgo.NewSessionStore() defer ds.Close() con := ds.C("user") if err := con.Find(bson.M{"username": username}).One(&user); err != nil { if err.Error() != mgo.GetErrNotFound().Error() { return user, err } } return user, nil } 

mgo方案一和二測試:
使用boom進行並發測試,並在每個 goroutine 里面sleep 5秒,這樣是讓連接暫時不釋放,就可以看到 mgo 方案二 會不斷創建新連接,方案一不會創建新連接。可以使用mongo shell 的db.serverStatus().connections來查看連接數。

mgo方案一測試連接數: 1000 並發:mongo 3個連接 5000 並發:mongo 3個連接。

mgo方案二測試連接數: 1000 並發:mongo 500多個連接 5000 並發:mongo 1400多個連接。

提示: mgo 默認連接池是 4096,在高並發下,如果每個 session都不調用 close(),會導致連接數會很快就達到 4096,並堵死其他請求,所以在使用clone() 或 copy()時 session 時一定要使用 defer close() 把連接關閉。啟用 maxPoolLimit 參數會限制總連接大小,當連接超過限制總數當前協程 等待,直到可以創建連接。

測試結果:mgo方案一和方案二在並發下,效率差不多。

 
為什么

可能性,由於數據少或者處理的單個mongo無法看出效果。
由於目前自己項目只使用了一個mongo,后期使用多個mongo進行或在大量數據下測試。如果大家有什么好的建議,提出來進行學習思考。
推薦學習:
http://goinbigdata.com/how-to-build-microservice-with-mongodb-in-golang/
官方博客詳講了mgo並發處理,如下:
https://www.mongodb.com/blog/post/running-mongodb-queries-concurrently-with-go


作者:WuXiao_
鏈接:https://www.jianshu.com/p/13b7f4630670
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。





github:demo:https://github.com/goinggo/beego-mgo


免責聲明!

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



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