MongoDB 复制集 Flow Control 流控机制:原理、服务端参数与源码实现解析
MongoDB 复制集 Flow Control 流控机制原理、服务端参数与源码实现解析【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongoMongoDB 在复制集中使用 Flow Control流控机制来防止主节点写入速度长期超过从节点的多数派提交进度其核心思想是每秒票证ticket配额——写入操作必须先在每秒限定的配额内拿到一张流控票才能去竞争全局 IX 锁。本文以仓库中 Flow Control 机制文档 为主线结合 flow_control.h、flow_control.cpp、flow_control_parameters.idl 与 FlowControlTicketholder 的实现完整解析该机制的准入流程、票据计算算法、全部可调服务端参数、采样机制、写入豁免规则与可观测性输出。一、Flow Control 要解决的问题复制集要求多数派已提交majority committed作为读关注级别majority和多数派快照一致性的基础。如果 Primary 持续高速写入而 Secondary 落后最后提交时间last committed time与最后应用时间last applied time之间的差距——即多数派提交滞后majority committed lag——会不断拉大直接削弱写后的读一致性保障。Flow Control 的目标对应文档第一段的原文语义把复制集多数派已提交滞后维持在不超过配置的最大值该最大值的默认是10 秒对应参数flowControlTargetLagSeconds当多数派已提交滞后达到目标值的一个阈值百分比默认 50%即 5 秒时开始对 Primary 的写入进行节流采用票证准入ticket admission方式节流在每一个 1 秒周期内只有固定张数的流控票可用操作要执行写入必须先拿到流控票再去获取全局 IX 锁超出当周期配额后获取尝试会阻塞stall到下一个 1 秒周期票据补充时某些系统操作被明确排除在票证准入之外即使没有票也可以继续执行见第六节。针对Primary-Secondary-ArbiterPSA拓扑的边界情况由于 Arbiter 不参与写入复制此类复制集可能永远无法建立多数派流控若不设限会造成写入无限期阻塞。因此文档明确该机制只在 read concern majority 可用时执行源码中flowControlUsable()的实现即canAcceptNonLocalWrites()见 flow_control.cpp并且管理员可以整体禁用该机制enableFlowControl参数。此外currentOp、serverStatus、数据库 profiling 和慢操作日志行都会包含 Flow Control 信息见第七节。二、票证准入机制没有票池的每秒配额文档对票证准入机制有一个常被误解的关键澄清不存在一个线程们互相借还的票池票据不由消费方归还而是一个独立机制每秒刷新一次票据计数。这一设计在源码中体现得非常直接FlowControlTicketholder的头文件注释写道It is fundamentally a semaphore, but allows a caller to increment by X in constant time … clients take a ticket anddo not return them to the pool. There is an external service that calculates the maximum number of tickets that should be allotted for the next time period (one second).见 flow_control_ticketholder.hFlowControl对象在构造时注册了一个周期任务FlowControlRefresher周期性调用getNumTickets()计算下一周期应发放的票据数并调用FlowControlTicketholder::refreshTo(numTickets)把计数整体重置为新值见 flow_control.cpp刷新周期由flowControlPollIntervalMs控制默认 1000ms范围 100–5000ms且该任务可被 stepdown 终止isKillableByStepdowntrue消费侧的getTicket()在票据为 0 时阻塞等待条件变量直到refreshTo()唤醒见 flow_control_ticketholder.cpp。每秒该机制决定发放多少票据时依据三个输入与文档Flow Control Ticket Admission Mechanism一节一致当前多数派已提交滞后相对于配置的目标最大滞后支撑提交点sustainer即维持多数派提交点前进的从节点在上一周期应用了多少操作上一周期每个操作平均获得了多少次 IX 锁因为一个逻辑操作可能对应多次全局 IX 锁获取如批量操作或内部拆分操作。前两个输入由TimestampProvider抽象接口提供目标时间是多数派快照提交时间getLastCommittedOpTimeAndWallTime被控制量是最后应用时间getMyLastAppliedOpTimeAndWallTime而 sustainer 是按已应用 optime 排序后的成员列表的中位数节点getMedianAppliedTimestamp见 flow_control.cpp。三、每秒发放多少张票两条计算路径FlowControl::getNumTickets()flow_control.cpp是票据数的唯一计算入口按滞后情况分为健康路径与滞后路径3.1 滞后低于阈值加法/乘法恢复路径当多数派提交滞后小于目标最大滞后 × 阈值百分比默认即 10s × 50% 5s时机制处于健康状态。此时票据数不再是 0 基而是以上一周期实际发放的票据数为基准做递增newTickets (lastTargetTickets flowControlTicketAdderConstant) * flowControlTicketMultiplierConstantflowControlTicketAdderConstant默认1000在刚从阻塞状态恢复、票据基数很低时加法项保证票据能有意义地上涨flowControlTicketMultiplierConstant默认1.05乘法项覆盖更一般的恢复场景让票据按指数速度爬升结果被multiplyWithOverflowCheck截断在kMaxTickets10 亿定义于 flow_control.h以内——这正是文档所说机制被禁用时每次刷新都允许每秒 10 亿次票证获取的同一上限常量源码中还有一个细节防误判空闲系统里只跑 no-op 写入时若目标时间戳与应用时间戳之间几乎没有操作_approximateOpsBetween返回 -1也视为健康避免no-op writer 欺骗滞后分类器见 flow_control.cpp。3.2 滞后达到或超过阈值指数衰减路径当提交滞后 ≥ 阈值时进入_calculateNewTicketsForLag()flow_control.cpp文档给出的公式在源码中对应为newTickets locksPerOp × sustainerAppliedCount × k ^ ((lagMillis - thresholdMillis) / thresholdMillis) × fudgebasesustainer rate 上一周期 sustainer 节点应用的操作数kflowControlDecayConstant默认0.5取值约束 0 k 1。指数(lag - threshold) / threshold在滞后恰好等于阈值时接近 0衰减系数接近 1Primary 基本按 sustainer 速率接收写入滞后增大到 2 倍阈值时指数接近 1吞吐被压到约k默认 0.5倍——源码注释中也给了这个例子见 flow_control.cppfudge factorflowControlFudgeFactor默认0.95。文档说明它应接近 1目的是在当前滞后接近阈值时发放略低于 base 的票据。这个小惩罚是抑制振荡的重要手段见第八节最后乘以locksPerOp把操作数换算成锁获取数对应文档中的 criterion #3兜底分支如果无法确定 sustainer 上一周期应用了多少操作采样历史不足时计数为 -1则发放上一周期实际锁用量的一半locksUsedLastPeriod / 2保证滞后时一定比过去用得少无论走哪条路径最终票据数都会与地板值flowControlMinTicketsPerSecond默认100取 max防止极端场景下写入完全饿死flowControlScaleMinTicketsByLocksPerOp默认 false开启后可把该地板按locksPerOp比例缩小避免 insertMany 这类批量操作放大有效最小吞吐见 flow_control.cpp。3.3 Sustainer 不动时的告警进入滞后路径后若发现 sustainer 位置长时间不前进说明从节点本身可能故障节流也无法让提交点前进机制会在持续flowControlWarnThresholdSeconds默认10 秒0 表示关闭告警后记录日志事件 22225Flow control is engaged and the sustainer point is not moving. Please check the health of all secondaries.见 flow_control.cpp并周期性重复。另外FlowControlTicketholder::getTicket的 500ms 条件变量超时等待是可被操作中断打断的客户端超时/断连时能正确退出。四、服务端参数一览以下参数全部定义于 flow_control_parameters.idl且均为set_at: [startup, runtime]即可以通过setParameter在运行时动态调整。表中默认值与约束均摘自该 IDL 文件参数默认值约束作用enableFlowControltrue—总开关禁用后每次刷新发放 10 亿张票等效不限流flowControlTargetLagSeconds10 0目标最大多数派已提交滞后秒flowControlThresholdLagPercentage0.50.0 ≤ x ≤ 1.0开始介入流控的滞后阈值是目标滞后的百分比flowControlDecayConstant0.50.0 x 1.0衰减常数 k滞后越深吞吐被压得越低flowControlFudgeFactor0.950.0 x ≤ 1.0接近阈值时的小惩罚系数1.0 表示无惩罚抑制振荡时应保持接近 1.0flowControlTicketAdderConstant1000 0恢复期每秒至少增加的票据数从阻塞中快速恢复用flowControlTicketMultiplierConstant1.05 1.0恢复期票据数的放大倍数一般恢复场景用flowControlMinTicketsPerSecond100≥ 0每秒票据数地板调小可提升批量写入下的分辨率但可能无意拖延系统操作flowControlScaleMinTicketsByLocksPerOpfalse—按 locksPerOp 缩放上述地板防止批量操作放大有效最小吞吐flowControlSamplePeriod1000 0采样周期按操作数计越小分辨率越高但发生提交点滞后时更早失去分辨率flowControlMaxSamples1000000 0采样表最多保留的样本数约 24MB 内存flowControlWarnThresholdSeconds10≥ 0sustainer 不前进持续多久后开始告警0 禁用告警flowControlPollIntervalMs1000100 ≤ x ≤ 5000票据刷新周期毫秒越小对滞后变化响应越快CPU 开销越大flowControlUseRateLimiterfalse—改用速率限制器而非传统 ticketholder来执行票据配额flowControlRateLimiterBurstCapacitySecs0.5 0.0使用速率限制器时可累积的突发容量以秒计的未用速率flowControlRateLimiterMaxQueueDepth1000000 0速率限制器队列最大深度超限请求直接以RateLimitExceeded拒绝测试与故障注入方面源码中还定义了 fail pointflowControlTicketOverrideflow_control.cpp可在flowControlUsable为真时强制指定每秒票据数flow_control_test.cpp 大量用例即围绕getNumTickets()的两条计算路径与该 fail point 构建。五、采样机制为算法供给过去一小时的操作账本文档指出 criterion #2 与 #3 依赖一个周期性采样机制Primary 处理写入时持续记录数据且该采样无论流控是否启用都会执行。源码对应FlowControl::sample()flow_control.cpp调用时机类注释说明在 Primary 上复制子系统每次生成新的 optime 时都会调用sampleFlowControl借此维护一个数据结构来近似回答两个时间戳之间有多少操作采样节奏并非每个 optime 都记而是累计操作数达到flowControlSamplePeriod默认每 1000 个操作左右才记一条(timestamp, 自启动以来累计操作数, 全局 IX 锁获取总数)三元组容量策略样本上限为flowControlMaxSamples默认 100 万条约 24MB。达到上限后不丢弃最旧样本而是覆写最新样本——源码注释解释了取舍出现复制滞后时最旧的样本恰恰是正在被用于计算票据数、最有价值的部分宁可牺牲最新端的分辨率也不让内存随滞后无限增长裁剪策略每次刷新时调用_trimSamples()删除早于当前提交目标时间戳的样本但至少保留 2 条计算locksPerOp需要最近两条样本做差分locksPerOp的取值用最近两条样本的锁获取增量除以操作增量_getLocksPerOp见 flow_control.cpp样本不足 2 条时返回 -1此时getNumTickets直接发放满额 10 亿票冷启动不节流构造时也以此初始化 ticketholder避免慢启动。六、写入路径如何被拦截getFlowControlTicket 与豁免流控的生效点在锁管理器。Locker::getFlowControlTicket()locker.cpp只有在同时满足以下条件时才去取流控票锁模式是全局MODE_IX即写入客户端状态为kInactive尚未持有执行票ExecutionAdmissionContext的优先级不是kExempt该操作没有请求不可中断锁uninterruptibleLocksRequested_DO_NOT_USE()为假。取票前先把客户端状态置为kQueuedWriter影响 serverStatus 的globalLock.currentQueue指标取到票后恢复。若设置了flowControlUseRateLimiter则改走FlowControlRateLimiter::acquireTicket()获得突发容量与队列深度上限语义否则走传统的FlowControlTicketholder::getTicket()阻塞路径。哪些操作豁免与文档Throttling internal operations一节对应流控拦截所有 IX 锁获取不论来自客户端还是系统操作除非它们属于明确排除在外的操作。结合源码可见的豁免渠道有优先级为kExempt的执行准入上下文直接绕过第 3 个条件请求不可中断锁的操作绕过第 4 个条件——复制集选举期间的写入即属此类文档特别点名选举产生的写入被排除参见 SERVER-39868应用名豁免app_name_exemption.idl 定义了appNames参数被豁免应用名数组配套匹配器 app_name_exemption_matcher.cpp 按操作携带的 application name 判定是否豁免。另外不能接收写入的节点非 Primary上流控本身就不生效flowControlUsable()返回 false 时直接发放kMaxTickets这与文档机制在无法接收写入的节点上被禁用一致。七、可观测性serverStatus、currentOp 与日志文档说明 currentOp、serverStatus、数据库 profiling 和慢操作日志都携带流控信息源码中的具体字段如下serverStatus 的flowControl段由 FlowControl::generateSection 生成默认随 serverStatus 输出enabledenableFlowControl当前值targetRateLimit上一周期计算出的目标票据数即当前每秒允许的 IX 锁获取配额timeAcquiringMicros累计的取票等待微秒数注意启用 rate limiter 后写入绕过 ticketholder此计数停止增长locksPerKiloOp每千次操作的全局 IX 锁获取数乘 1000 是为了在 FTDC 中以整数保留足够有效位数sustainerRatesustainer 上一周期的应用速率isLagged/isLaggedCount/isLaggedTimeMicros当前是否处于滞后、进入滞后的累计次数与累计时长启用 rate limiter 时另有rateLimiter子文档含queued排队数等。currentOp被流控阻塞的写入会显示waitingForFlowControl: true及flowControlStats子对象acquireCount、acquireWaitCount、timeAcquiringMicros见 flow_control_ticketholder.cpp。取票等待同时计入慢操作/操作日志的等待统计因此慢日志行中的流控信息由此产生。八、已知限制吞吐量振荡文档Oscillations一节坦承存在 Flow Control 使写入吞吐出现振荡的已知场景在不损害机制其他方面的前提下目前没有能完全消除振荡的方案。此前的工作SERVER-39867选择以牺牲吞吐为代价抑制振荡参数体系中flowControlFudgeFactor默认 0.95正是这种阻尼手段IDL 中的描述明确写道Reducing oscillations should keep this value close to 1.0即在滞后接近阈值时给 primary 施加一个略低于 sustainer 速率的持续性小惩罚避免 primary 追平阈值后再度放量、从而把滞后推回阈值之上形成往复振荡。调参时若观察到isLaggedCount反复跳变、targetRateLimit锯齿波动可优先审视该参数与flowControlDecayConstant的组合。九、小结从文档到源码的完整映射文档概念源码落点每秒固定票据、无票池、独立刷新FlowControlTicketholder::refreshTo / getTicket、FlowControlRefresher 周期任务禁用时每秒 10 亿票kMaxTickets、getNumTickets 的 disabled/不可用分支base * k^((lag-threshold)/threshold) * fudge_calculateNewTicketsForLag低于阈值时 (last adder) * multipliergetNumTickets 健康分支三个判定依据滞后、sustainer 速率、locksPerOpTimestampProvider 抽象与 ReplicationTimestampProvider、_getLocksPerOp采样机制与启用状态无关地运行FlowControl::sample选举写入等系统操作豁免Locker::getFlowControlTicket、app_name_exemption.idl振荡与抑制SERVER-39867/39868flowControlFudgeFactor 参数说明对使用者而言日常调优通常只碰三个参数flowControlTargetLagSeconds容忍多大滞后、flowControlDecayConstant/flowControlFudgeFactor滞后后压多狠、振荡阻尼其余参数保持默认即可排障时则依赖serverStatus.flowControl.targetRateLimit、isLagged*字段与 currentOp 的waitingForFlowControl定位写入是否正被流控阻塞。若机制与业务冲突最直接的开关是运行时执行enableFlowControl置 false——源码保证这等效于每秒发放 10 亿张票不会有任何残余限流。【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考