从源码分析 MGR 的流控机制( 五 )


num_non_recovering_members 什么时候会为 0 呢?在新节点加入时 , 因为认证队列中积压的事务过多而触发的流控 。

  • 如果设置了 group_replication_flow_control_min_quota , 则会将 group_replication_flow_control_min_quota 赋值给 min_capacity 。
  • 3. quota_size = min_capacity * 0.9 = 177 * 0.9 = 159 。这里的 0.9 是 1 - group_replication_flow_control_hold_percent /100 。之所以要预留部分配额 , 主要是为了处理积压事务 。
    4. quota_size 不能太大 , 不能超过 group_replication_flow_control_max_quota 。
    5. 注意 , 这里计算的 quota_size 是集群的吞吐量 , 不是单个节点的吞吐量 。如果要计算当前节点的吞吐量 , 最简单的办法是将 quota_size / 有实际写操作的节点数(num_writing_members) 。
    怎么判断一个节点是否进行了实际的写操作呢?很简单 , 上一周期有本地事务提交 , 即 m_delta_transactions_local > 0 。具体在本例中 , 只有一个写节点 , 所以 , 当前节点的 quota_size 就等于集群的 quota_size , 即 159 。
    除了均分这个简单粗暴的方法 , 如果希望某些节点比其它节点承担更多的写操作 , 也可通过 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;}

    经验总结扩展阅读