使用 MongoDB 时,可能会遇到因为 mongod 连接数用满了,导致客户端无法连接的问题。mongod 的最大连接数通过net.maxIncomingConnections
指定,默认值为 1000000,相当于没有限制,生产环境强烈建议根据实际需求配置,以避免客户端误用导致 mongod 负载过高。
为什么需要限制连接数?
连接是要消耗资源的,而且消耗的并不少。mongod 的服务模型是每个网络连接由一个单独的线程来处理,每个线程配置了 1MB 的栈空间,当网络连接数太多时,过多的线程会导致上下文切换开销变大,同时内存开销也会上涨。
- 内存:MongoDB为例,每个线程都要分配1MB的栈内存出来。1000个连接,1G内存就这么没了,甭管是否是活跃连接。
- 文件句柄:每个连接都要打开一个文件句柄,当然从成本上讲,这个消耗相对内存是小了很多。但换个角度,文件句柄也被其他模块消耗着,比如WT存储引擎,就需要消耗大量的文件句柄。
是否真的需要这么多的链接,一般的业务场景下请求压力在1000QPS左右,按照每个请求50ms计算,最多也就需要1000/(1000/50)==50个链接即可满足需求,并且是整个系统50个链接即可。
客户端驱动如何使用?
MongoDB 各个语言的 Driver 基本都会封装包含一个 MongoClient 的对象(不同语言的 Driver 名字可能稍有不同),通常应用在使用时通过 MongoDB connection string URI 来构造一个全局的 MongoClient,然后在后续的请求中使用该全局对象来发送请求给 mongod。
通常每个 MongoClient 会包含一个连接池,默认大小为 100,也可以在构造 MongoClient 的时候通过 maxPoolSize 选项来指定。
一种典型的错误使用方式是,用户为每个请求都构造一个 MongoClient,请求结束释放 MongoClient(或根本没释放),这样做问题是请求模型从长连接变成了短连接,每次短连接都会增加『建立 tcp 连接 + MongoDB 鉴权』的开销,并且并发的请求数会受限于连接数限制,极大的影响性能;另外如果 MongoClient 忘记释放,会导致 MongoClient 连接池里连接一直保持着,最终耗光所有的可用连接。
通常 MongoClient 使用默认100的连接池(具体默认值以 Driver 的文档为准)都没问题,当访问同一个 mongod 的源比较多时,则需要合理的规划连接池大小。
举个例子,mongod 的连接数限制为 2000,应用业务上有 40 个服务进程可能同时访问这个 mongod,这时每个进程里的 MongoClient 的连接数则应该限制在 2000/40 = 50 以下 (连接复制集时,MongoClient 还要跟复制集的每个成员建立一条连接,用于监控复制集后端角色的变化情况)。
查看最大连接数
1 2 |
> db.serverStatus().connections { "current" : 1, "available" : 818, "totalCreated" : NumberLong(1) } |
其中 available 表示空闲可用的连接数。current 表示已经占用了的连接数,两者相加就是实例的最大连接数。
但这个连接数也可以修改,只要在启动的时候加入--maxConns=2000
即可。如果再次查询最大连接数发现没有增加到 2000,那么其实是 Linux 默认进程能打开最大文件数有关,可以通过 ulimit 解决。
1 |
$ ulimit -n 25000 |
查看实例进程信息
返回包含在数据库实例正在进行的操作信息的文档。db.currentOp()
方法包装数据库命令currentOp
。
1 |
<span class="nx">db</span><span class="p">.</span><span class="nx">currentOp</span><span class="p">(</span><span class="o"><</span><span class="nx">operations</span><span class="o">></span><span class="p">)</span> |
db.currentOp() 可以接受的过滤器文档或布尔参数。
如果将过滤器文档传递给 db.currentOp(),则输出仅返回与过滤器匹配的当前操作的信息。筛选器文档可以包含:
Field | Description |
---|---|
"$ownOps" |
布尔值。如果设置为true,则只返回当前用户的操作信息。 用户可随时操作 db.currentOp( { “$ownOps”: true } ) 来查看自己的操作。 New in version 3.2.9. |
"$all" |
布尔值。如果设置为true,则返回所有操作信息,包括空闲连接和系统连接的操作。
|
<filter> | 指定在输出字段过滤条件。见例子。 |
另外,下面两个操作是等价的:
1 2 |
db.currentOp(true) db.currentOp({"$all": true}) |
currentOp 和数据库 profile 报告信息基本相同,主要包含以下所有 CRUD 操作:
aggregate
count
delete
distinct
find
(OP_QUERY andcommand
)findAndModify
getMore
(OP_GET_MORE andcommand
)insert
mapReduce
update
结果集解释:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
{ "host" : "g02b01248.cloud.nm89:3002", "desc" : "conn36279193", // 数据库的连接描述 "connectionId" : 36279193, // 数据库的连接ID "client" : "192.168.17.2:36692", // 客户端地址 "appName" : "MongoDB Shell", // 客户端名称 "clientMetadata" : { // 客户端元数据信息 "application" : { "name" : "MongoDB Shell" }, "driver" : { "name" : "MongoDB Internal Client", "version" : "4.0.9" }, "os" : { "type" : "Linux", "name" : "CentOS Linux release 7.6.1810 (Core) ", "architecture" : "x86_64", "version" : "Kernel 3.10.0-957.5.1.el7.x86_64" } }, "active" : true, // 进程是否是活动状态 "currentOpTime" : "2020-04-15T12:19:23.688+0800", "opid" : 71126194, // 进程ID "lsid" : { "id" : UUID("99ca88d9-cef1-4bc4-8da8-d8cbd4c9efc3"), "uid" : BinData(0,"Y5mrDaxi8gv8RmdTsQ+1j7fmkr7JUsabhNmXAheU0fg=") }, "secs_running" : NumberLong(169), // 操作运行了多少秒 "microsecs_running" : NumberLong(169395967), "op" : "query", // 操作类型 "ns" : "notice.notice_message", // 名称空间 "command" : { // 操作命令 "find" : "notice_message", "filter" : { "tpl" : 1 }, "limit" : 100, "singleBatch" : false, "lsid" : { "id" : UUID("99ca88d9-cef1-4bc4-8da8-d8cbd4c9efc3") }, "$clusterTime" : { "clusterTime" : Timestamp(1586924192, 2926), "signature" : { "hash" : BinData(0,"omQve8youMAlCCqBJslmnOlA884="), "keyId" : NumberLong("6785866474469720065") } }, "$readPreference" : { "mode" : "secondaryPreferred" }, "$db" : "notice" }, "planSummary" : "COLLSCAN", "numYields" : 914722, "locks" : { "Global" : "r", "Database" : "r", "Collection" : "r" }, "waitingForLock" : false, // 是否等待锁 "lockStats" : { // 持有锁时间(微秒) "Global" : { "acquireCount" : { "r" : NumberLong(914723) } }, "Database" : { "acquireCount" : { "r" : NumberLong(914723) } }, "Collection" : { "acquireCount" : { "r" : NumberLong(914723) } } } } |
示例
下面的示例使用具有各种查询文件 db.currentOp 方法来过滤输出。
Write Operations Waiting for a Lock
下面的示例返回正在等待锁的所有写操作的信息:
1 2 3 4 5 6 7 8 9 |
db.currentOp( { "waitingForLock" : true, $or: [ { "op" : { "$in" : [ "insert", "update", "remove" ] } }, { "query.findandmodify": { $exists: true } } ] } ) |
Active Operations with no Yields
下面的示例返回从未取得所有活动正在运行的操作:
1 2 3 4 5 6 7 |
db.currentOp( { "active" : true, "numYields" : 0, "waitingForLock" : false } ) |
Active Operations on a Specific Database
下面的示例返回对数据库DB1所有的活动操作已运行3秒以上信息:
1 2 3 4 5 6 7 |
db.currentOp( { "active" : true, "secs_running" : { "$gt" : 3 }, "ns" : /^db1\./ } ) |
Active Indexing Operations
下面的示例返回正在创建索引操作的信息:
1 2 3 4 5 6 7 8 |
db.currentOp( { $or: [ { op: "command", "query.createIndexes": { $exists: true } }, { op: "none", ns: /\.system\.indexes\b/ } ] } ) |
用JS处理结果集
1 2 3 |
db.currentOp(true).inprog.forEach((item) => { print(item.client) }) |
杀掉进程
先执行 db.currentOp() 获取进程号,类似 ps -ef。
1 |
> db.killOP(2920488) |
查看最近错误
1 |
> db.getLastError() |
1 2 3 4 5 |
var ops = db.currentOp({ "$all": true , "ns" : /^admin\./, "secs_running" : { "$gte" : 0 }}).inprog for(i = 0; i < ops.length; i++){ var opid = ops[i].opid; print('db.killOP(' + opid +')') } |
<参考>
https://docs.mongodb.com/manual/reference/command/currentOp
https://docs.mongodb.com/manual/reference/method/db.currentOp/#db.currentOp