MongoDB监控之数据库监控

二、数据库层面

2.1 db.serverStatus()

1、锁信息监控

rs0:PRIMARY> db.serverStatus().globalLock
{
	"totalTime" : NumberLong("2651301900000"),      //自上次发生lock以来的时间
	"currentQueue" : {          //锁等待队列信息
		"total" : 0,            //因为锁而产生的排队的总数
		"readers" : 0,          //等待读锁而产生的排队数(kQueuedReader)
		"writers" : 0           //等待写锁而产生的排队数(kQueuedWriter)
	},
	"activeClients" : {         //活跃连接数信息
		"total" : 38,           //当前活跃连接数
		"readers" : 0,          //当前执行读操作的活跃连接数(kActiveReader)
		"writers" : 0           //当前执行写操作的活跃连接数(kActiveWriter)
	}
}

MongoDB锁:("+" 表示兼容,"-"表示互斥)

锁模式 MODE_NONE MODE_IS MODE_IX MODE_S MODE_X
MODE_NONE + + + + +
MODE_IS + + + - -
MODE_IX + + + + -
MODE_S + + - + -
MODE_X + - - - -

MongoDB在加锁时是一个层次性的管理方式:

globalLock --> DBlock --> CollectionLock。

MongoDB wiredtiger是文档级别的锁并发。在读写并发时,具体锁的额实现如下:

写操作

    1. globalLock  (这一层只关注是读还是写,不关注具体是什么LOCK)
    2. DBLock MODE_IX
    3. Colleciotn MODE_IX
    4. pass request to wiredtiger

读操作
    1. globalLock MODE_IS  (这一层只关注是读还是写,不关注具体是什么LOCK)
    2. DBLock MODE_IS
    3. Colleciton MODE_IS
    4. pass request to wiredtiger

整体流程如下:

1.Client发送请求至MongoDB
2.判断Client状态是kQueuedReader或kQueuedWriter
2.获取ticket(globalLock完成)
    正常情况下,如果有没出现锁竞争,所有读写请求都会被pass到存储引擎层
    为了限制存储引擎层并发度,可以设置ticket这个值
    wiredtiger默认限制传递到引擎层面的最大读写并发数均为128
    mmapv1没有ticket的限制
3.Client状态转换为kActiveReader或kActiveWriter
    如果该参数长时间不为0,说明服务现在并发较大,负载较高
    可以考虑SQL优化、升配来处理
4.lockBegin
    加DB、Collection等层次锁
    更底层的锁竞争会间接影响到globalLock

总结:

    serverStatus.globalLock 或者 mongostat (qr|qw ar|aw指标)能查看mongod globalLock的各个指标情况。

    Wiredtiger限制传递到引擎层面的最大读写并发数均为128(合理的经验值,通常无需调整)如果超过这个阈值,排队的请求就会体现在globalLock.currentQueue.readers/writers里。

    如果globalLock.currentQueue.readers/writers个值长时间都不为0(此时globalLock.activeClients.readers/writers肯定是持续接近或等于128的),这也说明你的系统并发太高,或者有长时间占用互斥锁的请求比如前台建索引,可以通过优化单个请求的处理时间(比如建索引来减少COLLSCAN或SORT),或升级后端资源(内存、磁盘IO能力、CPU)来优化。

    globalLock.activeClients.readers/writers 持续不为0(但没达到128,此时currentQueue为空),并且你觉得请求处理已经很慢了,这时也可以考虑上述中寻找具体慢查询并进行优化处理,或者升级资源。

2、连接信息监控

rs0: PRIMARY > db.serverStatus().connections {
    "current": 5,                       //当前连接数
    "available": 814,                   //剩余可以连接数
    "totalCreated": NumberLong(186)     //截止到现在创建连接数
}

3、内存信息监控

rs0:PRIMARY> db.serverStatus().mem
{
	"bits" : 64,                    //64位
	"resident" : 245,               //物理内存消耗
	"virtual" : 1262,               //虚拟内存消耗
	"supported" : true,             //支持显示额外内存信息
	"mapped" : 0,                   //映射内存
	"mappedWithJournal" : 0         //除了映射内存外还包括journal日志消耗的映射内存
}

4、错误信息监控

rs0: PRIMARY > db.serverStatus().asserts {
    "regular": 0,           //服务启动后asserts错误个数
    "warning": 0,           //服务启动后warning个数
    "msg": 0,               //服务启动后message asserts个数
    "user": 22,             //服务启动后user asserts格式
    "rollovers": 0          //服务启动后重置次数
}	

5、网络流量监控

rs0:PRIMARY> db.serverStatus().network
{
	"bytesIn" : NumberLong(1013083142),     //网络入流量
	"bytesOut" : NumberLong(1123552013),    //网络处流量
	"numRequests" : NumberLong(3592562)     //累积请求数
}

2、db.stats()

rs0:PRIMARY> db.stats()
{
	"db" : "test",                          //数据库名
	"collections" : 5,                      //数据库中集合数
	"objects" : 139,                        //数据库预估数据行
	"avgObjSize" : 63.65467625899281,       //平均每行数据大小,单位为bytes
	"dataSize" : 8848,                      //当前数据库数据大小,单位为bytes
	"storageSize" : 1077248,                //当前数据库物理存储大小,单位为bytes
	"numExtents" : 5,                      
	"indexes" : 2,                          
	"indexSize" : 16352,                    //索引空间大小,单位为bytes
	"fileSize" : 67108864,                  //数据库预分配文件大小
	"nsSizeMB" : 16,
	"extentFreeList" : {
		"num" : 1,
		"totalSize" : 32768
	},
	"dataFileVersion" : {
		"major" : 4,
		"minor" : 22
	},
	"ok" : 1
}

3、查看当前活跃会话

3.1 db.currentOp()

在会话1执行db.fsyncLock()
在会话2执行db.cc.insert({"name":"aa"})
在会话3执行db.currentOp()

> db.currentOp()
{
	"inprog" : [
		{
			"desc" : "conn10",
			"threadId" : "0x3fdf860",
			"connectionId" : 10,
			"opid" : 380692,                        //db.killOp使用的就是该opid
			"active" : true,                        //是否活跃
			"secs_running" : 4,                     //执行时间(秒)
			"microsecs_running" : NumberLong(4603324),
			"op" : "insert",                        //执行操作类型
			"ns" : "test.cc",                       //执行操作数据库
			"insert" : {                            //执行操作语句
				"_id" : ObjectId("5bee323020e268b4d947a580"),
				"name" : "aa"
			},
			"client" : "127.0.0.1:42066",           //执行操作客户端
			"numYields" : 0,
			"locks" : {                             //执行操作需要持有锁
				"Global" : "w"
			},
			"waitingForLock" : true,                //是否锁等待 ?
			"lockStats" : {
				"Global" : {
					"acquireCount" : {
						"r" : NumberLong(1),
						"w" : NumberLong(1)
					},
					"acquireWaitCount" : {
						"w" : NumberLong(1)
					},
					"timeAcquiringMicros" : {
						"w" : NumberLong(22503637)
					}
				}
			}
		}
	],
	"fsyncLock" : true,                             //是否全局锁定数据库
	"info" : "use db.fsyncUnlock() to terminate the fsync write/snapshot lock"
}

值得注意的是:

1.断开MongoDB Shell后,连接会关闭,但是连接请求的线程并没有结束,直到命令执行完毕,线程给客户端返回结果时,发现连接已经关闭时才会退出线程。

2.MongoDB并不是发送完killOp后请求就会立刻结束

只有当连接对应的服务线程在代码逻辑上存储了killPending字段时,代码会不断调用该参数检查判断killPending的状态。发送killOp后,请求要执行到下一个【检查点】,判断killPending=1后才会杀掉当前会话。

3.2 杀掉慢会话

> db.killOp(380692)
{ "info" : "attempting to kill op" }

db.killOp(opid)的实现原理如下:

    每个连接对应的服务线程存储了一个killPending的字段,当发送killOp时,会将该字段置1;请求在执行过程中,可以通过不断的调用OperationContext::checkForInterrupt()来检查killPending是否被设置,如果被设置,则线程退出。

    一个请求要支持killOp,必须在请求的处理逻辑里加上checkForInterrupt()检查点才行,否则即使发送了killOp,也只能等待请求完全处理完毕线程才会退出。

猜你喜欢

转载自blog.csdn.net/weixin_37692493/article/details/113757888