最近出现个同步问题,我们数据的入库顺序为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