golang mgo的mongo连接池设置:必须手动加上maxPoolSize
本司礼物系统使用了golang的 mongo库 mgo,中间踩了一些坑,总结下避免大家再踩坑
golang的mgo库说明里是说明了开启连接复用的,但观察实验发现,这并没有根本实现连接的控制,连接复用仅在有空闲连接时生效,高并发时无可用连接会不断创建新连接,所以最终还是需要程序员自行去限制最大连接才行。
废话不多说,开始上代码
GlobalMgoSession, err := mgo.Dial(host)func (m *MongoBaseDao) Get(tablename string, id string, result interface{}) interface{} { session := GlobalMgoSession.Clone() defer session.Close() collection := session.DB(globalMgoDbName).C(tablename) err := collection.FindId(bson.ObjectIdHex(id)).One(result) if err != nil { logkit.Logger.Error("mongo_base method:Get " + err.Error()) } return result} |
golang main入口启动时,我们会创建一个全局session,然后每次使用时clone session的信息和连接,用于本次请求,使用后调用session.Close() 释放连接。
// Clone works just like Copy, but also reuses the same socket as the original// session, in case it had already reserved one due to its consistency// guarantees. This behavior ensures that writes performed in the old session// are necessarily observed when using the new session, as long as it was a// strong or monotonic session. That said, it also means that long operations// may cause other goroutines using the original session to wait.func (s *Session) Clone() *Session { s.m.Lock() scopy := copySession(s, true) s.m.Unlock() return scopy} // Close terminates the session. It's a runtime error to use a session// after it has been closed.func (s *Session) Close() { s.m.Lock() if s.cluster_ != nil { debugf("Closing session %p", s) s.unsetSocket() //释放当前线程占用的socket 置为nil s.cluster_.Release() s.cluster_ = nil } s.m.Unlock()} |
Clone的方法注释里说明会重用原始session的socket连接,但是并发请求一大,其他协程来不及释放连接,当前协程会怎么办?
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.slaveOk && slaveOk && (s.masterSocket == nil || s.consistency != PrimaryPreferred && s.consistency != Monotonic) { socket := s.slaveSocket socket.Acquire() s.m.RUnlock() logkit.Logger.Info("sgp_test 1 acquireSocket slave is ok!") return socket, nil } if s.masterSocket != nil { socket := s.masterSocket socket.Acquire() s.m.RUnlock() logkit.Logger.Info("sgp_test 1 acquireSocket master is ok!") 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) { s.slaveSocket.Acquire() logkit.Logger.Info("sgp_test 2 acquireSocket slave is ok!") return s.slaveSocket, nil } if s.masterSocket != nil { s.masterSocket.Acquire() logkit.Logger.Info("sgp_test 2 acquireSocket master is ok!") return s.masterSocket, nil } // Still not good. We need a new socket. sock, err := s.cluster().AcquireSocket(s.consistency, slaveOk && s.slaveOk, s.syncTimeout, s.sockTimeout, s.queryConfig.op.serverTags, s.poolLimit)...... logkit.Logger.Info("sgp_test 3 acquireSocket cluster AcquireSocket is ok!") return sock, nil} |
在源码中加debug,结果日志说明一切:
Mar 25 09:46:40 dev02.pandatv.com bikini[12607]: [info] sgp_test 1 acquireSocket master is ok!Mar 25 09:46:40 dev02.pandatv.com bikini[12607]: [info] sgp_test 1 acquireSocket master is ok!Mar 25 09:46:41 dev02.pandatv.com bikini[12607]: [info] sgp_test 1 acquireSocket slave is ok!Mar 25 09:46:41 dev02.pandatv.com bikini[12607]: [info] sgp_test 3 acquireSocket cluster AcquireSocket is ok!Mar 25 09:46:41 dev02.pandatv.com bikini[12607]: [info] sgp_test 3 acquireSocket cluster AcquireSocket is ok!Mar 25 09:46:41 dev02.pandatv.com bikini[12607]: [info] sgp_test 3 acquireSocket cluster AcquireSocket is ok! |
不断的创建连接 AcquireSocket
$ netstat -nat|grep -i 27017|wc -l
400
如果每个session 不调用close,会达到恐怖的4096,并堵死其他请求,所以clone或copy session时一定要defer close掉
启用maxPoolLimit 参数则会限制总连接大小,连接到限制则当前协程会sleep等待 直到可以创建连接,高并发时锁有问题,会导致多创建几个连接
src/gopkg.in/mgo.v2/cluster.go s, abended, err := server.AcquireSocket(poolLimit, socketTimeout) if err == errPoolLimit { if !warnedLimit { warnedLimit = true logkit.Logger.Error("sgp_test WARNING: Per-server connection limit reached. " + err.Error()) log("WARNING: Per-server connection limit reached.") } time.Sleep(100 * time.Millisecond) continue } session.go:// SetPoolLimit sets the maximum number of sockets in use in a single server // before this session will block waiting for a socket to be available. // The default limit is 4096. // // This limit must be set to cover more than any expected workload of the // application. It is a bad practice and an unsupported use case to use the // database driver to define the concurrency limit of an application. Prevent // such concurrency "at the door" instead, by properly restricting the amount // of used resources and number of goroutines before they are created. func (s *Session) SetPoolLimit(limit int) { s.m.Lock() s.poolLimit = limit s.m.Unlock() } |
连接池设置方法:
1、配置中 增加
[host]:[port]?maxPoolSize=10
2、代码中 :
dao.GlobalMgoSession.SetPoolLimit(10)
再做压测:
$ netstat -nat|grep -i 27017|wc -l
15
结论:
每次clone session之后,操作结束时如果调用 session.Close 则会unset Socket ,socket refer数减少,如果不设置上限,每个协程请求到来发现无空闲连接就会创建socket连接,直到达到最大值4096,而mongo的连接数上限一般也就是1万,也就是一个端口你只能启动一两个进程保证连接不被撑爆,过多的连接数客户端效率不高,server端更会耗费内存和CPU,所以需要启用自定义连接池 , 启用连接池也需要注意如果有pooMaxLimit个协程执行过长或者死循环不释放socket连接,也会悲剧。
mgo底层socket连接池只在maxPooMaxLimit 范围内实现复用,需要自行优化。
golang mgo的mongo连接池设置:必须手动加上maxPoolSize的更多相关文章
- 连接池设置导致的“血案” 原创: 一页破书 一页破书 5月6日 这个问题被投诉的几个月了,一直没重视——内部客户嘛😿 问题现象: 隔几周就会出现 A服务调用B服务超时 脚趾头想就是防火墙的问题,A、B两服务之间有防火墙 找运维查看防火墙日志确实断掉了tcp连接,但是是因为B服务5分钟没有回包,下面这个表情就是我当时的心情——其实我们在防火墙、A服务、B服务都抓包了,几十个G的t
连接池设置导致的“血案” 原创: 一页破书 一页破书 5月6日 这个问题被投诉的几个月了,一直没重视——内部客户嘛
- HttpClient连接池设置引发的一次雪崩
事件背景 我在凤巢团队独立搭建和运维的一个高流量的推广实况系统,是通过HttpClient 调用大搜的实况服务.最近经常出现Address already in use (Bind failed)的问 ...
- (转)websphere线程池 连接池设置
原文:http://www.talkwithtrend.com/Article/207511 池(Pool)是WebSphere中最常涉及的概念之一.从网络.Web 服务器.Web 容器.EJB 容器 ...
- Eclipse+Tomcat7.0+MySQL 连接池设置
http://blog.sina.com.cn/s/blog_85d71fb70101ab99.html 工程名:JavaWeb 第一步:配置server.xml 在Tomcat的server.xml ...
- Hibernate连接池设置
在公司第一次做项目放到服务器上测试,发现每隔一段时间不用数据库就连接不上了(以前学过连接池,很久没用就忘了),在myeclipse上的时候没发现,网上搜索才发现是hibernate连接池配置问题. 1 ...
- dbcp/c3p0连接池设置mysql会话变量
我们有几个计算风控值的定时任务,几乎每隔5秒会更新所有账户的当前总资产并以此通知风控,每隔一小时就产生一两个G的binlog,几十台服务器折腾..数据库是公用的,代码是通过工具自动生成的,直接修改流程 ...
- jmeter-JDBC 连接池设置
- mgo 的 session 与连接池
简介 mgo是由Golang编写的开源mongodb驱动.由于mongodb官方并没有开发Golang驱动,因此这款驱动被广泛使用.mongodb官网也推荐了这款开源驱动,并且作者在github也表示 ...
- Golang SQL连接池梳理
目录 一.如何理解数据库连接 二.连接池的工作原理 三.database/sql包结构 四.三个重要的结构体 4.1.DB 4.2.driverConn 4.3.Conn 五.流程梳理 5.1.先获取 ...
随机推荐
- Android的学习第六章(布局一LinearLayout)
今天我们来说一下Android五大布局-LinearLayout布局(线性布局) 含义:线性布局,顾名思义,指的是整个Android布局中的控件摆放方式是以线性的方式摆放的, 主要作用:主要对整个界面 ...
- 让你的PHP程序真正的实现多线程(PHP多线程类)
通过WEB服务器来实现PHP多线程功能. 当然,对多线程有深入理解的人都知道通过WEB服务器实现的多线程只能模仿多线程的一些效果,并不是真正意义上的多线程. 但不管怎么样,它还是能满足我们的一些需要的 ...
- 使用JavaScript访问子节点方法elementNode.childNodes时,需要注意的地方
有这样一个HTML结构 <div> javascript <p>javascript</p> <div>jQuery</div> <h ...
- Java读取Level-1行情dbf文件极致优化(3)
最近架构一个项目,实现行情的接入和分发,需要达到极致的低时延特性,这对于证券系统是非常重要的.接入的行情源是可以配置,既可以是Level-1,也可以是Level-2或其他第三方的源.虽然Level-1 ...
- 关于C语言宏定义 使用do{ xxxx }while()
暂时感觉像是由于":"的原因,关于使用习惯方面的问题!! 下面是copy的: 这样的宏见过么: Cpp代码 #define FOO(x) do {\ some_code_line_ ...
- 5.对与表与表之间的关系,efcore是如何处理的
public class Account { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Accoun ...
- PHP的PDO
PDO中包含三个预定一类:PDO.PODStatement和PDOException. 1.PDO类 PDO类代表一个PHP和数据库之间的连接,PDO类所拥有的方法如下: PDO:构造器,构建一个新的 ...
- 连锁机构3D指纹考勤系统解决方案
信息技术的高速发展加速了商业零售业连锁经营的信息化和全球化的进程,同时也推动了商业管理的变革.尽管人们对它的认识是被动与滞后的,但这种变革依然伴随着商业业态的转变和信息技术的发展或快或慢地在悄然进行着 ...
- OpenLayers元素选择工具
OpenLayers的selector工具相信挺多人都没有用过,其实这个工具用处还是不少的.比如完成元素查询时,需要实现图属性联动,使用这个工具很方便.最近做项目时也使用到这个工具,使用起来确实挺方便 ...
- 图像预处理第9步:存为.bmp文件
//图像预处理第9步:将最终标准化后的字符图像分为单个单个的HDIB保存,并存为.bmp文件 void CChildView::OnImgprcToDibAndSave() { unsigned ch ...