Author: linqing
MongoDB 提供 currentOp 命令,列出当前正在执行的查询操作,并提供 killOp 命令,用于中止一些耗时比较长,影响线上业务的操作,作为一种应急手段。
下图是一个 currentOp 命令的输出项之一,用户在获取到 opid 后,调用 killOp() 并没有把这个请求干掉。
opid 在 mongod 里是一个 uint32
类型的整数,当你从 mongo shell 里看到 opid 为负数时,说明你的 mongod 已经成功执行超过21(INT32_MAX)次请求了,相当牛逼。
MongoDB 客户端与server是通过 BSON 来交换数据的,而在 bson 标准里,是没有 uint32
类型的,所以 opid 最终是以 int32
传递给客户端的,shell 拿到这个opid,当这个值超过 INT32_MAX 时,打印出来就是负数了。
MongoDB 3.2.5 之前的确是有这个 bug,没有考虑到负数的情况,在 SERVER-23066 里已经修复了,阿里云上3.2、3.4、4.0 的最新版本均已修复这个问题。
修复的代码也很简单,就是接收到负数opid时,将其转换为 uint32
类型,详见 SERVER-23066 Make killOp accept negative opid
此时 killOp 不成功,已经跟 opid 是否是负数没有关系了,本来在 MongoDB 的设计里,也不是所有操作都能被 kill 的。
MongoDB 一个用户连接,后端对应一个线程,本身一个请求开始后,会有一个线程一直执行,直到技术。能被 killOp 杀掉的请求,是因为请求在执行过程中会检测,是否收到了 kill 信号,如果收到了,就走结束请求的逻辑。所以 killOp 的作用也只是给对应的操作一个 kill 信号标志而已。
SomeCommand::Run()
{
for (someCondition) {
doSomeThing();
if (killOpReceived) { // SomeCommand 主动检测了 killOp 的信号,才能被 kill 掉
break;
}
}
}
运行逻辑很简单、开销很低的命令无需捕获 killOp 信号,这种操作 kill 掉也没什么意义,解决不了根本问题。而复杂命令,比如 find、update、createIndex、aggregation 等操作,可能持续遍历很多条记录,才一定需要具备被 kill 的能力。MongoDB 会在执行这些命令的执行逻辑里加入检查是否收到 kill 命令的逻辑。
不一定,一个操作比如 createIndex,会分为很多步骤,命令解析、加锁、执行具体命令逻辑、释放锁、回包等,只有命令执行到具体执行逻辑里时,killOp 才会生效,如果一个操作还没有成功加上锁,本身每占用什么资源,而且对应的现成也没有执行,killOp 是不会生效的。
正常只读的请求、如 find、listIndexes 都是不会加写锁,但当 MongoDB 开启 profiling 的时候,请求执行超过一定阈值(默认100ms)的请求,会记录到 db.system.profile
capped colleciton 里,写这个集合就需要加意向写锁(w),同时对于 capped collection 的写入,会有一个特殊的 METADATA 互斥写锁(W),有兴趣的研究代码,关键字列在下面.
const ResourceId resourceCappedInFlightForOtherDb =
ResourceId(RESOURCE_METADATA, ResourceId::SINGLETON_CAPPED_IN_FLIGHT_OTHER_DB);
Lock::ResourceLock cappedInsertLockForLocalDb(
txn->lockState(), resourceCappedInFlightForLocalDb, MODE_X);