mysql一個冷門參數引起的同步故障

最近出現個同步問題,我們數據的入庫順序爲mysql->redis,業務層只對mysql做操作,而同步到redis的操作是採用開源的驅動包自己寫的同步工具,最近程序更新重啓,時有時無的報同樣的錯:

error ERROR 1105 (HY000): Failed to register slave: too long 'report-host'"

出現報錯重啓一兩次又正常,起初懷疑是程序裏面傳的什麼有問題,因爲自己寫的小同步程序沒出現過這個報錯,也就讓開發人員在檢測,經過檢查的確沒有傳什麼關於host的參數,於是去官網搜索了下report_host碰碰運氣看是否有這個參數,還真有........,官網介紹如下:

The host name or IP address of the slave to be reported to the master during slave registration. This value appears in the output of SHOW SLAVE HOSTS on the master server. Leave the value unset if you do not want the slave to register itself with the master.

大概意思就是這個參數是主從建立時由slave傳遞hostname用的,用show slave hosts命令可以看到,如果沒有加這個參數是看不到的,只能在processlist查看,於是開發的童鞋再到驅動包裏排查發現有去獲取了本機hostname傳遞,到這基本確定是由於傳遞的hostname超過mysql限制的長度造成的,那爲什麼更新啓動時報錯,重啓之後又正常了,繼續.......

 

我們應用是部署在docker集羣中,docker在利用鏡像啓動時是利用應用名稱加隨機字符串生成一個隨機的hostname,再重啓之後生成的會短一點,這就造成了第一次啓動會報錯,而重啓之後就正常,找到原因再來看mysql哪裏可以修改下限制,經過查找沒有關於這個的參數.............於是對源碼進行搜索,發現report_host的長度是寫死60

int register_slave_on_master(MYSQL* mysql, Master_info *mi,
                             bool *suppress_warnings)
{
  uchar buf[1024], *pos= buf;
  size_t report_host_len=0, report_user_len=0, report_password_len=0;
  DBUG_ENTER("register_slave_on_master");
 
  *suppress_warnings= FALSE;
  if (report_host)
    report_host_len= strlen(report_host); //獲取report_host的長度
  if (report_host_len > HOSTNAME_LENGTH)  //判斷report_host長度是否超過HOSTNAME_LENGTH=60的限制
  {
    sql_print_warning("The length of report_host is %zu. "
                      "It is larger than the max length(%d), so this "
                      "slave cannot be registered to the master%s.",
                      report_host_len, HOSTNAME_LENGTH,
                      mi->get_for_channel_str());
    DBUG_RETURN(0);
  }
 
  ...........................
 
  int4store(pos, server_id); pos+= 4; //前4bytes寫入server_id
  pos= net_store_data(pos, (uchar*) report_host, report_host_len);//這裏則是寫入report_host及length,規則爲length佔1bytes,後面緊跟report_host
  pos= net_store_data(pos, (uchar*) report_user, report_user_len);
  pos= net_store_data(pos, (uchar*) report_password, report_password_len);
  ......................................
}

這是源碼中slave端在協議建立時組協議包的縮略內容,其實在這裏對report_host的長度進行了檢查,如果超過60的長度就會報錯,就無法建立主從,但是我們是自己寫的工具而且驅動包裏只是獲取hostname並未對它的長度進行判斷


int register_slave(THD* thd, uchar* packet, size_t packet_length)
{
  int res;
  SLAVE_INFO *si;
  uchar *p= packet, *p_end= packet + packet_length;
  const char *errmsg= "Wrong parameters to function register_slave";
 
  if (check_access(thd, REPL_SLAVE_ACL, any_db, NULL, NULL, 0, 0))
    return 1;
  if (!(si = (SLAVE_INFO*)my_malloc(key_memory_SLAVE_INFO,
                                    sizeof(SLAVE_INFO), MYF(MY_WME))))
    goto err2;
 
  /* 4 bytes for the server id */
  if (p + 4 > p_end)
  {
    my_error(ER_MALFORMED_PACKET, MYF(0));
    my_free(si);
    return 1;
  }
 
  thd->server_id= si->server_id= uint4korr(p); //首先前面4bytes獲取server_id
  p+= 4;
  get_object(p,si->host, "Failed to register slave: too long 'report-host'"); //這裏就是獲取report_host,以及做判斷,報錯內容和我們應用報錯內容一致,si->host是char host[HOSTNAME_LENGTH+1]既61的長度
  get_object(p,si->user, "Failed to register slave: too long 'report-user'");
  get_object(p,si->password, "Failed to register slave; too long 'report-password'");
  ........................
}

上面這段縮略內容即是master接收到slave發起的協議包之後的操作,在其中出現了我們的報錯內容,而get_object中是這麼進行判斷的:


#define get_object(p, obj, msg) 
{
  uint len; 
  ......................
  len= (uint)*p++;  
  if (p + len > p_end || len >= sizeof(obj)) 
  {
    errmsg= msg;
    goto err; 
  }
  .........................
}


根據對源碼的查找分析,mysqlreport_host限制爲最長60個字節長度,也就是非中文的60個字符,這是個比較冷門的參數,而在源碼中又寫死了長度,我們只有對服務名及dockerhostname做控制解決這個問題

 

該問題在我們平時使用中其實很少遇到,而官網也沒有介紹這個長度限制,剛開始遇到的時候有點摸不到頭緒,如果有童鞋自己做同步可以做參考,我們使用的go-mysql這個開源包,如果有使用的可以注意下


ps:mysql技術交流qq羣479472450


qrcode_for_gh_3e32c761a655_258.jpg


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章