從源碼分析 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 更新節點的統計信息 。
更新后的統計信息包括以下兩部分: