MongoDB Golang驱动mgo的连接池使用问题

Posted 讯飞技术沙龙

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MongoDB Golang驱动mgo的连接池使用问题相关的知识,希望对你有一定的参考价值。

  • 现网遇到的问题

    • Xpush推送历史消息存储组件mgoproxy。该组件功能为接收推送消息,处理后将该消息存入MongoDB中,在网络异常、网络抖动的情况下,推送消息无法写入MongoDB,日志报EOF;在服务请求压力过高的情况下,可发现TCP发送队列有较高的数据积压,请求处理缓慢,后修改Mgo连接模式,问题得到解决

    • IM的web服务后端组件webServer。该组件功能为接收HTTP请求,对请求进行处理,将数据存入MongoDB数据库或从数据库中读取必要的数据,之后将结果返回给客户端。与上面的情况类似,同样是在网络异常的情况下,会出现数据库操作EOF错误,在请求量过大的情况下,数据库操作耗时过长,响应返回很慢的问题。修改连接模式后,问题解决。


  • Mgo数据结构简介

  • Session简介

Mgo客户端和MongoDB数据库通信是通过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

}

 

下面是几个相对比较重要的参数:

slaveSocket——Session到MongoDB从节点的物理连接缓存

masterSocket——Session到MongoDB主节点的物理连接缓存

m——mgo.Session的并发锁,Session实例线程安全

consistency——决定Session是否缓存物理连接,由一致性模式决定

cluster_——连接的MongoDB集群的引用

 

Session的策略总是优先使用缓存的连接,是否使用缓存连接,由consistency即该Session的模式决定,除了Eventual consistency mode不使用缓存的连接外,剩余的连接模式均会使用缓存中的连接。缓存的一主一从连接,Session本身不负责维护,即连接断开后,Session不会重置缓存,Session的使用者如果不主动重置缓存,调用者得到的永远是EOF。这种情况在数据库主从切换、网络断连及网络抖动时都会发生。后面的集成使用Mgo的注意事项中会讲到这一点。

 

  • Mgo连接池

Session中缓存的连接来自于Mgo维护的连接池,连接池以MongoDB数据库服务器为最小单位,每个MongoDB都会在Mgo内部都会对应一个mongoServer结构体的实例,一个实例代表着Mgo持有的到该数据库的连接。该数据结构被封装在mongoServer结构中,如下所示:

 

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
}

 

其中和连接密切相关的两个成员变量是unusedSocketsliveSockets,这两个slice就是连接池,unusedSockets存储当前空闲的可用连接,liveSockets存储当前活跃的连接,Session缓存的连接同时存放在liveSockets中。

 

  • MongoDB集群

每个mongoServer结构都会隶属于一个mongoCluster结构,相当于在Mgo内部,模拟出了MongoDB数据库集群的模型。其结构如下:

 

type mongoCluster struct {

sync.RWMutex

serverSyncedsync.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一致性模式介绍

  • Strong consistency mode

在强一致模式下,读写都是在主服务节点上通过同一个连接实现,这样读写是完全一致、有序并且查询到的数据也是最新的。这种模式在分布式负载架构下没有多少益处,但是保证了读写数据的强一致性。由于该模式下对数据库的读写都是在单独的TCP连接上进行的,所以效率不高,在高负载下难免会出现数据积压,上面所说的两个问题,也是这个原因导致的,所以在生产环境中,如果对数据一致性的要求不高,建议不要使用这种模式。

  • Monotonic consistency mode

Monotonic一致性模式下,读到的数据不一定是最新的,但是在同一个session中的一系列读保持着数据一致性。session 的读操作开始是向某个 secondary 服务器发起,只要出现了一次写操作,session 的连接就会切换至 primary 服务器。由此可见此模式下,能够分散一些读操作到 secondary 服务器,但是读操作不一定能够获得最新的数据。

  • Eventual consistency mode

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

 

总结来说,如下表所示:


Strong

Monotonic

Eventual

Read

read  to primary server

read  to arbitrary secondary

read  to any secondary

Write

write  to primary server

write  to primary

write  to primary

Connection

unique

unique

independent

Guarantee

the  most

some  useful

the  least

 

  • 集成使用Mgo的注意事项

  • Session拷贝

在与MongoDB通信前,首先要创建一个Session,但是后续的通信,我们不会直接使用这个Session,而是基于这个全局的Session,在每次操作的时候Copy一个临时Session出来,来与数据库进行通信,原因有两点:

  1. 实际的生产环境中,会有网络断连和网络抖动的情况,在网络连接恢复后,由于Session不会重置缓存的连接,所以会导致数据库操作失败(在不使用Eventual模式情况下),而如果在每次数据库操作前Copy一个临时Session,由于临时Session的缓存连接为空(Copy函数中的Refresh会将缓存置空),所以会从物理连接池中获得一个可用连接,来完成后续的数据库操作。

  2. 有些模式的数据库读写在一个Session中是在一个连接上完成的,如果只使用一个全局的Session,这会降低服务在高并发情况下的处理能力。

  • Eventual模式

通过Dial获取的Session如果没有设置,默认是Strong连接模式。可通过SetMode接口修改连接模式为Eventual模式,该模式不使用缓存连接,因此不需要每次操作数据时Copy一个临时Session,另外该模式下,数据库读写操作会分布到很多TCP连接中,因此服务在高并发情况下的处理能力也有所提升。




  • Q&A

Q1:Mgo三种一致性模式的区别是什么?

A1:主要的区别就是读写方式的不同。在强一致模式下,读写都是在主服务节点上通过同一个连接实现,这样读写是完全一致、有序并且查询到的数据也是最新的。这种模式在分布式负载架构下没有多少益处,但是保证了读写数据的强一致性。在Monotonic一致性模式下,读到的数据不一定是最新的,但是在同一个session中的一系列读保持着数据一致性;session 的读操作开始是向某个 secondary 服务器发起,只要出现了一次写操作,session 的连接就会切换至 primary 服务器;在Eventual模式下,session 的读操作会向任意的 secondary 服务器发起,多次读操作并不一定使用相同的连接,也就是读操作不一定有序。session 的写操作总是向 primary 服务器发起,但是会使用不同的连接,也就是写操作也不一定有序;总结就是文中的表格。


Q2:Monotonic模式下,开始在secondary 服务器读,然后有一次写之后转到了primary 服务器,那再读是在primary 服务器吗?这样数据会不会不一样?

A2:在数据同步的时间段内,会有几率导致刚写的数据读不到,如果想保持读写数据一致性,建议使用Strong连接模式

 

Q3:Strong模式在一个Session中是通过单个连接进行读写的,还是通过一组连接?

A3:在单个Session中,是通过单个连接进行数据读写的。

 

Q4:MongoDB的主节点挂点之后会出现什么问题?

A4:mongodb 底层的主从(即复制集)是用raft协议保证数据一致性和failover故障切换。数据库类的主从切换一般是代理负责,比如mysql的proxy或者是mongodb的mongos,至于数据库类为什么不用多路复用,而用连接池模式,是因为数据库类的连接会有些连接相关属性或者会话变量的设置,还有一些事物相关的操作,多路复用只会带来复杂度,并不能提高性能,至于客户端的连接池,主要是负责对proxy代理的异常检测就行了,proxy可以认为是均等的,具体内部的切换和连接都是proxy来决定的。下面是MongoDB拓扑图:

其中shard代表着分片,针对集合可以做水平范围切分,实现水平扩展。

 

Q5:MongoDB的文件系统是什么?

A5:文件系统用的操作系统的ext4.xfs等,mongod内部实现的数据格式叫bson

 




以上是关于MongoDB Golang驱动mgo的连接池使用问题的主要内容,如果未能解决你的问题,请参考以下文章

用golang的mgo驱动,mongodb时区怎么设置,总是慢8小时

labix.org/mgo 连接池泄漏问题

Go mgo 包中的连接池

Golang mongodb 从集合中删除所有项目 [mgo.v2]

如何使用 golang 和 mgo 库在 mongodb 中创建文本索引?

golang基础学习-MongoDB使用