从源码分析 MGR 的流控机制

Group Replication 是一种 Shared-Nothing 的架构,每个节点都会保留一份数据 。
虽然支持多点写入 , 但实际上系统的吞吐量是由处理能力最弱的那个节点决定的 。
如果各个节点的处理能力参差不齐,那处理能力慢的节点就会出现事务堆积 。
在事务堆积的时候,如果处理能力快的节点出现了故障,这个时候能否让处理能力慢的节点(存在事务堆积)接受业务流量呢?

  1. 如果不等待堆积事务应用完 , 直接接受业务流量 。
    一方面会读到旧数据,另一方面也容易出现写冲突 。
    为什么容易出现写冲突呢?因为基于旧数据进行的写操作,它的 snapshot_version 小于冲突检测数据库中对应记录的 snapshot_version , 这个时候,冲突检测会失败 。
  2. 如果等待堆积事务应用完才接受业务流量 , 又会影响数据库服务的可用性 。
为了避免出现上述两难场景,Group Replication 引入了流控机制 。
在实现上,Group Replication 的流控模块会定期检查各个节点的事务堆积情况 , 如果超过一定值,则会触发流控 。
流控会基于上一周期各个节点的事务认证情况和事务应用情况,决定当前节点(注意是当前节点 , 不是其它节点)下个周期的写入配额 。
超过写入配额的事务操作会被阻塞,等到下个周期才能执行 。
接下来,我们通过源码分析下流控的实现原理 。
本文主要包括以下几部分:
  1. 流控触发的条件 。
  2. 配额的计算逻辑 。
  3. 基于案例定量分析配额的计算逻辑 。
  4. 配额作用的时机 。
  5. 流控的相关参数 。
流控触发的条件默认情况下 , 节点的状态信息是每秒发送一次(节点的状态信息是在 flow_control_step 中发送的,发送周期由 group_replication_flow_control_period 决定) 。
当接受到其它节点的状态信息时,会调用 Flow_control_module::handle_stats_data 来处理 。
下面我们看看 Flow_control_module::handle_stats_data 函数的处理逻辑 。
int Flow_control_module::handle_stats_data(const uchar *data, size_t len,                                           const std::string &member_id) {  DBUG_TRACE;  int error = 0;  Pipeline_stats_member_message message(data, len);  m_flow_control_module_info_lock->wrlock();  // m_info 是个字典,定义是 std::map<std::string, Pipeline_member_stats>  // 其中,key 是节点的地址 , value 是节点的状态信息 。  Flow_control_module_info::iterator it = m_info.find(member_id);  // 如果 member_id 对应节点的状态信息在 m_info 中不存在,则插入 。  if (it == m_info.end()) {    Pipeline_member_stats stats;    std::pair<Flow_control_module_info::iterator, bool> ret = m_info.insert(        std::pair<std::string, Pipeline_member_stats>(member_id, stats));    error = !ret.second;    it = ret.first;  }  // 更新节点的统计信息  it->second.update_member_stats(message, m_stamp);  // 检查是否需要流控  if (it->second.is_flow_control_needed()) {    ++m_holds_in_period;#ifndef NDEBUG    it->second.debug(it->first.c_str(), m_quota_size.load(),                     m_quota_used.load());#endif  }  m_flow_control_module_info_lock->unlock();  return error;}首先判断节点的状态信息是否在 m_info 中存在 。如果不存在,则插入 。
接着通过 update_member_stats 更新节点的统计信息 。
更新后的统计信息包括以下两部分: