Golang mgo 剖析之 Session

golang 操作 mongodb 使用的包是 “gopkg.in/mgo.v2”,coding 过程中需要并发读写 mongodb 数据库,简单观摩了下源码,记录下自己的一些理解,如有错误,敬请斧正。 

一般来说,我们直接这样创建一个 Session:

Session, err = mgo.Dial(URL)
     if err != nil {
        log.Println(err)
 }

来看看Dial这个函数做了什么:

func Dial(url string) (*Session, error) {

    session, err := DialWithTimeout(url, 10*time.Second)

    if err == nil {
        session.SetSyncTimeout(1 * time.Minute)
        session.SetSocketTimeout(1 * time.Minute)
    }

    return session, err
}

调用 DialWithTimeout 函数设置默认的超时时间是 10 秒。该函数中调用了 DialWithInfo 这个函数,而 DialWithInfo 函数中比较重要是是调用了newSession,看看这个函数做了什么操作:

func newSession(consistency Mode, cluster *mongoCluster, timeout time.Duration) (session *Session) {

    cluster.Acquire()

    session = &Session{

        cluster_:    cluster,
        syncTimeout: timeout,
        sockTimeout: timeout,
        poolLimit:   4096,

    }

    debugf("New session %p on cluster %p", session, cluster)

    session.SetMode(consistency, true)
    session.SetSafe(&Safe{})
    session.queryConfig.prefetch = defaultPrefetch

    return session

}

返回的session设置了一些默认的参数,暂时先忽略,直接看看Session的数据结构:

type Session struct {
    m                sync.RWMutex
    cluster_         *mongoCluster
    slaveSocket      *mongoSocket
    masterSocket     *mongoSocket
    slaveOk          bool
    consistency      Mode
    queryConfig      query
    safeOp           *queryOp
    syncTimeout      time.Duration
    sockTimeout      time.Duration
    defaultdb        string
    sourcedb         string
    dialCred         *Credential
    creds            []Credential
    poolLimit        int
    bypassValidation bool
}

m 是 mgo.Session 的并发锁,因此所有的 Session 实例都是线程安全的。slaveSocket,masterSocket 代表了该 Session 到 mongodb 主节点和从节点的一个物理连接的缓存。而 Session 的策略总是优先使用缓存的连接。是否缓存连接,由 consistency 也就是该 Session 的模式决定。假设在并发程序中,使用同一个 Session 实例,不使用 Copy,而该 Session 实例的模式又恰好会缓存连接,那么,所有的通过该 Session 实例的操作,都会通过同一条连接到达 mongodb。虽然 mongodb 本身的网络模型是非阻塞通信,请求可以通过一条链路,非阻塞地处理,但是会影响效率。

其次 mgo.Session 缓存的一主一从连接,实例本身不负责维护。也就是说,当 slaveSocket,masterSocket 任意其一,连接断开,Session 自己不会重置缓存,该 Session 的使用者如果不主动重置缓存,调用者得到的将永远是 EOF。这种情况在主从切换时就会发生,在网络抖动时也会发生。

mgo 的 DB 句柄需要你做一个 copy 操作:

// 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
}

copySession 将源 Session 浅拷贝到临时 Session 中,这样源 Session 的配置就拷贝到了临时 Session 中。关键的 Refresh,将源 Session 浅拷贝到临时 Session 的连接缓存指针,也就是 slaveSocket,masterSocket 置为空,这样临时 Session 就不存在缓存连接,而转为去尝试获取一个空闲的连接。

mgo 自身维护了一套到 mongodb 集群的连接池。这套连接池机制以 mongodb 数据库服务器为最小单位,每个 mongodb 都会在 mgo 内部,对应一个 mongoServer 结构体的实例,一个实例代表着 mgo 持有的到该数据库的连接。看看这个连接池的定义:

type mongoServer struct {
    sync.RWMutex
    Addr          string
    ResolvedAddr  string
    tcpaddr       *net.TCPAddr
    unusedSockets []*mongoSocket
    liveSockets   []*mongoSocket
    closed        bool
    abended       bool
    sync          chan bool
    dial          dialer
    pingValue     time.Duration
    pingIndex     int
    pingCount     uint32
    pingWindow    [6]time.Duration
    info          *mongoServerInfo
}

info 代表了该实例对应的数据库服务器在集群中的信息——是否 master,ReplicaSetName 等。而两个 Slice,就是传说中的连接池。unusedSockets 存储当前空闲的连接,liveSockets 存储当前活跃中的连接,Session 缓存的连接就同时存放在 liveSockets 切片中,而临时 Session 获取到的连接就位于 unusedSockets 切片中。

每个 mongoServer 都会隶属于一个 mongoCluster 结构,相当于 mgo 在内部,模拟出了 mongodb 数据库集群的模型。

type mongoCluster struct {
    sync.RWMutex
    serverSynced sync.Cond
    userSeeds    []string
    dynaSeeds    []string
    servers      mongoServers
    masters      mongoServers
    references   int
    syncing      bool
    direct       bool
    failFast     bool
    syncCount    uint
    setName      string
    cachedIndex  map[string]bool
    sync         chan bool
    dial         dialer
}

mongoCluster 持有一系列 mongoServer 的实例,以主从结构分散到两个数组中。 每个 Session 都会存储自己对应的,要操作的 mongoCluster 的引用。

前面的描述可以总结成下面这张图:

这里写图片描述

那么我们在使用的时候就可以创建一个 Session,然后 clone 操作,用 clone 得到的 copysession 完成操作,结束后关闭这个 copysession 就可以了。

猜你喜欢

转载自blog.csdn.net/u010649766/article/details/79818240
mgo