MySQL源碼-binlog復(fù)制協(xié)議

Binlog 復(fù)制架構(gòu)

在Master開啟binlog后深胳,寫操作會記錄到binlog中所踊,Slave通過發(fā)送dump命令同步binlog。

Slave上會有2個處理binlog的線程,拉取binlog的IO Thread,會將binlog保存在本地relay log中辨图,同時SQL Thread讀取relay log中的binlog,應(yīng)用到Slave肢藐,從而完成寫操作復(fù)制故河。
slaverep.png

Binlog 文件結(jié)構(gòu)

binlog二進制日志文件中記錄著每個寫操作事件,下面以MySQL 5.0.0+版本的日志進行介紹吆豹,對應(yīng)binlog版本4鱼的,該協(xié)議下增加了FORMAT_DESCRIPTION_EVENT事件。
binlogfile.png

每個binlog日志文件固定4字節(jié)開頭:[ fe 'bin' ]痘煤;
第一個事件是FORMAT_DESCRIPTION_EVENT凑阶,描述了其他事件是如何布局,Slave在解析對應(yīng)事件時使用衷快。
最后一個事件是ROTATE_EVENT宙橱,記錄下一個binlog文件的信息。

事件類型

基于行復(fù)制的事件包括:

  1. TABLE_MAP_EVENT
  2. ROWS_EVENT
    • DELETE_ROWS_EVENTv2
    • UPDATE_ROWS_EVENTv2
    • WRITE_ROWS_EVENTv2

每個插入、更新和刪除操作都會前綴一個TABLE_MAP_EVENT事件用于描述操作對應(yīng)的表信息养匈,2個連續(xù)事件通過table_id進行關(guān)聯(lián)哼勇。

table_id和表名并不是一一對應(yīng)都伪,table_id的作用只是在基于行復(fù)制的協(xié)議中用于關(guān)聯(lián)TABLE_MAP_EVENT和ROWS_EVENT

握手協(xié)議

MySQL提供了基于日志文件名-位置和GTID2種復(fù)制binlog的方式呕乎,分別對應(yīng)COM_BINLOG_DUMP和COM_BINLOG_DUMP_GTID事件。


handshake.png

在發(fā)送DUMP事件之前陨晶,Master需要對Slave進行權(quán)限認證猬仁。

  1. Slave連接到Master時,Master會發(fā)送handshark包對Slave進行認證;
  2. Slave收到handshark包后先誉,會將用戶名和密碼作為認證信息發(fā)送ahthentication包;
  3. Master驗證用戶名和密碼湿刽,如果認證通過,則發(fā)送OK_Packet褐耳,否則發(fā)送ERR_Packet诈闺。

Handshake包構(gòu)造如下:

  Packet format:

    Bytes       Content
    -----       ----
    1           protocol version (always 10)
    n           server version string, \0-terminated
    4           thread id
    8           first 8 bytes of the plugin provided data (scramble)
    1           \0 byte, terminating the first part of a scramble
    2           server capabilities (two lower bytes)
    1           server character set
    2           server status
    2           server capabilities (two upper bytes)
    1           length of the scramble
    10          reserved, always 0
    n           rest of the plugin provided data (at least 12 bytes)
    1           \0 byte, terminating the second part of a scramble

static bool send_server_handshake_packet(MPVIO_EXT *mpvio,
                                         const char *data, uint data_len)
{
  Protocol_classic *protocol= mpvio->protocol;

  char *buff= (char *) my_alloca(1 + SERVER_VERSION_LENGTH + data_len + 64);
  char scramble_buf[SCRAMBLE_LENGTH];
  char *end= buff;

  DBUG_ENTER("send_server_handshake_packet");
  *end++= protocol_version;

  protocol->set_client_capabilities(CLIENT_BASIC_FLAGS);

  if (data_len)
  {
    mpvio->cached_server_packet.pkt= (char*) memdup_root(mpvio->mem_root, 
                                                         data, data_len);
    mpvio->cached_server_packet.pkt_len= data_len;
  }

  if (data_len < SCRAMBLE_LENGTH)
  {
    if (data_len)
    {
      /*
        the first packet *must* have at least 20 bytes of a scramble.
        if a plugin provided less, we pad it to 20 with zeros
      */
      memcpy(scramble_buf, data, data_len);
      memset(scramble_buf + data_len, 0, SCRAMBLE_LENGTH - data_len);
      data= scramble_buf;
    }
    else
    {
      generate_user_salt(mpvio->scramble, SCRAMBLE_LENGTH + 1);
      data= mpvio->scramble;
    }
    data_len= SCRAMBLE_LENGTH;
  }

  end= my_stpnmov(end, server_version, SERVER_VERSION_LENGTH) + 1;

  DBUG_ASSERT(sizeof(my_thread_id) == 4);
  int4store((uchar*) end, mpvio->thread_id);
  end+= 4;

  /* write server characteristics: up to 16 bytes allowed */
  end[2]= (char) default_charset_info->number;
  int2store(end + 3, mpvio->server_status[0]);
  int2store(end + 5, protocol->get_client_capabilities() >> 16);
  end[7]= data_len;
  DBUG_EXECUTE_IF("poison_srv_handshake_scramble_len", end[7]= -100;);
  memset(end + 8, 0, 10);
  end+= 18;
  /* write scramble tail */
  end= (char*) memcpy(end, data + AUTH_PLUGIN_DATA_PART_1_LENGTH,
                      data_len - AUTH_PLUGIN_DATA_PART_1_LENGTH);
  end+= data_len - AUTH_PLUGIN_DATA_PART_1_LENGTH;
  end= strmake(end, plugin_name(mpvio->plugin)->str,
                    plugin_name(mpvio->plugin)->length);

  int res= protocol->write((uchar*) buff, (size_t) (end - buff + 1)) ||
           protocol->flush_net();
}

收到authentication包后,Master會解析出用戶名和密碼進行驗證铃芦。

static size_t parse_client_handshake_packet(MPVIO_EXT *mpvio,
                                            uchar **buff, size_t pkt_len)
{
  size_t user_len;
  char *user= get_string(&end, &bytes_remaining_in_packet, &user_len);

  size_t passwd_len= 0;
  char *passwd= NULL;

  passwd= get_length_encoded_string(&end, &bytes_remaining_in_packet,
                                    &passwd_len);
  if (passwd_len)
    mpvio->auth_info.password_used= PASSWORD_USED_YES;
}

Dump命令解析

對于COM_BINLOG_DUMP命令雅镊,需要在之前發(fā)送COM_REGISTER_SLAVE進行注冊。
對于COM_BINLOG_DUMP_GTID命令刃滓,會根據(jù)該命令中g(shù)tidset字段從而定位起始發(fā)送日志位置仁烹。
Master收到命令后,會根據(jù)命令中flags字段是否設(shè)置BINLOG_DUMP_NON_BLOCK進行區(qū)分處理咧虎,未設(shè)置BINLOG_DUMP_NON_BLOCK的請求卓缰,會在binlog發(fā)送完成后,返回EOF_Packet砰诵,否則會一致阻塞等待下一個事件征唬。

bool com_binlog_dump_gtid(THD *thd, char *packet, size_t packet_length)
{
  const uchar* packet_position= (uchar *) packet;
  size_t packet_bytes_todo= packet_length;
  Sid_map sid_map(NULL/*no sid_lock because this is a completely local object*/);
  Gtid_set slave_gtid_executed(&sid_map);

  thd->status_var.com_other++;
  thd->enable_slow_log= opt_log_slow_admin_statements;
  if (check_global_access(thd, REPL_SLAVE_ACL))
    DBUG_RETURN(false);

  //解析COM_BINLOG_DUMP_GTID https://dev.mysql.com/doc/internals/en/com-binlog-dump-gtid.html
  READ_INT(flags,2);
  READ_INT(thd->server_id, 4);
  READ_INT(name_size, 4);
  READ_STRING(name, name_size, sizeof(name));
  READ_INT(pos, 8);
  DBUG_PRINT("info", ("pos=%llu flags=%d server_id=%d", pos, flags, thd->server_id));
  READ_INT(data_size, 4);
  CHECK_PACKET_SIZE(data_size);
  if (slave_gtid_executed.add_gtid_encoding(packet_position, data_size) !=  //將包中內(nèi)容解析到slave_gtid_executed中interval
      RETURN_STATUS_OK)
    DBUG_RETURN(true);
  slave_gtid_executed.to_string(&gtid_string);  //解析為gtid_string
  //T@2: | | | info: Slave 1828716545 requested to read  at position 4 gtid set '075ca916-e025-11e9-bde7-bd71fea5404f:1'.
  DBUG_PRINT("info", ("Slave %d requested to read %s at position %llu gtid set "
                      "'%s'.", thd->server_id, name, pos, gtid_string));

  kill_zombie_dump_threads(thd);
  query_logger.general_log_print(thd, thd->get_command(),
                                 "Log: '%s' Pos: %llu GTIDs: '%s'",
                                 name, pos, gtid_string);
  my_free(gtid_string);
  mysql_binlog_send(thd, name, (my_off_t) pos, &slave_gtid_executed, flags);

  unregister_slave(thd, true, true/*need_lock_slave_list=true*/);
  /*  fake COM_QUIT -- if we get here, the thread needs to terminate */
  DBUG_RETURN(true);
}

DUMP_GTID命令中slave_gtid_executed表示Slave已經(jīng)執(zhí)行過的事件集合,mysql_binlog_send函數(shù)中會根據(jù)該集合確定發(fā)送binlog的起點茁彭。

日志發(fā)送

發(fā)送日志邏輯在單獨的線程Binlog_sender中進行总寒,邏輯如下:

  1. 校驗slave_gtid_executed是否合法,定位第一個發(fā)送文件名尉间;
  2. 發(fā)送偽造的rotate_event事件偿乖,打開第一個發(fā)送文件名;
  3. 依次發(fā)送每個文件哲嘲。
void run()
{
  init();
  while (!has_error() && !m_thd->killed)
  {
    if (unlikely(fake_rotate_event(log_file, start_pos)))
      break;

    file= open_binlog_file(&log_cache, log_file, &m_errmsg);  //根據(jù)文件名打開文件
    if (send_binlog(&log_cache, start_pos))  //發(fā)送一個文件,返回0表示讀完了贪薪,即log_pos == end_pos,然后開始下一個文件
      break;

    /* Will go to next file, need to copy log file name */
    set_last_file(log_file);

    int error= mysql_bin_log.find_next_log(&m_linfo, 0);  //定位下一個文件
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末眠副,一起剝皮案震驚了整個濱河市画切,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌囱怕,老刑警劉巖霍弹,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件毫别,死亡現(xiàn)場離奇詭異,居然都是意外死亡典格,警方通過查閱死者的電腦和手機岛宦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來耍缴,“玉大人砾肺,你說我怎么就攤上這事》牢耍” “怎么了变汪?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蚁趁。 經(jīng)常有香客問我裙盾,道長,這世上最難降的妖魔是什么他嫡? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任番官,我火速辦了婚禮,結(jié)果婚禮上涮瞻,老公的妹妹穿的比我還像新娘鲤拿。我一直安慰自己,他們只是感情好署咽,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布近顷。 她就那樣靜靜地躺著,像睡著了一般宁否。 火紅的嫁衣襯著肌膚如雪窒升。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天慕匠,我揣著相機與錄音饱须,去河邊找鬼。 笑死台谊,一個胖子當著我的面吹牛蓉媳,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播锅铅,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼酪呻,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了盐须?” 一聲冷哼從身側(cè)響起玩荠,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后阶冈,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體闷尿,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年女坑,在試婚紗的時候發(fā)現(xiàn)自己被綠了填具。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡堂飞,死狀恐怖灌旧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情绰筛,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布描融,位于F島的核電站铝噩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏窿克。R本人自食惡果不足惜骏庸,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望年叮。 院中可真熱鬧具被,春花似錦、人聲如沸只损。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽跃惫。三九已至叮叹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間爆存,已是汗流浹背蛉顽。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留先较,地道東北人携冤。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像闲勺,于是被迫代替她去往敵國和親曾棕。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345