golang服务与mongodb交互,以及驱动mgo源码浅析

公司大数据部门有一个需求,要将所有的交易数据进行落地,以便进行分析和价格预测等等。

具体场景:
1,写多读少
2,数据量庞大,并且每天日益增长

为什么选mongodb:
1,mongodb是弱数据结构模式,每个文档之间的结构互不影响,日后如果需要增加字段直接增加即可,对集合不会产生任何影响。
2,分布式,面对庞大的数据量,mongo原生支持sharding存储。
3,无事务和join的查询需求

下面使用go来和mongodb进行交互:
mongo serer是3.6版本,安装过程略过。
go的mongo驱动用的是”https://github.com/go-mgo/mgo

.
.
.

var globalSession = new(mgo.Session)

func MongoInit() error {
    var err error
    var url string

    mongoConfig := config.GetConfig().MongoDb
    url = "mongodb://" + mongoConfig.UserName + ":" + mongoConfig.Password + "@" +
        mongoConfig.IP + ":" + mongoConfig.Port + "/" + mongoConfig.DbName

    globalSession, err = mgo.Dial(url)
    if err != nil {
        logger.Error("mongo init error", err)
    }

    globalSession.SetMode(mgo.Strong, true)

    return err
}

初始化mongo连接,同时设置模式为”Strong”。

Strong 一致性模式

session 的读写操作总向 primary 服务器发起并使用一个唯一的连接,因此所有的读写操作完全的一致(不存在乱序或者获取到旧数据的问题)。

Monotonic 一致性模式

session 的读操作开始是向某个 secondary 服务器发起(且通过一个唯一的连接),只要出现了一次写操作,session 的连接就会切换至 primary 服务器。由此可见此模式下,能够分散一些读操作到 secondary 服务器,但是读操作不一定能够获得最新的数据。

Eventual 一致性模式

session 的读操作会向任意的 secondary 服务器发起,多次读操作并不一定使用相同的连接,也就是读操作不一定有序。session 的写操作总是向 primary 服务器发起,但是可能使用不同的连接,也就是写操作也不一定有序。Eventual 一致性模式最快,其是一种资源友好(resource-friendly)的模式。

因为mongo没有开启主从,所以这里设为strong一致性模式。

但是strong模式和Monotonic模式会缓存socket到session中,导致我拿到的始终是同一个连接,这对并发请求mongo server会损失一定的效率(一个连接在使用过程中会有多次加锁解锁操作)。所以后续为了从连接池拿空闲的连接而不是一直使用同一个连接,会用到copy方法,拿到一个没有缓存连接的session,这样它就会去连接池拿空闲的可用的连接。

func GetCollection(collectionName string, se *mgo.Session) *mgo.Collection {
    return se.DB("").C(collectionName)
}

func GetDb(se *mgo.Session) *mgo.Database {
    return se.DB("")
}

func GetSession() *mgo.Session {
    return globalSession.Copy()
}

GetSession()方法用的是copy(),下面进源码分析一下

type Session struct {
    ...
    syncTimeout      time.Duration
    sockTimeout      time.Duration
    poolLimit        int
    ...
    consistency      Mode
    creds            []Credential
    ...
    slaveSocket      *mongoSocket
    masterSocket     *mongoSocket
    ...
    slaveOk          bool
}

// Copy works just like New, but preserves the exact authentication
// information from the original session.
func (s *Session) Copy() *Session {
    s.m.Lock()
    scopy := copySession(s, true)
    s.m.Unlock()
    scopy.Refresh()
    return scopy
}

copy方法,只会将一些认证信息等进行copy,而slaveSocket和masterSocket 不会进行拷贝,这样我从新得到的session去获取连接的时候发现连接为空,就会去连接池里拿空闲可用的连接。

func (s *Session) acquireSocket(slaveOk bool) (*mongoSocket, error) {

    // Read-only lock to check for previously reserved socket.
    s.m.RLock()
    // If there is a slave socket reserved and its use is acceptable, take it as long
    // as there isn't a master socket which would be preferred by the read preference mode.
    if s.slaveSocket != nil && s.slaveSocket.dead == nil && s.slaveOk && slaveOk && (s.masterSocket == nil || s.consistency != PrimaryPreferred && s.consistency != Monotonic) {
        socket := s.slaveSocket
        socket.Acquire()
        s.m.RUnlock()
        return socket, nil
    }
    if s.masterSocket != nil && s.masterSocket.dead == nil {
        socket := s.masterSocket
        socket.Acquire()
        s.m.RUnlock()
        return socket, nil
    }
    s.m.RUnlock()

    // No go.  We may have to request a new socket and change the session,
    // so try again but with an exclusive lock now.
    s.m.Lock()
    defer s.m.Unlock()

    if s.slaveSocket != nil && s.slaveOk && slaveOk && (s.masterSocket == nil || s.consistency != PrimaryPreferred && s.consistency != Monotonic) {
        if s.slaveSocket.dead == nil {
            s.slaveSocket.Acquire()
            return s.slaveSocket, nil
        } else {
            s.unsetSocket()
        }
    }
    if s.masterSocket != nil {
        if s.masterSocket.dead == nil {
            s.masterSocket.Acquire()
            return s.masterSocket, nil
        } else {
            s.unsetSocket()
        }
    }
        // Still not good.  We need a new socket.
    sock, err := s.cluster().AcquireSocketWithPoolTimeout(
        s.consistency, slaveOk && s.slaveOk, s.syncTimeout, s.sockTimeout, s.queryConfig.op.serverTags, s.poolLimit, s.poolTimeout,
    )
    if err != nil {
        return nil, err
    }
    .
    .
    .

发现session里面没有连接,则会调用s.cluster().AcquireSocketWithPoolTimeout()方法,再次经过层层调用

if n > 0 {
            socket = server.unusedSockets[n-1]
            server.unusedSockets[n-1] = nil // Help GC.
            server.unusedSockets = server.unusedSockets[:n-1]
            info := server.info
            server.Unlock()
            err = socket.InitialAcquire(info, timeout)
            if err != nil {
                continue
            }
        } else {

这个unusedSockets切片,就是我们的空闲连接池,下面是server的数据结构:

type mongoServer struct {
    sync.RWMutex
    Addr          string
    ResolvedAddr  string
    tcpaddr       *net.TCPAddr
    unusedSockets []*mongoSocket //这个和下面那个liveSockets就是mgo使用的连接池
    liveSockets   []*mongoSocket
    sync          chan bool
    dial          dialer
    pingValue     time.Duration
    pingIndex     int
    pingWindow    [6]time.Duration
    info          *mongoServerInfo
    pingCount     uint32
    closed        bool
    abended       bool
    minPoolSize   int
    maxIdleTimeMS int
    poolWaiter    *sync.Cond
}

拿到连接之后,就可以和mongo server交互了,这里就举个增的例子,其余的删改查都差不多。

func Insert(data *models.QuoteData) error {
    metal := utils.GetProductNameByInstrumentID(data.InstrumentID)
    se := GetSession()             //获取session,此方法见文章开头定义
    co := GetCollection(metal, se) //获取集合,此方法见文章开头定义
    defer se.Close()              //最后session别忘了关闭,否则连接池一会会就被用完了
    var mongoData = models.MongoQuoteData{
        InstrumentId: data.InstrumentID,
        TradingDay:   data.TradingDay,
        Data:         data,
    }
    err := co.Insert(&mongoData)  //插入数据
    return err
}

猜你喜欢

转载自blog.csdn.net/jeffrey11223/article/details/80144236
mgo
今日推荐