MySQL5.5半同步複製實現原理
在semi-sync replication中,master等待binlog被成功寫入到至少一個slave的relay log之後才會繼續執行commit,否則會出現等待。當超過預定的等待時間之後,semi-syncreplication會被禁用,切換回非同步再提交。
Semi-sync replication在一定程度上保證提交的事務至少有一個已經傳到slave上了。
google提出了兩個解決方法:full synchronous replication和semi-synchronous replication。但是full sync不僅要保證事務已經傳遞到slave上,而且還要等到事務在slave上執行完畢才能返回,因此不被推薦。
Semi-sync replication主要流程:
Detailed Design:
Semi-synchronous commit protocol
Our proposed semi-synchronous replication works in the followingway:
-
commit the transaction
-
wait for the replica databases acknowledge that they already received the transaction - this step has a timeout
-
tell the client that the commit has been processed
在第2步中,如果replica 在預定的等待時間內沒有返回資訊,semi-sync replication會被禁用,切換回asynchronous replication。因此,一個transaction至少需要一次完成TCP環路的時間。
目前的mysqlreplication過程:
在slave端:
Slave呼叫safe_connect()去連線master;
COM_BINLOG_DUMP 獲取master上的binlog資訊,主要有:binlog_filename, binlog_pos, binlog_flag, server_id。
在master端:
處理COM_BINLOG_DUMP;
Mysql_binlog_send()向slave傳送binlog事件。
因為binlog_flag是由slave發出的,並且在master中進行了設定後返回,所以semi-sync主要在binlog_flag上加了一個額外的頭位元組,用來判斷slave是不是同步的目標。
semisync.h:
static const unsigned char kSyncHeader[2];
static constunsigned char kPacketMagicNum;
static constunsigned char kPacketFlagSync;
Packet 由三部分組成:
1.the magic num
2. thebinlog positon
3. the binlog filename
Master端的執行過程:
在master端建立了一棵搜尋樹用來儲存等待事務。當一個事務提交,並且寫到binlog中後,將(binlog_filename, binlog_pos)插入到搜尋樹中,這樣做的目的是為了方便replication thread找出當前等待事務。當事務結束等待後,該事務記錄就會被移除出搜尋樹。
Replication thread讀取binlog event,並且檢查binlog position是否在搜尋樹中,然後根據binlog position在搜尋樹中的位置,設定額外的頭位元組。
Semisync_master.cc:
int ActiveTranx::insert_tranx_node(const char*log_file_name, my_off_t log_file_pos)
{
…………………………
strncpy(ins_node->log_name_, log_file_name, FN_REFLEN-1);
ins_node->log_name_[FN_REFLEN-1] = 0;
ins_node->log_pos_ = log_file_pos;
}
//讀取slave返回的資訊獲取binlog的pos,用來確定slave讀取事務是否已經完成。
intrepl_semi_report_binlog_update(Binlog_storage_param *param,
const char *log_file,
my_off_t log_pos, uint32 flags)
{
………………….
//為了儲存log_file,log_pos,這樣的目的是為了確定binlog還需要多長時間才能複製到slave
error=repl_semisync.writeTranxInBinlog(log_file, log_pos);
…………………
}
int ReplSemiSyncMaster::writeTranxInBinlog(const char*log_file_name,
my_off_t log_file_pos)
{
……….
lock();
//更新最新事務提交位置
if(commit_file_name_inited_)
{
int cmp =ActiveTranx::compare(log_file_name, log_file_pos,
commit_file_name_, commit_file_pos_);
…………..
//將(log_file_name,log_file_pos)插入到search tree
if (is_on())
{
assert(active_tranxs_ != NULL);
if(active_tranxs_->insert_tranx_node(log_file_name,log_file_pos))
{
………….
}
}
……….
unlock();
………
}
int repl_semi_report_commit(Trans_param *param)
{
……………………
return repl_semisync.commitTrx(binlog_name, param->log_pos);
…………………
}
CommitTrx()的作用是:如果開啟了semi-sync,會讓binlog-dump thread去等待slave返回的資訊,如果超過了等待時間,禁用semi-sync,通知其他事務都無需等待返回資訊。
int ReplSemiSyncMaster::commitTrx(const char*trx_wait_binlog_name,
my_off_t trx_wait_binlog_pos)
//開始呼叫binlog_dump_thread,首先判斷是否有新的semi-sync slave,如果有的話新增到semi-sync master的slave表中。判斷完成後呼叫reportReplyBinlog,以確定該slave是否已經接收到所有的binlog event。
int repl_semi_binlog_dump_start(Binlog_transmit_param*param,
const char *log_file,
my_off_t log_pos)
{
bool semi_sync_slave= repl_semisync.is_semi_sync_slave();
if(semi_sync_slave)
{
repl_semisync.add_slave();
repl_semisync.reportReplyBinlog(param->server_id,log_file, log_pos);
}
…………………..
}
intreportReplyBinlog(uint32 server_id, const char* log_file_name,
my_off_t end_offset);
//傳送binlog中的event到semi-sync slave,前面已經講過,semi synchronous replication主要就是通過傳送packet到各個slave上,用來匹配判斷一個slave是否是semi-sync slave。
intrepl_semi_before_send_event(Binlog_transmit_param *param,
unsigned char*packet, unsigned long len,
const char*log_file, my_off_t log_pos)
{
return repl_semisync.updateSyncHeader(packet, log_file, log_pos, param->server_id);
}
int updateSyncHeader(unsignedchar *packet,
const char *log_file_name,
my_off_t log_file_pos,
uint32 server_id)
{
……………..
if (sync)
{
(packet)[2] =kPacketFlagSync; //即為在binlog_flag 中新增的一個byte
}
……………
}
//在傳送event完之後等待,呼叫readSlaveReply讀取semi-sync slave返回的資訊
int repl_semi_after_send_event(Binlog_transmit_param*param,
const char*event_buf, unsigned long len)
{
if(repl_semisync.is_semi_sync_slave())
{
THD *thd=current_thd;
(void) repl_semisync.readSlaveReply(&thd->net,param->server_id, event_buf);
thd->clear_error();
}
return 0;
}
int ReplSemiSyncMaster::readSlaveReply(NET *net,uint32 server_id,
constchar *event_buf)
{
const char*kWho = "ReplSemiSyncMaster::readSlaveReply";
const unsignedchar *packet;
char log_file_name[FN_REFLEN];
my_off_tlog_file_pos;
…………
//計算網路等待時間
rpl_semi_sync_master_net_wait_num++;
rpl_semi_sync_master_net_wait_time += wait_time;
…………
//如果返回資訊匹配成功,則呼叫reportReplyBinlog來返回報告資訊
result = reportReplyBinlog(server_id, log_file_name,log_file_pos);
………..
return function_exit(kWho, result);
}
Slave端執行過程分析:
//呼叫binlog dump thread去連線master,並且讀取binlog
int repl_semi_slave_request_dump(Binlog_relay_IO_param*param,
uint32 flags)
{
………
//校驗master端有沒安裝semi-sync
query="SHOW VARIABLES LIKE 'rpl_semi_sync_master_enabled'";
……….
//通知master的dump thread該slave一起開啟semi-sync
query="SET @rpl_semi_sync_slave= 1";
………
}
//semi-sync讀取master端傳遞過來的binlog,呼叫slaveReadSyncHeader來識別傳送過來的packet
int repl_semi_slave_read_event(Binlog_relay_IO_param*param,
const char *packet, unsigned long len,
const char **event_buf, unsigned long*event_len)
{
if(rpl_semi_sync_slave_status)
return repl_semisync.slaveReadSyncHeader(packet, len,
&semi_sync_need_reply,
event_buf, event_len);
*event_buf=packet;
*event_len=len;
return 0;
}
//header就是master傳遞過來的packet
//need_reply是判斷master是否會等待slave返回資訊
int ReplSemiSyncSlave::slaveReadSyncHeader(const char*header,
unsignedlong total_len,
bool *need_reply,
constchar **payload,
unsignedlong *payload_len)
{
const char*kWho = "ReplSemiSyncSlave::slaveReadSyncHeader";
int read_res =0;
function_enter(kWho);
if ((unsigned char)(header[0]) == kPacketMagicNum)
{
*need_reply =(header[1] & kPacketFlagSync);
//將header傳遞過來的flag與自身的kPacketFlagSync進行匹配,其中header[1]為slave設定的binlog_flag,傳遞給master後,進行了設定後的新值,可以在updateSyncHeader函式中找到。
………….
}
//slave執行讀取的event,執行完畢後呼叫slaveReply返回資訊給master
int repl_semi_slave_queue_event(Binlog_relay_IO_param*param,
constchar *event_buf,
unsigned long event_len,
uint32flags)
{
if(rpl_semi_sync_slave_status && semi_sync_need_reply)
{
(void) repl_semisync.slaveReply(param->mysql,
param->master_log_name,
param->master_log_pos);
}
return 0;
}
int ReplSemiSyncSlave::slaveReply(MYSQL*mysql,
const char*binlog_filename,
my_off_tbinlog_filepos)
//呼叫mysql->net介面返回結果資訊
在mysql 5.5版本中,半同步實現方法和google在5.0和5.1版本當中的方式有點不同,其實現為兩個plugin的方式,而且對本身程式碼的侵入更小,當然這是在原本的程式碼上進行重構的前提下。
semi_sync_master 和semi_sync_slave
分別在主庫和從庫上註冊。
install plugin rpl_semi_sync_master soname 'semisync_master.so';
install plugin rpl_semi_sync_slave soname 'semisync_slave.so';
實現原理:
MySQL在事務函式介面,Binlog傳送接收介面,relay刷盤函式介面的實現中都提供了一個鉤子。這些鉤子實現為代理物件。通過向代理物件中註冊觀察者,可以實現在事件觸發時呼叫觀察者提供的回撥函式。從而達到控制主從同步邏輯的目的。
semi-sync中有四個觀察物件,主庫中3個,從庫中1個。
這些觀察者的成員函式會在合適的時候被MySQL內部的代理物件回撥。
master中三個觀察者:
事務觀察者,事務提交或回滾的時候回撥。
Trans_observer{
Uint32_t
len;
Int (*after_commit)(Trans_param *param)
Int (*after_rollback)(Trans_param *param);
}
Binlog儲存觀察者。在binlog寫盤的時候回撥。
Binlog_storage_observer{
uint32_t len;
Int (*after_flush)(Binlog_storage_param *param ,const char *log_file, my_off_t log_pos, uint32
flags)
}
Binlog dump觀察者,會在binlog被髮送到slave的過程中被呼叫。
Binlog_transmit_obaserver{
Uint32_t len;
Int (*transmit_start)(Binlog_transmit_param *param,const char *log_file, my_off_t log_pos);
Int (*transmit_stop)(Binlog_transmit_param *param);
Int (*reserve_header)(Binlog_transmit_param *param, unsigned char *header, unsigned long size,unsignedlong *len);
Int (*before_send_event)(Binlog_transmit_param *param,unsigned char *packet, unsigned long len,const char*log_file, my_off_t log_pos );
Int (*after_send_event)(Binlog_transmit_param *param,const char *event_buf, unsigned long len);
Int (*after_reset_master)(Binlog_transmit_param *param);
}
slave中一個觀察者
Relay 日誌IO觀察者
Binlog_relay_IO_observer{
Uint32_t len;
int (*thread_start)(Binlog_relay_IO_param *param);
int (*thread_stop)(Binlog_relay_IO_param *param);
int (*before_request_transmit)(Binlog_relay_IO_param *param, uint32
flags);
int (*after_read_event)(Binlog_relay_IO_param *param,const char *packet, unsigned long len, const char**event_buf, unsigned long *event_len);
int (*after_queue_event)(Binlog_relay_IO_param *param, const char *event_buf, unsigned long event_len,uint32
flags);
int (*after_reset_slave)(Binlog_relay_IO_param *param);
}
mysql內部的代理物件,
在mysql啟動時init_server_components的過程中,會執行delegates_init()函式。該函式初始化了四個代理物件:
Trans_delegate transaction_delegate;
Binlog_storage_delegate binlog_storage_delegate;
Binlog_transmit_delegate binlog_transmit_delegate;
Binlog_relay_IO_delegate binlog_relay_io_delegate;
這四個代理類都是Delegeate類的子類。在Delegate中有一個在其中註冊的觀察者列表。
typedef List<Observer_info> Observer_info_list;
所有對該代理中發生的動作感興趣的觀察者均可以通過add_observer(void *observer, st_plugin_int *plugin)函式來向代理註冊。
當事件發生時,代理將迴圈呼叫在其上面註冊的觀察者提供的回撥函式。
例如transaction_delegate代理物件在一個事務提交時被呼叫,則其會呼叫如下一個巨集,喚醒所有的觀察者進行相應的動作。
FOREACH_OBSERVER(ret, after_commit, thd, (¶m));
在mysql的程式碼中,通過一個巨集來呼叫這四個代理,該巨集定義如下
#define RUN_HOOK(group, hook, args) \
(group ##_delegate->is_empty() ? \
0 : group ##_delegate->hook args)
例如在儲存引擎介面的父類handler當中,ha_commit_trans介面函式的最末尾發生如下一個呼叫。
RUN_HOOK(transaction, after_commit, (thd, FALSE));
該巨集展開之後:
transaction_delegate->is_empty()? 0 : transaction_delegate->after_commit(thd,FALSE);
如果在transaction_delegate當中註冊有觀察者。則都會響應after_commit函式。
複製同步過程
有了以上這些介面提供的功能,要實現主從複製的同步,經如下流程。
-
主庫事務提交時在提交前的最後一刻,進行如下呼叫。
RUN_HOOK(transaction, after_commit, (thd, FALSE));
該函式呼叫會導致主庫SQL執行緒處於"Waiting for semi-sync ACK from slave",狀態,等 待binlog event傳送到從庫之後得到的響應。
RUN_HOOK呼叫的事務觀察代理最終會呼叫的是Trans_observer觀察者的after_commit方法,該函式的引數Trans_param當中包含了當前事務提交的binlog_name和binlog_pos。該after_commit將binlog位置和從從庫獲得的binlog位置進行對比,如果從庫已經對該binlog或該binlog之後的position傳送了確認包。則提交該事務,否則事務將等待。
主庫當中的binlog dump執行緒保持著reply_file_name和reply_file_pos兩個變數來記錄從庫當中記錄的最新的確認包。在reply_file_name.reply_file_pos之前的binlog都已經被從庫接收並同步到磁碟。
replay_file_name和reply_file_pos兩個變數,則由Binlog_transmit_delegate代理呼叫Binlog_transmit_obaserver觀察者的回撥函式after_send_event來維護。即在接收到新的確認包之後就對這兩個變數進行更新。
-
在從庫當中的binlog_relay_io_delegate代理負責呼叫relay_io_observer觀察者來響應寫relay日誌的操作。在binlog寫入從庫當中的relay日誌之後將向主庫傳送響應。響應回覆當中包含了三個欄位。這三個欄位標誌了從庫當前寫入的這個event對應主庫當中的binlog的檔名和pos。
REPLY_MAGIC_NUM |
REPLY_BINLOG_POS_ |
REPLY_BINLOG_NAME |
在install plugin的過程中,向主庫的三個代理物件註冊三個監聽者,分別監聽事務提交事件,binlog寫盤事件和binlog傳送事件。
在從庫的plugin載入的過程中,向一個代理物件註冊一個監聽者,監聽relay的寫盤事
相關文章
- MySQL 8 複製(二)——半同步複製MySql
- mysql 5.7半同步複製MySql
- MySQL主從複製之半同步複製MySql
- Mysql5.7半同步複製MySql
- MySQL5.7主從複製-半同步複製搭建MySql
- mysql半同步複製的設定MySql
- 半同步複製報錯mysql8.0.25MySql
- Mariadb之半同步複製叢集配置
- MySQL增強(Loss-less)半同步複製MySql
- MySQL5.7半同步複製報錯案例分析MySql
- #MySQL# mysql5.7新特性之半同步複製MySql
- MongoDB原理:複製集狀態同步機制MongoDB
- 多從庫時半同步複製不工作的BUG分析
- js 實現深複製/深複製JS
- mysql 複製原理與實踐MySql
- linux下mysql主從複製,實現資料庫同步LinuxMySql資料庫
- MySQL 8 複製(一)——非同步複製MySql非同步
- 從一個群友問題看流複製實現原理
- Mysql半同步複製模式說明及配置示例 - 運維小結MySql模式運維
- MySQL主從複製之非同步複製MySql非同步
- 前端實現複製功能前端
- JS實現複製大法JS
- MySQL半同步複製資料最終一致性驗證MySql
- 【RocketMQ】主從同步實現原理MQ主從同步
- js實現複製連結JS
- redis 主從複製實現Redis
- vue實現物件的複製Vue物件
- mysql實現主從複製MySql
- Java物件複製原理剖析及最佳實踐Java物件
- 大神教你C++寫時複製實現原理及例項解析C++
- GreatSQL 非同步複製及搭建SQL非同步
- MongoDB 重新同步複製整合員MongoDB
- 主從複製--非同步篇非同步
- MySQL 8 複製(七)——組複製基本原理MySql
- mysql過濾複製的實現MySql
- vue 中實現複製貼上Vue
- JS 物件如何實現深複製JS物件
- docker實現mysql主從複製DockerMySql
- 尾遞迴實現深複製遞迴