mongos 连接数问题解析

mongos 连接数问题解析_第1张图片
笔者:李大
有任何疑问欢迎关注微信公众号:网易游戏运维平台。(长按识别上图二维码)
微信公众号原文链接:mongos 连接数问题解析

文章目录

    • 客户端到 mongos 的连接
      • 每连接每线程
      • adaptive 线程池
    • mongos 的连接池
        • 相关结构
        • 传统连接池的使用
        • 相关结构
        • 使用
    • TaskExecutor 与 TaskExecutorPool
    • connPoolStats 与 shardConnPoolStats 命令
    • 连接相关的参数
    • 总结

Ocean 项目的 mongo 分片架构中,mongos 作为 mongo 分片架构的路由选择器,目前有两种使用方式,lbc 模式和传统的直连模式。直连模式下,mongos 由项目 SRE 维护管理,在使用中,项目 SRE 提到的比较多的一个问题是 : 客户端到 mongos 和 mongos 到 mongod 的连接数是什么样的关系?是 1: 1 的关系吗?
基于以上疑问,本文深度解析 mongos 的连接数问题 。

客户端到 mongos 的连接

每连接每线程

由于传输层 IO 模式的不同(ASIO 和 legacy), 不同版本 mongos 接收客户端连接的模块代码有些许差异,但是无论底层是 asio 还是 legacy , 默认情况下 mongos 对客户端连接都是【 每连接每线程 】的模型,每个线程分配 1 M 的内存。

        static const size_t STACK_SIZE = 
            1024 * 1024;  // if we change this we need to update the warning    // 分配的栈空间,1M

        struct rlimit limits;
        invariant(getrlimit(RLIMIT_STACK, &limits) == 0);
        if (limits.rlim_cur > STACK_SIZE) {
            size_t stackSizeToSet = STACK_SIZE;
#if !__has_feature(address_sanitizer)
            if (kDebugBuild)
                stackSizeToSet /= 2;
#endif
            int failed = pthread_attr_setstacksize(&attrs, stackSizeToSet);
            if (failed) {
                const auto ewd = errnoWithDescription(failed);
                warning() << "pthread_attr_setstacksize failed: " << ewd;
            }
        } else if (limits.rlim_cur < 1024 * 1024) {
            warning() << "Stack size set to " << (limits.rlim_cur / 1024) << "KB. We suggest 1MB";
        }

        pthread_t thread;
        int failed = pthread_create(&thread, &attrs, runFunc, ctx.get()); // 创建新的线程

所以,当 mongos 接收大量的连接时,对内存的消耗还是挺大的。

adaptive 线程池

为了减少每次新连接时创建与销毁线程的消耗,mongos 在 3.6 版本引入了 一种 adaptive 的线程池。预先创建 adaptiveServiceExecutorReservedThreads 个 worker 线程 和一个 controller 线程,当新的连接时,线程池内分配一个空闲 woker 线程。如果线程池内无空闲线程时,则会创建新的 worker 线程。当 worker 线程在一个执行内周期结束时,检测到真正执行 IO 操作的时间小于 adaptiveServiceExecutorIdlePctThreshold 比例时,则会自动销毁线程。

adaptiveServiceExecutorReservedThreads : 线程池预创建线程数。默认 CPU 核数 / 2 个。 
adaptiveServiceExecutorIdlePctThreshold : 线程池 worker 线程空闲时间的百分比,小于该百分比时自动销毁。

3.6 及以上版本才有 adaptive 的线程池功能,而且默认不开启,可以设置以下参数启动线程池功能。

//yaml
net:
	serviceExecutor: adaptive
//ini
serviceExecutor = adaptive

adaptive 线程池模式下,每个 worker 线程需要处理多个连接的 IO 操作,具体的连接数情况和 worker 线程无固定的比例关系,mongos 根据内部worker线程的执行情况,自适应的新建或销毁线程。可以通过 mongo shell 执行 db.serverStatus().network.serviceExecutorTaskStats 查看线程池的使用情况。

mongos> db.serverStatus().network.serviceExecutorTaskStats
{
        "executor" : "adaptive",
        "totalQueued" : NumberLong(25),
        "totalExecuted" : NumberLong(25),
        "threadsInUse" : 1,   // 正在使用(有IO操作)的worker线程数
        "totalTimeRunningMicros" : NumberLong(282344119),
        "totalTimeExecutingMicros" : NumberLong(11322),
        "totalTimeQueuedMicros" : NumberLong(180),
        "threadsRunning" : 8, // 当前的 worker 线程数 
        "threadsPending" : 0,  // 创建中的wroker 数量
        "threadCreationCauses" : {  // 3.6.4 版本新增模块  创建worker线程的原因情况统计 
                "stuckThreadsDetected" : NumberLong(0),  // 因为worker卡住而创建新的worker的次数
                "starvation" : NumberLong(0),  //线程池饥饿(即worker不够用)而创建worker的次数
                "belowReserveMinimum" : NumberLong(8), // 保持最低worker数量(adaptiveServiceExecutorReservedThreads) 而创建worker的数量
                "replacingCrashedThreads" : NumberLong(0) // worker crash 而创建新的worker的数量
        },
        "metricsByTask" : {
	        /* 3.6.4 版本新增模块,按照执行任务的种类汇总所有worker线程执行情况。所谓不同的任务种类,就是不同的函数。
	         1、一个连接进来时,首先要经过 startSession 开启一个回话 (session)。所以startSession可以理解为所有接受过的连接的总数。
	         2、开启 session 以后进去 sourceMessage ,等待接受客户端的命令(message)
	         3、收到客户端的命令(message) 以后就要处理对应命令,进入 processMessage 
	         4、processMessage 处理完消息以后,如果正常的执行完,得到正常的回复(response)或者无回复则循环执行 sourceMessage  等待新的命令;如果response 是一个 exhaust 的游标,那么继续执行 processMessage, 并记录一次 exhaustMessage的任务种类。
	         5、对每个任务种类:
		         totalQueued : 在线程池内排队的次数
		         totalExecuted : 在线程池内执行的次数
		         totalTimeExecutingMicros : 在线程池内执行的时间,单位微秒。注意这个时间是所有worker的线程的汇总信息,包含历史worker的统计时间。
		         totalTimeQueuedMicros : 在线程池内排队的时间,单位微秒。同上。
	        */
                "processMessage" : {
                        "totalQueued" : NumberLong(12),
                        "totalExecuted" : NumberLong(12),
                        "totalTimeExecutingMicros" : NumberLong(11114),
                        "totalTimeQueuedMicros" : NumberLong(5)
                },
                "sourceMessage" : {
                        "totalQueued" : NumberLong(12),
                        "totalExecuted" : NumberLong(12),
                        "totalTimeExecutingMicros" : NumberLong(78),
                        "totalTimeQueuedMicros" : NumberLong(132)
                },
                "exhaustMessage" : {
                        "totalQueued" : NumberLong(0),
                        "totalExecuted" : NumberLong(0),
                        "totalTimeExecutingMicros" : NumberLong(0),
                        "totalTimeQueuedMicros" : NumberLong(0)
                },
                "startSession" : { //开启session的次数,
                        "totalQueued" : NumberLong(1),
                        "totalExecuted" : NumberLong(1),
                        "totalTimeExecutingMicros" : NumberLong(19),
                        "totalTimeQueuedMicros" : NumberLong(42)
                }
        }
}

相比每连接每线程模式,adaptive 线程池模式可以减少大量连接时的内存消耗以及线程上下文切换的系统消耗,但是 adaptive 线程池模式暂时还不是默认的 serviceExecutor ,在测试中也发现,并发1000个连接压测,刚开始时线程池只有几十个 worker , 这时候通过 mongo shell 执行命令是很卡的,跑一段时间之后 worker 数量才会慢慢增加,最后甚至有可能基本到达每线程每连接的程度。所以 adaptive 线程池模式线上环境还需谨慎使用,暂时不推荐使用,Ocean 会持续跟进研究背后的原理及表现。

mongos 的连接池

mongos 内部有两种类型的连接池:传统的连接池( DBConnectionPool 结构) 和 ASIO 的连接池 ( ConnectionPool 结构)。

mongos 混用两种类型的连接池,对 CRUD 操作来说,在3.2 和 3.4 版本,查询通过 ASIO 的连接池 ,插入、更新、删除等通过传统连接池执行; 而 3.6 及以上版本所有的读写操作都是通过 ASIO 连接池执行。一般常见的 command 命令对连接池的使用情况见后文的总结表格。总之,随着版本的发展,传统连接池的使用场景越来越少。

###传统的连接池

相关结构

DBConnectionPool 是封装的传统的连接池,主要成员:

        typedef std::map PoolMap;  // servername -> pool
        PoolMap _pools;
        int _maxPoolSize;
        int _maxInUse; // 3.6 版本新增成员

PoolMap: 以 map 结构保存的到每个 host 的「 空闲连接池 」。
PoolKey : 就是一个封装的字符串。 可以是单节点 server 或者 复制集 mongo1/server1,server2,server3
PoolForHost: 管理对一个 host 的连接。用一个 stack 保存目前可用连接,_checkedOut 记录正在使用的连接数。一个连接就是 DBClientBase 对象 ; 每个连接有一个时间属性,表示连接添加进空闲连接池的时间,当连接使用完以后重新加进来时会重置。

	     std::stack _pool; // 所有目前可用的连接
	     struct StoredConnection {
		 std::unique_ptr conn; // 真正的连接对象 
	         Date_t added;//连接添加进来的时间,当连接使用完以后重新加进来时会重置。表示开始 idle 的时间。
	     }
	     int _checkedOut;  // 派发出去,正在使用中的连接数    

_maxPoolSize:限制了到每个 host 空闲连接数最大值 (即 _pool.size())。新建的连接使用完以后才会存到 _pool 中保存,所以这个限制是在连接使用完成以后才判断,并不能限制到 host 的最大连接。connPoolMaxShardedConnsPerHostconnPoolMaxConnsPerHost 就是设置的这个值。所以,设置上述两个参数为 0 并不影响 mongos 的功能,只是每次都创建新的连接,之后马上销毁。

_maxInUse : 3.6 版本新增的成员,用于控制到指定 host 最大可用的连接数。请注意:最大可用连接 = _checkedOut + _pool.size() ,即 _maxInUse 限制的 正在使用的连接和 空闲连接池中连接数之和 的大小 。connPoolMaxInUseConnsPerHostconnPoolMaxShardedInUseConnsPerHost 参数就是设置的这个变量。这个限制是在新建连接时判断的,到达此限制以后,打印日志 :

Too many in-use connections; waiting until there are fewer than xxx

所以如果设置上述参数为 0,那么所有用到传统连接池的地方都不能新建连接,mongos 也就不能正常使用了。

传统连接池的使用

mongos 构造了两个全局 DBConnectionPool 对象 :shardConnectionPoolglobalConnPool

  • ShardConnection 是封装从 DBConnectionPool (shardConnectionPool ) 取连接的结构。使用方法如下

    ShardConnection conn("myserver")
    conn->runCommand()
    

    在 3.2 和 3.4 版本,重要的 CRUD 操作中,mongos 执行的 findAndModify / insert / update / delete 等命令都是通过 ShardConnection 从 shardConnectionPool 传统连接池取的连接。另外也有个别 command 是使用 shardConnectionPool 连接池 ( 详见后文汇总表格 )。

  • ScopedDbConnection 是封装的从 DBConnectionPool ( globalConnPool ) 取连接的结构 。 使用方法如下

    ScopedDbConnection c("myserver");
    c.conn()
    

    大部分的 command 命令都是使用 globalConnPool 连接池执行命令(详见后文汇总表格),另外 globalConnPool 有一个重要的使用场景:mongos 对集群的所有的 分片和 config , 都会周期性(30s)的执行 isMaster 命令,并以此更新分片主从的信息。这里执行isMaster 命令,就是使用 globalConnPool传统连接池执行的。

注意,以单节点 或者 rs 复制集从传统连接池取连接时,有些许区别。最常见的 findAndModify 命令,就是 以 rs 复制集保存的连接,当然最终的执行命令,实际是一个到 primary 的连接。

	ShardConnection/ScopedDbConnection  conn("rsname/server1,server2,server3");  
	conn->runCommand() // 此时才建立到某个节点的连接。

	ShardConnection/ScopedDbConnection  conn("server1");   // 已经建立连接。

传统连接池内的连接执行命令不会创建新的线程,是在 mongos 接收的连接创建的线程上执行的,所以不会消耗额外的内存空间。
###ASIO 的连接池

相关结构

ConnectionPool 是 ASIO 的连接池结构,主要成员是一个 unordered_map ,对每个 host 由一个 SpecificPool 管理所有的连接。

		stdx::unordered_map> _pools;
		struct Options {
		    Options() {}
		    size_t minConnections = kDefaultMinConns; //最小连接
		    size_t maxConnections = kDefaultMaxConns; // 最大连接
		    size_t maxConnecting = kDefaultMaxConnecting; // 同时建立连接的最大个数    (3.2.18新增)
		    Milliseconds refreshTimeout = kDefaultRefreshTimeout; // Refresh 超时时间 
		    Milliseconds refreshRequirement = kDefaultRefreshRequirement; //Refresh 周期
		    Milliseconds hostTimeout = kDefaultHostTimeout; // 空闲状态超时时间
		};
		const Options _options;

SpecificPool: 主要成员是四个 pool ,代表四种不同的状态:已经建立好的可用连接,正在创建中的连接,正在使用中的连接和准备删除的连接。每个 pool 是一个 unordered_map 结构,unordered_map 的 value 就是一个到 host 的连接。到一个 host 的连接数是四种连接的和,但是 _droppedProcessingPool 一般是一种中间状态,很快会被清除,所以具体计算到 一个 host 的连接数时可只算 available+refreshing+inUse 三部分。

        using OwnedConnection = std::unique_ptr;
        using OwnershipPool = stdx::unordered_map;
        
        OwnershipPool _readyPool;  已经建立好的连接    //这个就是available
        OwnershipPool _processingPool;   正在创建中的连接  // refreshing
        OwnershipPool _checkedOutPool;  正在使用的连接   //这个是 inUse
        OwnershipPool _droppedProcessingPool;  准备删除的连接 

Options : 设置了连接池的最大连接,最小连接,刷新时间,超时时间等限制。其中建立新的连接的逻辑如下:

	  auto target = [&] { //计算需要的最少连接数,肯定超过 minConnections,又不能大于 maxConnections, 同时也要满足请求数的需求。
	        return std::max(
	            _parent->_options.minConnections,
	            std::min(_requests.size() + _checkedOutPool.size(), _parent->_options.maxConnections));
	    };
	
	    // While all of our inflight connections are less than our target
	    while ((_readyPool.size() + _processingPool.size() + _checkedOutPool.size() < target()) &&
	           (_processingPool.size() < _parent->_options.maxConnecting)) { //这里也限制了创建连接的速度
	           // 建立新的连接。
	           ......
	           }

可以看出,建立新的连接时,当前的连接数既要满足最小连接数和最大连接数的限制,同时也要满足请求的要求。比如最大连接数是200,10个连接正在使用,如果当前请求有70个,那么池子内就需要 80个连接。所以后续 while 循环中直接创建满足需求的连接数。同时这里也可以看出,maxConnections 最大连接数的限制是一个硬限制,池子内所有状态(available、refreshing、inUse)的连接肯定不会超过最大连接。另外,maxConnecting 可以限制建立新连接的速度。

使用

每个 TaskExecutor 包含一个ASIO 连接池,通过 TaskExecutor->scheduleRemoteCommand()接口执行的远程命令,都是使用的 asio 连接池。另外通过 ASIO 连接池的连接执行的命令,是在每个 TaskExecutor 创建的一个线程(stdx::thread 创建)执行的,默认的栈空间大小是 8 M 。所以 ASIO 连接池内 mongos 到 mongod 的连接消耗的最大内存是 TaskExecutor 的个数 * 8M 。

TaskExecutor 与 TaskExecutorPool

TaskExecutor 是 mongos 执行任务的调度器,主要包含两部分:

 NetworkInterfaceThreadPool : 负责调度逻辑
 NetworkInterfaceASIO : 网络接口,内部有一个 ASIO 连接池(ConnectionPool)和一个线程

**NetworkInterfaceASIO **: 网络接口,内部有一个 ASIO 连接池(ConnectionPool) 和一个只有一个线程的线程池。通过 TaskExecutor->scheduleRemoteCommand() 执行的命令,就是从 NetworkInterfaceASIO 的连接池内取得到 host 的连接,并在切换到NetworkInterfaceASIO 线程执行对应的命令。

mongos 通过 TaskExecutorPool 管理所有的 TaskExecutor , 包括:

  • 一个 TaskExecutor (Fixed Executor) : 和 ConfigServer 通信,(日志中的 【NetworkInterfaceASIO-ShardRegistry-0】就是这个TaskExecutor 的日志)。
  • 多个 TaskExecutor (匿名 Executor) :和 ShardServer通信 。 taskExecutorPoolSize 参数设置与分片通信的 TaskExecutor 的个数,3.6及以下版本默认值为 cpu 的核数,4.0 版本默认为 1 。(日志中的 【NetworkInterfaceASIO-TaskExecutorPool-1-0】就是这部分 TaskExecutor 的日志 )。

3.2 / 3.4 版本,只有查询是通过 TaskExecutor 执行的,而 3.6 及之后的版本 查询、插入、更新、删除等操作都通过 TaskExecutor 执行 。mongos 采用 RoundRobin 的方式从 TaskExecutorPool 中来选择一个来执行。

connPoolStats 与 shardConnPoolStats 命令

connPoolStats 与 shardConnPoolStats 命令可以查看 mongos/mongod 内部连接池的状态,输出所有连接池( 传统连接池和 ASIO 连接池 ) 的连接情况,以连接池和 host 两个维度统计连接情况。(四种状态)

       "inUse" : 0        //正在使用的连接数
       "available" : 0    //可用的连接数
       "created" : 0      //历史创建过的连接数,包含已经回收的
       "refreshing" : 0   // refresh 状态的连接数

当连接处于idle状态时,mongos会周期性的检查这个连接是否正常,异常时会重连,此时就处于 refresh 状态;创建中的连接也属于 refresh 状态。统计传统连接池的连接情况时,refreshing 状态的连接数代码中写死了 0 ,所以这个结果参考结果不大。

  1. connPoolStats: 查看 TaskExecutor ASIO连接池和 globalConnPool 连接池;

  2. shardConnPoolStats: 打印 shardConnectionPool 连接池的情况。

理论上:

mongos 到一个节点 A 的连接数 = db.runCommand({shardConnPoolStats:1}).hosts[A] + db.runCommand( { connPoolStats:1 } ).hosts[A] 

但是实际统计 globalConnPool 和 shardConnectionPool 连接池的连接情况时,有以下两个问题导致实际的结果和统计值有些偏差:

  • 以 rs 复制集建立的连接实际是一个到 primary 节点的连接,shardConnPoolStats 和 connPoolStats 统计时确会保存到第一个 server 上,造成结果看着有些误差。例如:通过以下方式,从传统连接池获取一个连接
    ShardConnection/ScopedDbConnection conn("mongo1745/10.160.182.94:27017,10.160.183.94:27017,10.160.184.94:27017")
    

shardConnPoolStats 和 connPoolStats 把这一个连接统计到 10.160.182.94:27017,但是 10.160.182.94:27017 不一定是 priamry,导致命令的结果看着是错误的现象。

  • 以 rs 复制集保存的连接,一般实际连到复制集的 primary , 如果 primary 意外挂掉,那些这些连接是马上结束的,但是 DBConnectionPool 保存记录并不会被删除。只有在下次使用时,检测到连接异常,会建立一个新的到新 primary的连接 。所以这里也可能造成 shardConnPoolStats 和 connPoolStats 命令的结果和实际连接(例如 netstat 的结果)有偏差。

连接相关的参数

| 参数 | 设置对象 | 意义 | 备注 | 默认值| Ocean 建议值|
|:-----|:-----|:-----|:-----|:-----|
| taskExecutorPoolSize | TaskExecutorPool | 与 shard 通信的 TaskExecutor的个数 | | 4.0版本 1,4.0 以下版本默认 CPU 核数| mongos绑定的 cpu 核数 |
| ShardingTaskExecutorPoolHostTimeoutMS | TaskExecutor | 空闲连接的超时回收时间 | | 5分钟| 5分钟 |
| ShardingTaskExecutorPoolMaxConnecting | TaskExecutor | asio 连接池同时建立连接的个数 | 3.2.15新增 | 3.6以下 默认 -1 ,3.6 及以上默认 2| 2|
| ShardingTaskExecutorPoolMaxSize| TaskExecutor | asio 连接池对每个host最大连接数 | | 64位int最大值,无限制| 根据应用实际情况调整,暂设 200 |
| ShardingTaskExecutorPoolMinSize | TaskExecutor | asio 连接池对每个host最小连接数 | |1 | 1|
| ShardingTaskExecutorPoolRefreshRequirementMS |TaskExecutor | Refresh 周期 | | 60s | 60s |
| ShardingTaskExecutorPoolRefreshTimeoutMS | TaskExecutor | Refresh 超时时间 | | 20s | 20s |
| connPoolMaxConnsPerHost | globalConnPool | 设置 globalConnPool 中到每个host的最大空闲连接数 | | 200| 200 |
| connPoolMaxInUseConnsPerHost | globalConnPool | globalConnPool 的最大可用连接数 | 3.6.3新增 | 最大值,无限制| 200 |
| globalConnPoolIdleTimeoutMinutes | globalConnPool | globalConnPool 连接池的空闲连接的超时回收时间 | 3.6.3新增|最大值,无限制| 5 分钟 |
| connPoolMaxShardedConnsPerHost | shardConnectionPool | 设置 shardConnectionPool 中到每个host的最大空闲连接数 | |200 | 200 |
| connPoolMaxShardedInUseConnsPerHost | shardConnectionPool | shardConnectionPool 的最大可用连接数 | 3.6.3新增 |最大值,无限制|200 |
| shardedConnPoolIdleTimeoutMinutes | shardConnectionPool | shardConnectionPool 连接池的空闲连接的超时回收时间 |3.6.3新增|最大值,无限制| 5分钟 |
说明:

  • **taskExecutorPoolSize ** : 4.0 版本默认值修改为 1 。之前的版本中,默认是 cpu 核心数。对于 40 核的服务器,一个 mongos 40个 taskExecutor 确实有点多了,而且其中大部分 taskExecutor 都是空闲的状态。但是具体设置几个,要根据应用的使用情况做下评估。

  • ShardingTaskExecutorPoolMaxConnecting : 3.6 版本修改了此参数的默认值为 2,而之前是无穷大,也就是不限制。这里只需理解参数是限制同时创建连接的数量,至于到底设置为多少,还需在实践中观察。

  • **ShardingTaskExecutorPoolMaxSize 与 ShardingTaskExecutorPoolMinSize ** :最大连接数的限制是硬限制。最小连接数是指有请求时,TaskExecutor 的连接池保存的最小连接池,但是空闲连接在 ShardingTaskExecutorPoolHostTimeoutMS 时间以后,会被回收掉,也就是说连接池内的连接数是可以小于最小连接的。

  • 设置合适的超时参数,不满足要求时,mongos 会做修正。

ShardingTaskExecutorPoolHostTimeoutMS > ShardingTaskExecutorPoolRefreshRequirementMS > ShardingTaskExecutorPoolRefreshTimeoutMS
```

  • **connPoolMaxConnsPerHost 和 connPoolMaxShardedConnsPerHost **:可用空闲连接数的限制是软限制,不能限制连接池内到 host 的最大连接数。

  • connPoolMaxShardedInUseConnsPerHost 和 connPoolMaxInUseConnsPerHost : 可以硬性限制到 host 的最大连接数。

  • **globalConnPoolIdleTimeoutMinutes 和 shardedConnPoolIdleTimeoutMinutes **: 默认不设置,建议设置合适的超时回收参数,可以同 ShardingTaskExecutorPoolHostTimeoutMS 一样,默认 300s 。

  • 随着版本的发展,mongos 内部的连接池的管理越来越完善,特别是3.6版本以后的传统连接池。但是,也是从 3.6 开始,重量级的 CRUD 操作都是通过 TaskExecutor 的 ASIO 连接池执行,而传统连接池的使用场景是很少的了。那么这就有点奇怪了,传统连接池更好了,使用场景确表少了,那么完善的意义也就很小了。

总结

  • mongos 默认情况是【每连接每线程 】的模式,3.6 版本新增 【adaptive 线程池】模式。

  • mongos 接收的连接创建的每个线程消耗 1M 的栈内存空间;对于 mongos 创建的到 mongod 的连接,连接本身来说消耗资源时比较少的,主要是 asio 连接池所在的 TaskExecutor 创建的一个单独的线程,默认情况下每个消耗 8M 的栈内存空间。

  • 不同连接池的日志

asio 的连接日志
	建立连接 [modle] Connecting to 10.160.185.84:27019
	连接成功 [modle] Successfully connected to 10.160.185.84:27019, took 9ms (2 connections now open to 10.160.185.84:27019)
	断开连接 [modle] Ending idle connection to host 10.160.185.84:27019 because the pool meets constraints; 1 connections to that host remain open
	其中 【modle】部分可以是 【NetworkInterfaceASIO-ShardRegistry-0】或者【NetworkInterfaceASIO-TaskExecutorPool-x-0】(其中x带边第几个TaskExecutor)

传统连接方式日志
	建立连接  [modle]  creating new connection to: 10.160.187.84:27019
	连接成功  [modle] Successfully connected to 10.160.187.84:27019 (1 connections now open to 10.160.187.84:27019 with a 5 second timeout)
	断开连接  [modle] Ending idle connection to host 10.160.193.23:27017(with timeout of 0 seconds) because the pool meets constraints; 1847 connections to that host remain open
	其中 【model】 部分是 conn+数字
  • shardConnPoolStats 和 connPoolStats 的结果由于本身的误差,仅供参考。必要时,加以甄别。特别是对于 3.2 和 3.4 版本。

  • mongos 内部有两大类连接池(传统连接池和 ASIO 连接池),分为四部分:

  • 传统连接池: shardConnectionPoolglobalConnPool

  • ASIO 连接池: FixedExecutor 和 若干个 匿名 Executor (ArbitraryExecutor)

其中,3.2 / 3.4 版本查询通过 TaskExecutor 的 ASIO 连接池执行 ,insert/update/delete/findAndModify 等更新操作通过传统连接池 (shardConnectionPool )执行;3.6 及以上版本读写操作都是通过 TaskExecutor 的ASIO连接池执行。

除了 CRUD 之外,一些常见的 command使用的连接情况如下表:

|   command   |  3.2    |   3.4   |     3.6 | 4.0 |  
|:-----|:-----|:-----|:-----|:-----|
|  find    |    ArbitraryExecutor    |  ArbitraryExecutor         |     ArbitraryExecutor         |    ArbitraryExecutor         |
|  insert/update/delete/findAndModify |    shardConnectionPool|  shardConnectionPool|     ArbitraryExecutor         |    ArbitraryExecutor     |
|  killOp    |    globalConnPool  |  globalConnPool    |     globalConnPool |    globalConnPool  |
| collStats/collstats     |   未分片表 shardConnectionPool    分片表 globalConnPool|  未分片表 shardConnectionPool    分片表 globalConnPool      |   未分片表 shardConnectionPool    分片表 globalConnPool   |    ArbitraryExecutor          |
| dataSize/datasize    |   未分片表 shardConnectionPool    分片表 globalConnPool|   未分片表 shardConnectionPool    分片表 globalConnPool   |   未分片表 shardConnectionPool    分片表 globalConnPool   |  ArbitraryExecutor    |
|  listCollections    |  globalConnPool      | globalConnPool           |  globalConnPool          |  ArbitraryExecutor         |
|  listIndexes    |    globalConnPool    | globalConnPool         |  globalConnPool          |   ArbitraryExecutor         |
|  dropIndexes/deleteIndexes    | globalConnPool           |  globalConnPool               | ArbitraryExecutor     |  ArbitraryExecutor         |
|   createIndexes   |   globalConnPool         |  globalConnPool     |   ArbitraryExecutor   |  ArbitraryExecutor |
|   shardCollection |  globalConnPool        |  globalConnPool     |  FixedExecutor   |  FixedExecutor       |
|   movePrimary |  globalConnPool         |   globalConnPool     | FixedExecutor            |  FixedExecutor                |
|   dbStats/dbstats |  globalConnPool           |   globalConnPool       |    ArbitraryExecutor     |   ArbitraryExecutor   |
|   currentOp    | globalConnPool      |  globalConnPool   |  ArbitraryExecutor   |  ArbitraryExecutor       |
|   repairDatabase    | globalConnPool  |    globalConnPool   |    无此命令  |  无此命令      |
|   dropDatabase   |  globalConnPool     |    FixedExecutor|  FixedExecutor   | FixedExecutor            |
|   split |  shardConnectionPool    |   FixedExecutor|   FixedExecutor   |   FixedExecutor      |
|   moveChunk   | shardConnectionPool      |  FixedExecutor |  FixedExecutor     |   FixedExecutor        |
|  renameCollection     |   shardConnectionPool   | shardConnectionPool    | shardConnectionPool    |     ArbitraryExecutor      |
|  create     |  shardConnectionPool    |    shardConnectionPool      |   shardConnectionPool        |  FixedExecutor            |
|  copydb     |   shardConnectionPool       |   shardConnectionPool          | shardConnectionPool            |   ArbitraryExecutor   |
|   convertToCapped|    shardConnectionPool  |  shardConnectionPool      |  FixedExecutor    |  ArbitraryExecutor      |
|   splitVector  |   shardConnectionPool   | shardConnectionPool     |   FixedExecutor      |   FixedExecutor   |
|   distinct   |    shardConnectionPool|shardConnectionPool      | ArbitraryExecutor        |   ArbitraryExecutor        |
|   count |  shardConnectionPool|    shardConnectionPool |   ArbitraryExecutor|   ArbitraryExecutor|
|   listDatabases      | FixedExecutor|  FixedExecutor|  FixedExecutor   | FixedExecutor     |
|   addShard     |  FixedExecutor    |    FixedExecutor|   FixedExecutor       |   FixedExecutor          |
|   drop  | 未分片表 shardConnectionPool   分片表 FixedExecutor     | FixedExecutor        | FixedExecutor     |   FixedExecutor          |
```注:有些 command 涉及到可能不止一种连接池,请周知。```


另外,传统连接池 (`globalConnPool`)还有一个重要的使用场合 : mongos 对集群的所有的 分片和 config , 都会周期性(30s)的执行 `isMaster` 命令,并以此更新分片主从的信息。这里执行`isMaster` 命令,就是使用 `globalConnPool`传统连接池执行的。

也正是因为这一场景,在 3.6 及以上版本,设置 connPoolMaxInUseConnsPerHost = 0 ,mongos 不能正常的执行 isMaster 命令获得 config 的主从信息,所以也就不能正常的使用了。 4.0 版本已经不再使用 shardConnectionPool, 所以设置 connPoolMaxShardedInUseConnsPerHost = 0 , 不影响 mongos 的使用。

  • 3.2 / 3.4 版本传统连接池的连接回收策略不是很完善,大量的空闲连接不能及时回收,所以允许的时候重启 mongos 还是很有必要的。

  • 3.2 / 3.4 版本传统连接池不能限制同时到每个 host 的连接; 3.6 及以上版本可以通过 connPoolMaxShardedInUseConnsPerHost 和 connPoolMaxInUseConnsPerHost 控制到每个 host 的连接数。

  • 在本文的最后,我尝试着回答下文章开头提出的问题: 客户端到 mongos 和 mongos 到 mongod 的连接数是什么的关系?是 1: 1 的关系吗?

答:mongos 进来的连接和出去的连接的关系和 mongos 的版本、运行时间、进来的连接数、执行的操作等因素密切相关。

  • 3.2和3.4版本
   一个历时悠久,饱经沧桑的 mongos,传统连接池(`shardConnectionPool`  和 `globalConnPool`)内到每个 host 的连接数可能早已达到 MaxConnsPerHost 的上限,特别是执行较多 findAndModify / insert / update / delete 命令的 mongos , 其 shardConnectionPool 更容易达到上限。那么即使此时 mongos 接收连接很少,确会保持大量出去的连接,是小于 1: 1的 。如果 mongos 进来的连接数很多,但是又不做操作,那么此时进来的连接和出去的连接又是可能大于 1:1 的关系。
   
   刨除上述场景,对新启动的 mongos, 如果创建大量的连接,并且每个连接执行 CRUD 操作,那么当执行查询操作的连接数不超过 taskExecutorPoolSize  * ShardingTaskExecutorPoolMaxSize 的上限时,此时进来的连接和出去的连接基本保持 1:1 的关系。如果超过上限,那么进来的连接和出去的连接是大于 1:1 的关系。
  • 3.6及以上版本
  	3.6 及以上的版本,连接池的使用与回收策略都比较完善,并且查询、更新、删除、插入等操作都是通过 TaskExecutor 的 ASIO 连接池执行的。所以如果 mongos 进来的连接都很繁忙,那么在 taskExecutorPoolSize * ShardingTaskExecutorPoolMaxSize 范围内,进来的连接和出去的连接基本保持 1:1 的关系;否则进来的连接和出去的连接是大于 1:1 的。当然如果进来连接不执行任何操作,那么 mongos 不会创建到 mongod 的连接,此时 mongos 进来的连接和出去的连接是大于 1:1 的。

你可能感兴趣的:(mongos 连接数问题解析)