ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MongoDB 上下文单例架构解析:ServiceContext、Client 与 OperationContext 的协作机制

MongoDB 上下文单例架构解析:ServiceContext、Client 与 OperationContext 的协作机制 MongoDB 上下文单例架构解析ServiceContext、Client 与 OperationContext 的协作机制【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo本篇技术指南以 MongoDB 官方文档 docs/contexts.md 为主体系统讲解 MongoDB 服务器mongod与mongos中用于管理全部操作状态的三个核心单例式上下文类——ServiceContext、Client与OperationContext——的职责划分、生命周期、并发语义与可中断机制。读者读完本文将掌握MongoDB 服务器进程如何从一条网络连接组织到一次查询操作的完整上下文链客户端锁Client lock的读写规则以及操作如何被killOp、主节点降级或超时中断的内部原理。概述进程、连接与操作的三层上下文模型MongoDB 服务器进程mongod或mongos上执行的所有操作其状态都由一个全局单例ServiceContext统一跟踪与管理。围绕它存在清晰的层级关系ServiceContext一个服务器进程的状态容器 └── 管理任意数量的 Client每条逻辑连接一个 └── 管理任意数量的 OperationContext每个操作一个ServiceContext代表单个 MongoDB 服务器进程的全部状态Client代表一条到数据库的逻辑连接可能是一个用户也可能是一个需要执行命令或查询的内部进程操作在连接之上执行OperationContext管理单个操作如一次查询或一条命令从开始到完成或取消的整个生命周期。三者之间最关键的不变式是一个Client同一时刻只能执行一个逻辑操作因此同一时刻最多只能持有一个OperationContext。因为一个Client上的操作是串行执行的所以它会在生命周期内依次创建并销毁多个OperationContext。值得注意的是这三个类都被高度装饰化decorated——它们均继承自Decorable参见 decorable.h这意味着可以通过装饰机制在运行时动态地向这些上下文对象附加任意类型的数据而无需修改类本身。从源码可见client.h 中class Client final : public DecorableClientoperation_context.h 中class OperationContext final : public OperationContextBase, public Interruptible, public DecorableOperationContext装饰机制是贯穿整个上下文体系的基础设施。ServiceContext进程级全局状态容器ServiceContext表示单个 Mongo 服务器进程mongod或mongos的全部状态。它的职责远不止创建和管理Client与OperationContext还负责TransportLayer执行网络操作mongo/transport子系统PeriodicRunner周期性执行后台维护任务housekeepingStorageEngine与真正的数据库存储引擎交互一组时间源time sources为服务器提供统一的时钟与计时基准。从 service_context.h 的接口可以看出ServiceContext通过setStorageEngine/getStorageEngine持有存储引擎实例其头文件还引入了mongo/transport/session.h、mongo/util/periodic_runner.h、mongo/util/clock_source.h、mongo/util/tick_source.h等分别对应上述职责。全局 ServiceContext 的生命周期一般情况下每个 Mongo 服务器进程只有一个ServiceContext即全局ServiceContextglobalServiceContext。它在该进程初始化阶段被创建仅在该进程关闭shutdown时被销毁因此在整个服务器运行期间始终可用。全局ServiceContext的创建与设置通过setGlobalServiceContextservice_context.h完成该函数同时负责在传入空指针时取消并删除当前的全局ServiceContext在关闭shutdown阶段全局ServiceContext会杀死所有未完成的OperationContext和Client除了服务器初始化和关闭之外全局ServiceContext的典型使用场景包括按线程或操作查找Client/OperationContext信息以及在例如主节点降级primary step-down时杀死一个或多个正在运行的操作。从源码看杀死全部操作的能力由setKillAllOperations提供service_context.h它会向除排除列表excludedClientPredicate指定的 Client之外的所有OperationContext发出 kill 信号对应的getKillAllOperations用于查询当前是否处于全局 kill 状态service_context.h。获取 ServiceContext 的三种方式ServiceContext与给定Client的关联可以通过几种方式获取官方建议优先使用Client::getServiceContext()方式语义建议Client::getServiceContext()通过当前 Client 反查其所属的 ServiceContextclient.h首选ServiceContext::getCurrentServiceContext()返回与当前 Client 关联的 ServiceContext若无当前 Client 则返回nullptrservice_context.h推荐ServiceContext::getGlobalServiceContext()直接返回进程级全局单例若无则触发 fatalservice_context.h尽量避免直接依赖之所以要偏向Client::getServiceContext()或ServiceContext::getCurrentServiceContext()而不是getGlobalServiceContext()虽然截至撰写本文时每个服务器进程只维护一个ServiceContext但优先使用前两者可以在未来需要时更容易地支持每个服务器进程维护多个ServiceContext。ServiceContext::make工厂函数service_context.h是创建实例的唯一途径它允许注入 fast/precise 时钟源与 tick source。Client数据库视角下的外部客户端每个到 Mongo 服务的逻辑连接都由一个Client对象管理。所谓逻辑连接可以是一个用户也可以是需要在数据库上执行命令或查询的内部进程。正如 client.h 文件头注释所述Client 代表到数据库的连接服务器端视角对应来自客户端的一个已打开的 socket或在 socket 上复用连接时的逻辑连接。Client 的构造方式Client对象的构造通常有两种途径调用全局ServiceContext上的makeClient更准确地说是调用Service::makeClient见 service_context.h其内部委托给ServiceContext::makeClientForService创建一个Client并返回ServiceContext::UniqueClient智能指针随后可以把它绑定到任意线程上执行调用Client::initThreadclient.h在全局ServiceContext上构造一个Client并将其绑定到当前线程存入当前线程的 TLS还可通过参数指定描述字符串、关联的Service、可选的transport::Session、是否可被降级stepdown杀死ClientOperationKillableByStepdown、以及是否在关闭时被排除在全局中断之外ClientExcludedFromInterruptAtShutdown。Client构造函数的最后一个关键参数是sessionclient.h若传入非空的transport::Session则该Client的所有操作都将在Session所管理的网络连接上串行执行Session在Client构造时传入若没有传入Session则该Client被视为操作于本地数据库local database不会执行任何网络操作。这类Client有时被称为 local clients常用于 Mongo 服务查询自身数据库的场景。Client在生命周期内通常会执行多个操作为每个操作派生一个OperationContext。由于这些操作串行执行每个Client在同一时刻最多只与一个OperationContext关联。源码中Client::makeOperationContext()client.h即为创建新操作上下文的入口其注释明确一个客户端上同一时刻最多只能有一个 operation context 处于作用域内getOperationContext()client.h用于获取当前活跃的OperationContext。Client 锁The Client lock所有Client都有一个关联的锁SpinLock见 client.h用于保护其内部状态——包括当前关联的OperationContext——免受并发访问的破坏。之所以如此重要是因为OperationContext随时可能被杀死并销毁任何对Client关联OperationContext或其他受保护内部状态的修改操作必须先获取Client锁。例如从 service_context.h 可以看到ClientLock这个 RAII 包装类它正是service_context_detail::ObjectLockClient的别名构造时自动加锁、析构时自动解锁。Client锁的完整语义如下表所示内部状态Client拥有线程owning thread其他线程读reads始终允许无需加锁必须加锁写writes必须加锁永远不允许即只有Client的拥有线程才能写入其内部状态且写入时必须加锁拥有线程读取自身内部状态可以不加锁但读取其他线程的Client内部状态时必须持有该Client的锁。为实现上述机制Client实现了标准的 lockable 接口——lock()、unlock()与try_lock()client.h。这一规则的实际应用示例KillOpListenerInterface::interrupt会在持锁ClientLock状态下被调用service_context.hClient::getOperationContext()的注释也明确要求在未加锁的 Client 上调用是错误的且不得在未持锁期间使用返回的指针client.h。Client 的连接标签TagsClient还维护一组连接标签TagMaskuint32_t位掩码用于在关闭连接时区分应保留的连接类型client.hkEmptyTagMask 0连接的关闭不受限制kKeepOpen 1副本集成员回滚或移除时连接应保持打开kLatestVersionInternalClientKeepOpen 2内部客户端且其最大 wire version 不低于本服务器连接应保持打开kExternalClientKeepOpen 4外部客户端连接应保持打开kPending 1 31尚未分类的客户端应保持打开分类发生在处理hello命令期间。标签的读取、设置与原子更新分别通过getTags、setTags、unsetTags、mutateTags完成client.h。此外Client还维护_killed状态setKilled()会向当前OperationContext及其未来创建的每个OperationContext传递 kill 信号client.h。Client 线程绑定与切换工具Client::cc()获取当前线程的 ClientClient::cc()client.h用于获取与当前执行线程绑定的Client对象配套的还有haveClient()用于判断当前线程是否已绑定Client。不过官方明确建议能通过参数传递Client对象时优先传参而不是调用Client::cc()。ThreadClientRAII 式绑定ThreadClientclient.h是一个 RAII 风格的辅助类构造时在当前线程上创建并绑定一个Client当ThreadClient超出作用域时自动解绑Client随之销毁。其构造函数只要求传入Service*其余参数描述、Session、是否可被降级杀死、是否排除在关闭中断之外均有合理默认值——描述默认取当前线程名Session 默认取无会话哨兵值Client::noSession()可被杀默认true。// 示例在任意线程中创建一个绑定到当前线程的 Client { ThreadClient tc(service); // desc 默认使用当前线程名 // 此时 tc- 即当前线程的 Client auto opCtx tc-makeOperationContext(); // ... 执行操作 ... } // 作用域结束Client 自动解绑销毁AlternativeClientRegion临时切换当前线程的 ClientAlternativeClientRegionclient.h是另一个 RAII 类用于临时将某个Client对象绑定到当前线程在其生命周期内当前线程原有的Client如果有被寄存起来退出作用域时自动将原Client重新绑定回当前线程。ClientStrand可跨任意线程绑定 ClientClientStrandclient_strand.h的功能与上述工具类似但更进一步它还提供一个Executor接口client_strand.h允许把Client绑定到任意线程上执行任务。ClientStrand::bind()client_strand.h负责绑定makeExecutorclient_strand.h则基于 strand 构造一个可将任务调度到对应线程的OutOfLineExecutor。这使异步任务可以安全地切回持有特定Client的线程执行。OperationContext一次操作的执行上下文在 Mongo 服务器上执行的每个操作例如一次查询或一条命令都由独立的OperationContext管理。OperationContext负责引导shepherd一次操作从开始到完成或取消的整个执行过程。源码中 operation_context.h 的类注释进一步说明它从网络操作被派发时存活到执行结束cursor 上的每次getMore都是独立操作构造时与当前Client关联析构时解除关联每个OperationContext还关联一个RecoveryUnit其生命周期不一定相同可通过releaseRecoveryUnit/setRecoveryUnit转移。取消Cancellation机制OperationContext的取消可能由外部或内部触发外部触发由控制的ServiceContext发起——典型场景是主节点降级primary step-down时setKillAllOperations会向全部或除排除项外的OperationContext发送 kill 信号用户发出killOp命令——KillOpListenerInterfaceservice_context.h允许各子系统注册监听器interrupt(ClientLock, OperationContext*)在操作被杀死之后、仍持有其 Client 锁时调用interruptAll(ServiceContextLock)在所有操作被杀死并给定错误码之后、持有ServiceContext锁时调用底层连接断开——OperationContext会通过Baton周期性地检查客户端连接状态_schedulePeriodicClientConnectedCheck与_checkClientConnected见 operation_context.cpp一旦发现session()-isConnected()为 false即以Client的断开错误码默认ErrorCodes::ClientDisconnectclient.h调用markKilled。内部触发操作的**截止时间deadline**到期例如maxTimeMS超时。此时OperationContext会以ExceededTimeLimit类错误码标记自身被杀死默认超时错误码为ErrorCodes::ExceededTimeLimit见 operation_context.h。在 operation_context.cpp 中有一段完整注释总结了waitForConditionOrInterruptNoAssertUntil的返回条件即操作的苏醒来源普通的条件变量等待条件cv被通知、截止时间已过OperationContext的 kill 条件_deadline已过人工 deadline 或maxTimeMS、调用了markKilled对应killOpBaton条件_baton被通知有人向 baton 排队工作、_baton::run返回超时触发 / 网络就绪 / socket 断开。OperationContext 与 Client、Baton 的关系每个OperationContext都与单个Client关联通过getClient()获取见 operation_context.h该Client管理着操作真正执行所经由的逻辑连接OperationContext还可以可选地关联一个Batonbaton.h它代表一个可以在其上异步执行网络操作的线程。Baton提供networking()视图baton.h返回transport::NetworkingBaton从而支持在等待条件的同时异步处理网络事件OperationContext携带操作 IDgetOpIDoperation_context.h与可选的操作键OperationKey一个 UUID用于客户端作为稳定令牌引用操作service_context.h。ServiceContext::makeOperationContext(Client*)service_context.h是创建操作上下文的工厂要求目标Client当前没有活跃的操作上下文ServiceContext::makeKillOpsExemptOperationContext(Client*)service_context.h则创建对killOp免疫的上下文用于不应被killOp杀死的内部操作。所有操作上下文都通过UniqueOperationContextstd::unique_ptrOperationContext, OperationContextDeleterservice_context.h进行所有权管理。Interruptible可中断性与中断检查OperationContext实现了Interruptible接口interruptible.h这使它可以被关联的Client或通过Client间接地被其所属的ServiceContext杀死。Interruptible提供了一套完整的可中断等待原语供操作执行过程中的阻塞点使用checkForInterrupt()/checkForInterruptNoAssert()检查操作是否处于 killed 状态若是则抛出/返回错误interruptible.h。OperationContext实现了自己的checkForInterruptNoAssert版本operation_context.h并在checkForInterruptNoAssert()中处理 deadline 过期、人工 deadline、kill 状态等仓库还为此维护了超期中断检查统计OverdueInterruptCheckStatsoperation_context.h用于采样跟踪checkForInterrupt调用是否超期以避免性能回退waitForConditionOrInterrupt...系列在条件变量上等待直到谓词为真、或给定 deadline 到期、或操作被中断interruptible.h。中断与 deadline 到期会抛出DBException而给定 deadline 到期仅返回cv_status::timeoutsleepFor/sleepUntil可中断的睡眠被中断时抛出异常interruptible.hDeadlineGuard/makeDeadlineGuard为操作提供一个附加subsidiarydeadline可用于临时缩短当前 deadline 或替换错误码但不能延长interruptible.hInterruptible::notInterruptible()返回一个永远不可被中断的静态实例常用于给接受Interruptible*参数的函数提供默认参数interruptible.h。这套机制解释了killOp、降级、超时是如何真正中断一个正在执行的查询的查询在内部阻塞点如等待条件变量、睡眠、等待锁调用Interruptible原语时会发现操作已被标记为 killed 或 deadline 已过从而抛出异常并终止执行。相关机制与 fail point 也有交互maxTimeAlwaysTimeOut会让带合法非零 maxTime 的查询/命令立即超时maxTimeNeverTimeOut则让服务器永不超时二者不能同时启用见 operation_context.h后者会在 operation_context.cpp 的等待逻辑中被显式检查。最佳实践小结结合官方文档与源码使用这三类上下文时应当遵循以下约定获取ServiceContext优先走Client::getServiceContext()其次是ServiceContext::getCurrentServiceContext()尽量避免直接调用getGlobalServiceContext()为未来多ServiceContext支持留出空间跨线程访问Client内部状态包括其OperationContext必须先持有该Client的锁可用 RAII 的ClientLock只有拥有线程才能在加锁后写入其他线程只能加锁读取传递Client优先用参数传递而非Client::cc()在作用域内管理线程级Client时优先使用 RAII 工具ThreadClient新建并绑定、AlternativeClientRegion临时切换跨线程调度时使用ClientStrand创建操作统一走makeOperationContext工厂其返回UniqueOperationContext负责所有权需要免疫killOp的内部操作使用makeKillOpsExemptOperationContext阻塞等待务必使用Interruptible原语如waitForConditionOrInterruptUntil、sleepFor并定期调用checkForInterrupt否则操作将无法被killOp、降级或超时及时终止。以上结论均可在 docs/contexts.md 及 src/mongo/db/service_context.h、src/mongo/db/client.h、src/mongo/db/operation_context.h、src/mongo/db/operation_context.cpp、src/mongo/util/interruptible.h、src/mongo/db/client_strand.h 等源码文件中交叉印证。【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表