设为首页 收藏本站
查看: 1164|回复: 0

[经验分享] golang mgo的mongo连接池设置:必须手动加上maxPoolSize

[复制链接]

尚未签到

发表于 2018-9-20 08:39:18 | 显示全部楼层 |阅读模式
  本司礼物系统使用了golang的 mongo库 mgo,中间踩了一些坑,总结下避免大家再踩坑
  golang的mgo库说明里是说明了开启连接复用的,但观察实验发现,这并没有根本实现连接的控制,连接复用仅在有空闲连接时生效,高并发时无可用连接会不断创建新连接,所以最终还是需要程序员自行去限制最大连接才行。
  废话不多说,开始上代码
GlobalMgoSession, err := mgo.Dial(host) func (m *MongoBaseDao) Get(tablename string,>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 范围内实现复用,需要自行优化。


运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-598568-1-1.html 上篇帖子: Golang的交互模式进阶 下篇帖子: Golang之Mysql事务
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表