Mongos连接模型探究
经常有同学会问, client/mongos/mongod之间的连接模型是怎样的关系,一个客户端连接对应多少个对后端mongod的连接。这个问题是有意义的,因为我们知道,client到mongod之间的连接,是 one-thread-per-connection的模式的,而且每个连接线程默认分配1MB内存,一千个连接就是1GB的内存; 而且活跃连接多了,内核态的线程切换引起的性能开销又是一个让人头痛的问题。one-thread-per-connection的模型相当传统(落后),该模型将线程切换/调度交给操作系统管理,带来的结果就是:延迟不可控。不过mongos接入层的引入,较好的缓解了该问题,本文主要介绍mongos和mongod之间的连接池模型,以及调优参数项。
连接模型
mongos作为client和mongod之间的中间层,需要管理两方面的连接
- client对mongos的连接
- mongos对mongod分片以及每个副本集的连接
mongos对client的连接模型
client对mongos的连接采用one-thread-per-connection的模式,listener线程负责accept到新的连接,每个连接新分配一个线程来处理,线程栈大小默认为1MB。
总结,mongos对客户端的连接模型是采用one-thread-per-connection的经典方式。 另外,该方式有一个较为严重的问题:在活跃连接较多的场景下,会引起较多的线程切换,导致处理延时难以量化。
mongos对后端分片的连接模型
mongos对后端分片mongod的连接模型如下图。 mongos采用ASIO网络框架,每个用户请求通过网络实践回调/异步状态机的Reactor模型驱动。ReactorPool被划分为N个ReactWorker,每个Worker对等的处理用户请求。每个Worker中有M个连接池(M=后端所有mongod的个数)。如果后端有3分片,每个分片3副本,则每个Worker中管理9个连接池。
用户请求被RoundRobin到ReactWorker中,如下代码所示:
mongos连接池可调优参数
mongos连接池可调优参数主要在 src/mongo/executor/connectionpool.h 和 src/mongo/executor/connectionpool.cpp 中。默认值为
连接池的细分
上面我们说过,mongos有一个ASIO的ReactorPool,每个Pool有有若干个连接池,每个连接池负责管理某个特定的mongod的连接。 每个连接池又分为 1. readyPool (管理空闲连接) 2. processingPool (管理在创建中/定期检查健康状态的连接) 3. checkoutPool (管理正在使用中的连接)
对于一个特定的连接,它在三个池中的状态转移关系如下图。
- minConnections 为默认为1,表示每个worker默认对每个mongod初始连接是1,且如果readyPool中连接过多,最终会将readyPool连接个数收敛到1
- maxConnections 默认没做限制,表示每个worker不限制对后端mongod的连接个数
- refreshTimeout 和 refreshRequirement, 每个连接有最长IdleTime,大小为refreshRequirement,默认为5分钟,超过这个值,如果当前连接个数大于minConnections,就会丢弃该连接,否则对该连接进行heartBeat后重新放回readyPool。refreshTimeout参数不重要,表示与mongod进行heartBeat的超时时间。
相关代码如下所示:
hostTimeout 如果该连接池长时间没有处理任何请求,就将该连接池全部释放掉。
ASIO ReactorWorkerPool大小
mongodb 提供taskExecutorPoolSize参数挑中mongos的ReactorPool的大小。用户可以通过setParameter参数,在mongos的配置文件中指定。该参数默认没有指定,因此mongos默认的workerPool大小为cpu核心个数(cat /proc/cpuinfo)。
有价值的调优项
1. minConnection和 refreshRequirement
minConnection值默认为1, 举个例子,如果在一个8核机器上默认配置部署mongos,则mongos对后端每个mongod有1X8=8个连接,如果是3副本,2分片,则总用有1X8X3X2=48个连接。如果有流量峰值,或后端mongod处理不过来,则会创建新的连接,峰值过去后,多余的连接会在refreshRequrement指定的时间后释放。 如果对业务平均响应和平均峰值间隔有个合理的预估,就可以有把握的调整这两个值的大小。 举个例子,如果两分片,平均写入响应时间在10ms,写入TPS要求为1000。峰值间隔为5分钟。则minConnection最好设置大于 1000/2(两副本)/100(10ms)=5,从而防止冷启动带来的延迟开销。refreshRequirement = 5X2 = 10分钟,idle回收时间大于业务峰值间隔。从而避免峰值时创建新的连接。
2. maxConnection
这个值默认没有做限制,这样非常容易使mongos对mongod造成connection-flood。如果mongod请求处理超过客户端超时。客户端对mongos发起重试,mongos对mongod的请求还在checkoutPool中没有回收,readyPool中不够分配,只能创建新连接处理客户端的重试,重试依然超时,如此往复导致对mongod的连接数暴增。 这个值建议通过最大QPS/TPS来估算。以写为例: maxConnection = maxTPS / nShards / nMongos / taskExecutorPoolSize
3. taskExecutorPoolSize 与mongos部署模式
mongos默认不适合单机多部署,因为asioWorker的个数等于CPU核心个数。如果单机多部署,则需要考虑线程切换带来的影响。比较合理的mongos单机部署多方式是使用cgroups或taskset将mongos binding到对应的核上。并且binding的核心个数 = taskExecutorPooSize
文章转载自公众号: Mongoing中文社区