MongoDB serverStatus.globalLock 深入解析

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS SQL Server Serverless,2-4RCU 50GB 3个月
推荐场景:
云数据库 RDS SQL Server,基础系列 2核4GB
简介: MongoDB的用户在遇到性能问题时,经常会关注到 serverStatus.globalLock 指标,但对指标的含义不是很明确,本文会深入解释下 globalLock 指标的含义。 PRIMARY> db.

MongoDB的用户在遇到性能问题时,经常会关注到 serverStatus.globalLock 指标,但对指标的含义不是很明确,本文会深入解释下 globalLock 指标的含义。

PRIMARY> db.serverStatus().globalLock
{
    "totalTime" : NumberLong("7069085891000"),
    "currentQueue" : {
        "total" : 0,
        "readers" : 0,
        "writers" : 0
    },
    "activeClients" : {
        "total" : 23,
        "readers" : 0,
        "writers" : 0
    }
}

大家可以先看下官方文档 对globalLock的解释 (使用MongoDB遇到问题都请第一时间去查阅官方文档) ,如果中间分析部分的内容读起来有困难,可直接调至最后的总结部分。

globalLock

A document that reports on the database’s lock state.

Generally, the locks document provides more detailed data on lock uses.

globalLock.totalTime

The time, in microseconds, since the database last started and created the globalLock. This is roughly equivalent to total server uptime.

globalLock.currentQueue

A document that provides information concerning the number of operations queued because of a lock.

globalLock.currentQueue.total

The total number of operations queued waiting for the lock (i.e., the sum of globalLock.currentQueue.readers and globalLock.currentQueue.writers).

A consistently small queue, particularly of shorter operations, should cause no concern. The globalLock.activeClients readers and writers information provides contenxt for this data.

globalLock.currentQueue.readers

The number of operations that are currently queued and waiting for the read lock. A consistently small read-queue, particularly of shorter operations, should cause no concern.

globalLock.currentQueue.writers

The number of operations that are currently queued and waiting for the write lock. A consistently small write-queue, particularly of shorter operations, is no cause for concern.

globalLock.activeClients

A document that provides information about the number of connected clients and the read and write operations performed by these clients.

Use this data to provide context for the globalLock.currentQueue data.

globalLock.activeClients.total

The total number of active client connections to the database (i.e., the sum of globalLock.activeClients.readers and globalLock.activeClients.writers).

globalLock.activeClients.readers

The number of the active client connections performing read operations.

globalLock.activeClients.writers

The number of active client connections performing write operations.

Client锁的状态


enum ClientState {  // 枚举常量,标识Client的当前状态
   kInactive, 
   kActiveReader, 
   kActiveWriter, 
   kQueuedReader, 
   kQueuedWriter };

Mongod上每个连接会对应一个Client对象,Client里包含当前锁的状态,初始为 kInactive,根据请求及并发状况的不同,会进入到其他的状态,核心逻辑在 lockGlobalBegin 里实现。

template <bool IsForMMAPV1>
LockResult LockerImpl<IsForMMAPV1>::lockGlobalBegin(LockMode mode) {
    dassert(isLocked() == (_modeForTicket != MODE_NONE));
    if (_modeForTicket == MODE_NONE) {
        const bool reader = isSharedLockMode(mode);
        auto holder = ticketHolders[mode];
        if (holder) {
            _clientState.store(reader ? kQueuedReader : kQueuedWriter);
            holder->waitForTicket();
        }
        _clientState.store(reader ? kActiveReader : kActiveWriter);
        _modeForTicket = mode;
    }
    const LockResult result = lockBegin(resourceIdGlobal, mode);
    if (result == LOCK_OK)
        return LOCK_OK;

    // Currently, deadlock detection does not happen inline with lock acquisition so the only
    // unsuccessful result that the lock manager would return is LOCK_WAITING.
    invariant(result == LOCK_WAITING);

    return result;
}

而 serverStatus.globalLock 其实根据这个锁的状态进行导出

2018-03-13 update

  • 获取 Client 状态时,已经获取到 ticket 的 Reader/Writer 如果在等锁,也会认为是 Queued 状态,这个之前忽略了。
template <bool IsForMMAPV1>
Locker::ClientState LockerImpl<IsForMMAPV1>::getClientState() const {
    auto state = _clientState.load();
    if (state == kActiveReader && hasLockPending())
        state = kQueuedReader;
    if (state == kActiveWriter && hasLockPending())
        state = kQueuedWriter;

    return state;
}
   ret.append("totalTime", (long long)(1000 * (curTimeMillis64() - _started)));

    {
        BSONObjBuilder currentQueueBuilder(ret.subobjStart("currentQueue"));

        currentQueueBuilder.append("total",
                                   clientStatusCounts[Locker::kQueuedReader] +
                                       clientStatusCounts[Locker::kQueuedWriter]);
        currentQueueBuilder.append("readers", clientStatusCounts[Locker::kQueuedReader]);
        currentQueueBuilder.append("writers", clientStatusCounts[Locker::kQueuedWriter]);
        currentQueueBuilder.done();
    }

    {
        BSONObjBuilder activeClientsBuilder(ret.subobjStart("activeClients"));

        activeClientsBuilder.append("total", clientStatusCounts.sum());
        activeClientsBuilder.append("readers", clientStatusCounts[Locker::kActiveReader]);
        activeClientsBuilder.append("writers", clientStatusCounts[Locker::kActiveWriter]);
        activeClientsBuilder.done();
    }

总结一下

globalLock.totalTime = 进程启动后经历的时间
globalLock.currentQueue.total = 下面2者之和
globalLock.currentQueue.readers = kQueuedReader 状态Client总数
globalLock.currentQueue.writers = kQueuedWriter 状态Client总数
globalLock.activerClients.totol = 下面2者之和 + 系统内部的一些Client(比如同步线程)
globalLock.activerClients.readers = kActiveReader 状态Client总数
globalLock.activerClients.writers = kActiveWriter 状态Client总数

详解 globalLock 状态转换

为了方便后续介绍,先科普一下MongoDB的层次锁模型

锁的模式


/**
 * Lock modes.
 *
 * Compatibility Matrix
 *                                          Granted mode
 *   ---------------.--------------------------------------------------------.
 *   Requested Mode | MODE_NONE  MODE_IS   MODE_IX  MODE_S   MODE_X  |
 *     MODE_IS      |      +        +         +        +        -    |
 *     MODE_IX      |      +        +         +        -        -    |
 *     MODE_S       |      +        +         -        +        -    |
 *     MODE_X       |      +        -         -        -        -    |
 */

MongoDB 加锁时,有四种模式【MODE_IS、MODE_IX、MODE_S、MODE_X】,MODE_S, MODE_X 很容易理解,分别是互斥读锁、互斥写锁,MODE_IS、MODE_IX是为了实现层次锁模型引入的,称为意向读锁、意向写锁,锁之间的竞争情况如上图所示。

MongoDB在加锁时,是一个层次性的管理方式,从 globalLock ==> DBLock ==> CollecitonLock ... ,比如我们都知道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

根据上图的竞争情况,IS和IX是无需竞争的,所以读写请求可以在没有竞争的情况下,同时传到wiredtiger引擎去处理。

再举个栗子,如果一个前台建索引的操作跟一个读请求并发了

前台建索引操作

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

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

根据竞争表,MODE_X和MODE_IS是要竞争的,这也就是为什么前台建索引的过程中读是被阻塞的。

我们今天介绍的 globalLock 对应上述的第一步,在globalLock这一层,只关心是读锁、还是写锁,不关心是互斥锁还是意向锁,所以 globalLock 这一层是不存在竞争的。那么 globalLock 里的几个指标到底意味着什么?

从上述的代码可以发现,globalLockBegin里(基本所有的数据库读写请求都要走这个路径)决定了globalLock的状态转换,核心逻辑如下

 template <bool IsForMMAPV1>
LockResult LockerImpl<IsForMMAPV1>::lockGlobalBegin(LockMode mode) {

   const bool reader = isSharedLockMode(mode);
    auto holder = ticketHolders[mode];
    if (holder) {
          _clientState.store(reader ? kQueuedReader : kQueuedWriter);
                holder->waitForTicket();
       }
   _clientState.store(reader ? kActiveReader : kActiveWriter);
   
   ....
   const LockResult result = lockBegin(resourceIdGlobal, mode);
    if (result == LOCK_OK)
        return LOCK_OK;
   ...
 }

上述代码里,如果holder不为空,Client会先进去kQueuedReader或kQueuedWriter状态,然后获取一个ticket,获取到后转换为kActiveReader或kActiveWriter状态。这里的ticket是什么东西?

这里的ticket是引擎可以设置的一个限制。正常情况下,如果没有锁竞争,所有的读写请求都会被pass到引擎层,这样就有个问题,你请求到了引擎层面,还是得排队执行,而且不同引擎处理能力肯定也不同,于是引擎层就可以通过设置这个ticket,来限制一下传到引擎层面的最大并发数。比如

  • wiredtiger设置了读写ticket均为128,也就是说wiredtiger引擎层最多支持128的读写并发(这个值经过测试是非常合理的经验值,无需修改)。
  • mmapv1引擎并没有设置ticket的限制,也就是说用mmapv1引擎时,globalLock的currentQueue会一直是0.

globalLock完成后,client就进入了kActiveReader或kActiveWriter中的一种状态,这个就对应了globalLock.activerClients字段里的指标,接下来才开始lockBegin,加DB、Collection等层次锁,更底层的锁竞争会间接影响到globalLock。

总结

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

  1. Wiredtiger限制传递到引擎层面的最大读写并发数均为128(合理的经验值,通常无需调整),如果超过这个阈值,排队的请求就会体现在globalLock.currentQueue.readers/writers里。
  2. 如果globalLock.currentQueue.readers/writers个值长时间都不为0(此时globalLock.activeClients.readers/writers肯定是持续接近或等于128的),说明你的系统并发太高(或者有长时间占用互斥锁的请求比如前台建索引),可以通过优化单个请求的处理时间(比如建索引来减少COLLSCAN或SORT),或升级后端资源(内存、磁盘IO能力、CPU)来优化。
  3. globalLock.activeClients.readers/writers 持续不为0(但没达到128,此时currentQueue为空),并且你觉得请求处理已经很慢了,这时也可以考虑2中提到的优化方法。
相关实践学习
MongoDB数据库入门
MongoDB数据库入门实验。
快速掌握 MongoDB 数据库
本课程主要讲解MongoDB数据库的基本知识,包括MongoDB数据库的安装、配置、服务的启动、数据的CRUD操作函数使用、MongoDB索引的使用(唯一索引、地理索引、过期索引、全文索引等)、MapReduce操作实现、用户管理、Java对MongoDB的操作支持(基于2.x驱动与3.x驱动的完全讲解)。 通过学习此课程,读者将具备MongoDB数据库的开发能力,并且能够使用MongoDB进行项目开发。 &nbsp; 相关的阿里云产品:云数据库 MongoDB版 云数据库MongoDB版支持ReplicaSet和Sharding两种部署架构,具备安全审计,时间点备份等多项企业能力。在互联网、物联网、游戏、金融等领域被广泛采用。 云数据库MongoDB版(ApsaraDB for MongoDB)完全兼容MongoDB协议,基于飞天分布式系统和高可靠存储引擎,提供多节点高可用架构、弹性扩容、容灾、备份回滚、性能优化等解决方案。 产品详情: https://www.aliyun.com/product/mongodb
相关文章
|
4月前
|
运维 监控 NoSQL
【MongoDB 复制集秘籍】Secondary 同步慢怎么办?深度解析与实战指南,让你的数据库飞速同步!
【8月更文挑战第24天】本文通过一个具体案例探讨了MongoDB复制集中Secondary成员同步缓慢的问题。现象表现为数据延迟增加,影响业务运行。经分析,可能的原因包括硬件资源不足、网络状况不佳、复制日志错误等。解决策略涵盖优化硬件(如增加内存、升级CPU)、调整网络配置以减少延迟以及优化MongoDB配置(例如调整`oplogSize`、启用压缩)。通过这些方法可有效提升同步效率,保证系统的稳定性和性能。
114 4
|
1月前
|
存储 NoSQL MongoDB
MongoDB面试专题33道解析
大家好,我是 V 哥。今天为大家整理了 MongoDB 面试题,涵盖 NoSQL 数据库基础、MongoDB 的核心概念、集群与分片、备份恢复、性能优化等内容。这些题目和解答不仅适合面试准备,也是日常工作中深入理解 MongoDB 的宝贵资料。希望对大家有所帮助!
|
6月前
|
存储 监控 NoSQL
MongoDB索引解析:工作原理、类型选择及优化策略
MongoDB索引解析:工作原理、类型选择及优化策略
|
2月前
|
存储 NoSQL MongoDB
MongoDB 概念解析
10月更文挑战第12天
45 0
MongoDB 概念解析
|
4月前
|
C# 开发者 Windows
全面指南:WPF无障碍设计从入门到精通——让每一个用户都能无障碍地享受你的应用,从自动化属性到焦点导航的最佳实践
【8月更文挑战第31天】为了确保Windows Presentation Foundation (WPF) 应用程序对所有用户都具备无障碍性,开发者需关注无障碍设计原则。这不仅是法律要求,更是社会责任,旨在让技术更人性化,惠及包括视障、听障及行动受限等用户群体。
97 0
|
6月前
|
存储 JSON NoSQL
深入解析MongoDB的存储原理
深入解析MongoDB的存储原理
深入解析MongoDB的存储原理
|
6月前
|
SQL NoSQL 关系型数据库
ClickHouse(24)ClickHouse集成mongodb表引擎详细解析
**MongoDB引擎在ClickHouse中提供只读访问远程数据,用于`SELECT`查询。不支持写入。创建MongoDB表引擎的语法:`CREATE TABLE ... ENGINE = MongoDB(host, db, coll, user, pass)`。例如:**查看[ClickHouse中文文档](https://zhangfeidezhu.com/?p=468)获取更多教程,包括系列文章覆盖的各种表引擎解析。
165 0
|
8天前
|
存储 JSON NoSQL
学习 MongoDB:打开强大的数据库技术大门
MongoDB 是一个基于分布式文件存储的文档数据库,由 C++ 编写,旨在为 Web 应用提供可扩展的高性能数据存储解决方案。它与 MySQL 类似,但使用文档结构而非表结构。核心概念包括:数据库(Database)、集合(Collection)、文档(Document)和字段(Field)。MongoDB 使用 BSON 格式存储数据,支持多种数据类型,如字符串、整数、数组等,并通过二进制编码实现高效存储和传输。BSON 文档结构类似 JSON,但更紧凑,适合网络传输。
42 15
|
16天前
|
存储 NoSQL 关系型数据库
阿里云数据库MongoDB版助力信也科技 打造互联网金融企业样板
我们的风控系统引入阿里云数据库MongoDB版后,解决了特征类字段灵活加减的问题,大大提高了开发效率,极大的提升了业务用户体验,获得了非常好的效果
阿里云数据库MongoDB版助力信也科技 打造互联网金融企业样板
|
1月前
|
NoSQL Cloud Native atlas
探索云原生数据库:MongoDB Atlas 的实践与思考
【10月更文挑战第21天】本文探讨了MongoDB Atlas的核心特性、实践应用及对云原生数据库未来的思考。MongoDB Atlas作为MongoDB的云原生版本,提供全球分布式、完全托管、弹性伸缩和安全合规等优势,支持快速部署、数据全球化、自动化运维和灵活定价。文章还讨论了云原生数据库的未来趋势,如架构灵活性、智能化运维和混合云支持,并分享了实施MongoDB Atlas的最佳实践。

推荐镜像

更多