本司礼物系统使用了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的更多相关文章

  1. 连接池设置导致的“血案” 原创: 一页破书 一页破书 5月6日 这个问题被投诉的几个月了,一直没重视——内部客户嘛😿 问题现象: 隔几周就会出现 A服务调用B服务超时 脚趾头想就是防火墙的问题,A、B两服务之间有防火墙 找运维查看防火墙日志确实断掉了tcp连接,但是是因为B服务5分钟没有回包,下面这个表情就是我当时的心情——其实我们在防火墙、A服务、B服务都抓包了,几十个G的t

    连接池设置导致的“血案” 原创: 一页破书 一页破书 5月6日 这个问题被投诉的几个月了,一直没重视——内部客户嘛

  2. HttpClient连接池设置引发的一次雪崩

    事件背景 我在凤巢团队独立搭建和运维的一个高流量的推广实况系统,是通过HttpClient 调用大搜的实况服务.最近经常出现Address already in use (Bind failed)的问 ...

  3. (转)websphere线程池 连接池设置

    原文:http://www.talkwithtrend.com/Article/207511 池(Pool)是WebSphere中最常涉及的概念之一.从网络.Web 服务器.Web 容器.EJB 容器 ...

  4. Eclipse+Tomcat7.0+MySQL 连接池设置

    http://blog.sina.com.cn/s/blog_85d71fb70101ab99.html 工程名:JavaWeb 第一步:配置server.xml 在Tomcat的server.xml ...

  5. Hibernate连接池设置

    在公司第一次做项目放到服务器上测试,发现每隔一段时间不用数据库就连接不上了(以前学过连接池,很久没用就忘了),在myeclipse上的时候没发现,网上搜索才发现是hibernate连接池配置问题. 1 ...

  6. dbcp/c3p0连接池设置mysql会话变量

    我们有几个计算风控值的定时任务,几乎每隔5秒会更新所有账户的当前总资产并以此通知风控,每隔一小时就产生一两个G的binlog,几十台服务器折腾..数据库是公用的,代码是通过工具自动生成的,直接修改流程 ...

  7. jmeter-JDBC 连接池设置

  8. mgo 的 session 与连接池

    简介 mgo是由Golang编写的开源mongodb驱动.由于mongodb官方并没有开发Golang驱动,因此这款驱动被广泛使用.mongodb官网也推荐了这款开源驱动,并且作者在github也表示 ...

  9. Golang SQL连接池梳理

    目录 一.如何理解数据库连接 二.连接池的工作原理 三.database/sql包结构 四.三个重要的结构体 4.1.DB 4.2.driverConn 4.3.Conn 五.流程梳理 5.1.先获取 ...

随机推荐

  1. Linux 指令大全

    作为一个小前端,以前有我们的运维大神在的时候,要给服务器做什么配置的时候就找他(那时幸福到哭),如今他走了,公司也没招人(想把这个钱省下来,让我发现了,毕竟我能当小运维用,虽然很这方面很渣渣,哈哈,偷 ...

  2. 虚拟机中Linux系统盘空间不足

    虚拟机中Linux系统盘在使用过程中, 出现空间不足的提示. 使用命令du --max-depth=1 -h 查看Home目录下各个文件占用空间, 发现是./cache(隐藏文件)占用很大空间.进入c ...

  3. 关于AngularJs,数据绑定与自定义验证

    最近开始着手学起了Angular,抱着好奇的心情开始研究了起来.忽然发现angular可以巧妙而方便的进行数据的绑定验证啊什么的.(当然,我只是刚开始学,所有可能有更强大的功能,只是我还没有看到) 那 ...

  4. 深入理解javascript原型和闭包(完结)

    原文链接:http://www.cnblogs.com/wangfupeng1988/p/3977924.html 说明: 该教程绕开了javascript的一些基本的语法知识,直接讲解javascr ...

  5. dynamic与匿名对象

    用dynamic接收匿名对象很方便,因为不需要去定义model了,但是也有一个弊端,就是匿名对象的作用范围是internal的,也就是只能存在于当前程序域,所以用dynimic跨程序域去接收一个匿名对 ...

  6. HDFS shell

    bin/hdfs -help bin/hdfs dfs -mkdir -p /yfq/test/ bin/hdfs dfs -put /etc/profile /yfq/test/profile 上传 ...

  7. 04 KVC|KVO|Delegate|NSNotification区别

    一. iOS 中KVC.KVO.NSNotification.delegate 在实际的编程中运用的非常多,掌握好他们的运行原理和使用场合对于我们程序的开发将会带来事办工倍的效果:   二. KVC ...

  8. my.cnf

    skip-external-locking skip-name-resolve back_log= key_buffer_size=384M max_allowed_packet=4M thread_ ...

  9. 将JSON对象带有格式的写出到文件中

    需求:将一个JSON对象写出到文件中,要求文件中的JSON数据带有简单的格式.代码的实现参考了Java算法中的栈处理括号匹配问题.好了,不多说了,下面是代码的实现. 代码: package gemu. ...

  10. 狗汪汪玩转无线电 -- GPS Hacking

    狗汪汪玩转无线电 -- GPS Hacking Kevin2600 · 2015/12/09 10:12 0x00 序 GPS Hacking 在过去几年的安全会议上一直都是很受关注的议题. 但往往因 ...