除了均分这个简单粗暴的方法 , 如果希望某些节点比其它节点承担更多的写操作 , 也可通过 group_replication_flow_control_member_quota_percent 设置权重 。这个时候 , 当前节点的吞吐量就等于 quota_size * group_replication_flow_control_member_quota_percent / 100 。
6. 最后,当前节点的 quota_size 还会减去上个周期超额使用的 quota(extra_quota) 。
上个周期的 extra_quota 等于上个周期的 quota_used - quota_size = 156 - 146 = 10 。所以,当前节点的 quota_size 就等于 159 - 10 = 149 , 和日志中的输出完全一致 。为什么会出现 quota 超额使用的情况呢?这个后面会提到 。
7. 当 m_holds_in_period 又恢复为 0 时,就意味着流控结束 。流控结束后 , MGR 不会完全放开 quota 的限制,否则写入量太大 , 容易出现突刺 。MGR 采取的是一种渐进式的恢复策略,即下一周期的 quota_size = 上一周期的 quota_size * (1 + group_replication_flow_control_release_percent / 100) 。
8. group_replication_flow_control_mode 是 DISABLED ,则会将 m_quota_size 和 m_quota_used 置为 0 。m_quota_size 置为 0,实际上会禁用流控 。为什么会禁用流控,这个后面会提到 。
配额的作用时机既然我们已经计算出下一周期的 m_quota_size,什么时候使用它呢?事务提交之后,GCS 广播事务消息之前 。
int group_replication_trans_before_commit(Trans_param *param) { ... // 判断事务是否需要等待 applier_module->get_flow_control_module()->do_wait(); // 广播事务消息 send_error = gcs_module->send_transaction_message(*transaction_msg); ...}
接下来,我们看看 do_wait 函数的处理逻辑 。
int32 Flow_control_module::do_wait() { DBUG_TRACE; // 首先加载 m_quota_size int64 quota_size = m_quota_size.load(); // m_quota_used 自增加 1 。 int64 quota_used = ++m_quota_used; if (quota_used > quota_size && quota_size != 0) { struct timespec delay; set_timespec(&delay, 1); mysql_mutex_lock(&m_flow_control_lock); mysql_cond_timedwait(&m_flow_control_cond, &m_flow_control_lock, &delay); mysql_mutex_unlock(&m_flow_control_lock); } return 0;}
可以看到,如果 quota_size 等于 0,do_wait 会直接返回,不会执行任何等待操作 。这也就是为什么当 m_quota_size 等于 0 时,会禁用流控操作 。
如果 quota_used 大于 quota_size 且 quota_size 不等于 0,则意味着当前周期的配额用完了 。这个时候,会调用 mysql_cond_timedwait 触发等待 。
这里的 mysql_cond_timedwait 会在两种情况下退出:
- 收到 m_flow_control_cond 信号(该信号会在 flow_control_step 函数中发出 ) 。
- 超时 。这里的超时时间是 1s 。
在等待的过程中 , 如果客户端是多线程并发写入(且单个线程的下个操作会等待上个操作完成),这里会等待多个事务,并且超额使用的事务数不会多于客户端并发线程数 。
所以,在上面的示例中,为什么 quota_used(156) 比 quota_size(146)多 10 , 这个实际上是 sysbench 并发线程数的数量 。
接下来,我们看看示例中这 156 个事务在 do_wait 处的等待时间 。
...0.0000200.0000170.0000230.0000730.0000230.0000180.5701800.5679990.5619160.5611620.5589300.5577140.5566830.5505810.5481020.547176
前 146 个事务的平均等待时间是 0.000035s , 后 10 个事务的平均等待时间是 0.558044s 。很显然,后 10 个事务是被流控了,最后被 flow_control_step(默认一秒执行一次)中发送的 m_flow_control_cond 信号释放的 。
流控的相关参数group_replication_flow_control_mode
是否开启流控 。默认是 QUOTA,基于配额进行流控 。如果设置为 DISABLED,则关闭流控 。
group_replication_flow_control_period
流控周期 。有效值 1 - 60,单位秒 。默认是 1 。注意 , 各个节点的流控周期应保持一致,否则的话,就会将周期较短的节点配额作为集群配额 。
看下面这个示例 , 127.0.0.1:33061 这个节点的 group_replication_flow_control_period 是 10,而其它两个节点的 group_replication_flow_control_period 是 1 。
推荐阅读
- RAID5 IO处理之replace代码详解
- Linux Block模块之IO合并代码解析
- 从0开始写一个简单的vite hmr 插件
- React魔法堂:echarts-for-react源码略读
- 惠普星13Air锐龙版怎么样_惠普星13Air锐龙版性能表现
- 我的世界如何去末影之地(我的世界如何从末影之地回家)
- 记一次 .NET 某企业OA后端服务 卡死分析
- 【C++】从零开始的CS:GO逆向分析3——写出一个透视
- ysoserial commonscollections6 分析
- 实例分析Scheduled Thread Pool Executor与Timer的区别