《從源碼探究MySQL5.7高吞吐事務(wù)量的背后操手》要點(diǎn):
本文介紹了從源碼探究MySQL5.7高吞吐事務(wù)量的背后操手,希望對您有用。如果有疑問,可以聯(lián)系我們。
大家都知道在MySQL中,在事務(wù)真正COMMIT之前,會(huì)將事務(wù)的binlog日志寫入到binlog文件中.在MySQL的5.7版本中,提供了所謂的無損復(fù)制功能,該功能的作用就是在主庫的事務(wù)對其他的會(huì)話線程可見之前,就將該事務(wù)的日志同步到從庫,保證了事務(wù)可以安全地?zé)o丟失地復(fù)制到從庫.
下面我們從源碼來分析MySQL的事務(wù)提交以及事務(wù)在何時(shí)將binlog復(fù)制到從庫的.
MYSQL_BIN_LOG::ordered_commit,這個(gè)是事務(wù)在binlog階段提交的核心函數(shù),通過該函數(shù),實(shí)現(xiàn)了事務(wù)日志寫入binlog文件,以及觸發(fā)dump線程將binlog發(fā)送到Slave,在最后的步驟,將事務(wù)設(shè)置為提交狀態(tài).
我們來分析MYSQL_BIN_LOG::ordered_commit這個(gè)函數(shù)的核心過程,該函數(shù)位于binlog.cc文件中.
MYSQL_BIN_LOG::ordered_commit,這個(gè)函數(shù),核心步驟如下:
第一步驟:flush
Stage#1: flushing transactions to binary log:
步驟1 :將事務(wù)的日志寫入binlog文件的buffer中,函數(shù)如下:
process_flush_stage_queue(&total_bytes,&do_rotate, &wait_queue);
從5.6開始,MySQL引入了Group Commit的概念,這樣可以避免每個(gè)事務(wù)提交都會(huì)鎖定一次binlog.
另外,還有一個(gè)用處,就是MySQL5.7的基于logical_clock的并行復(fù)制.在一個(gè)組里面(其實(shí)是一個(gè)隊(duì)列),這一組隊(duì)列的頭事務(wù)是相同的,因此這一組事務(wù)的last_committed(上一組的最后一個(gè)提交的事務(wù))的事務(wù)也是同一個(gè).我們都知道,last_committed相同的事務(wù),是可以在從庫并行relay(重演)的.
該函數(shù)process_flush_stage_queue的作用,就是將commit隊(duì)列中的線程一個(gè)一個(gè)地取出,然后執(zhí)行子函數(shù) flush_thread_caches(head);循環(huán)的代碼如下:將各自線程中的binlog cache寫入到binlog中.
/* Flush thread caches to binary log. */
for (THD *head= first_seen ; head ; head = head->next_to_commit)
{
std::pair<int,my_off_t>result= flush_thread_caches(head);
total_bytes+= result.second;
if(flush_error == 1)
flush_error= result.first;
#ifndef DBUG_OFF
no_flushes++;
#endif
}
第二步驟:SYNC to disk
Stage#2: Syncing binary log file to disk
第二步:將binlog file中cache的部分寫入disk.但這個(gè)步驟參數(shù)sync_binlog起決定性的作用.
我們來看看源碼,除了這些還有哪些細(xì)節(jié)步驟,聽完源碼分析之后,你應(yīng)該有新的收獲與理解.在執(zhí)行真正的將binlog寫到磁盤之前,會(huì)進(jìn)行一個(gè)等待,函數(shù)如下:
stage_manager.wait_count_or_timeout(opt_binlog_group_commit_sync_no_delay_count,
opt_binlog_group_commit_sync_delay,
Stage_manager::SYNC_STAGE);
等待的時(shí)間由MySQL參數(shù)文件中的binlog_group_commit_sync_delay,binlog_group_commit_sync_no_delay_count 這兩參數(shù)共同決定.第一個(gè)表示該事務(wù)組提交之前總共等待累積到多少個(gè)事務(wù),第二個(gè)參數(shù)則表示該事務(wù)組總共等待多長時(shí)間后進(jìn)行提交,任何一個(gè)條件滿足則進(jìn)行后續(xù)操作.
因?yàn)橛羞@個(gè)等待,可以讓更多事務(wù)的binlog通過一次寫binlog文件磁盤來完成提交,從而獲得更高的吞吐量.
接下來,就是執(zhí)行sync_binlog_file,該函數(shù)會(huì)用到MySQL參數(shù)文件中sync_binlog參數(shù)的值,如果為0,則不進(jìn)行寫磁盤操作,由操作系統(tǒng)決定什么時(shí)候刷盤,如果為1,則強(qiáng)制進(jìn)行寫磁盤操作.
再接下來,執(zhí)行update_binlog_end_pos函數(shù),用來更新binlog文件的最后的位置binlog_end_pos,該binlog_end_pos是一個(gè)全局的變量.在執(zhí)行更新該位置之前,先得找到最后一個(gè)提交事務(wù)的線程(因?yàn)槭荊roup Commit,多個(gè)事務(wù)排隊(duì)提交的機(jī)制).因?yàn)橐呀?jīng)將要提交事務(wù)的線程組成了一個(gè)鏈表,所以通過從頭到尾找,可以找到最后一個(gè)線程.代碼如下:
if(update_binlog_end_pos_after_sync)
{
THD*tmp_thd= final_queue;
while(tmp_thd->next_to_commit != NULL)
tmp_thd= tmp_thd->next_to_commit;
update_binlog_end_pos(tmp_thd->get_trans_pos());
}
接下來,我們來看一下這個(gè)函數(shù)update_binlog_end_pos.這個(gè)函數(shù)很簡單,傳入一個(gè)pos,然后將其賦值給全局變量binlog_end_pos,接下來就是最核心的一行代碼,signal_update(),發(fā)送binlog更新的信號.因此從主庫同步binlog到從庫的dump線程,會(huì)接收到這個(gè)binlog已有更新的信號,然后啟動(dòng)dump binlog的流程.
函數(shù)update_binlog_end_pos的完整代碼如下:
void update_binlog_end_pos(my_off_tpos)
{
lock_binlog_end_pos();
if (pos >binlog_end_pos)
binlog_end_pos= pos;
signal_update();
unlock_binlog_end_pos();
}
通過上面的步驟介紹,我們可以看到在binlog文件的最新位置更新的時(shí)候,就已經(jīng)通過signal_update函數(shù)發(fā)送信號給binlog的dump線程,該線程就可以將事務(wù)的binlog同步到從庫,從庫接收到日志之后,就可以relay日志,實(shí)現(xiàn)了主從同步.
因此,再次重復(fù)說明一下,按照上面的解釋,在事務(wù)真正提交完成之前就開始發(fā)送了binlog已經(jīng)更新的信號,dump線程收到信號,即可以進(jìn)行binlog的同步.那Semisync的作用是什么呢?
實(shí)際上,有沒有Semisync機(jī)制,對上面介紹的MySQL的有關(guān)事務(wù)提交中關(guān)于binlog的流程都是一樣的.Semisync的作用,只是主從之間的一個(gè)確認(rèn)過程,主庫等待從庫返回相關(guān)位置的binlog已經(jīng)同步到從庫的確認(rèn)(而實(shí)際實(shí)現(xiàn)則是等待dump線程給用戶會(huì)話線程一個(gè)回復(fù)),沒有得到確認(rèn)之前(或者等待時(shí)間達(dá)到timeout),事務(wù)提交則在該函數(shù)(步驟)上等待直至獲得返回.
具體執(zhí)行binlog已經(jīng)同步到某個(gè)位置的的確認(rèn)函數(shù)為repl_semi_report_binlog_sync,函數(shù)如下:
intrepl_semi_report_binlog_sync(Binlog_storage_param *param,
constchar *log_file,
my_off_t log_pos)
{
if(rpl_semi_sync_master_wait_point == WAIT_AFTER_SYNC)
returnrepl_semisync.commitTrx(log_file, log_pos);
return 0;
}
通過觀察上述函數(shù),我們可以看到有個(gè)rpl_semi_sync_master_wait_point變量與WAIT_AFTER_SYNC比較,如果不相等,則直接返回,直接返回則表示不需要在此時(shí)此刻確認(rèn)binlog是否已經(jīng)同步.而這個(gè)變量的取值來自于半同步參數(shù)semi_sync_master_wait_point的初始設(shè)置,我們可以設(shè)置為after_sync與after_commit.
這兩個(gè)參數(shù)含義的區(qū)別是:after_sync是在將binlog sync到disk之后(具體是否真正sync由參數(shù)sync_binlog的值決定)進(jìn)行日志同步確認(rèn),而after_commit是將事務(wù)完成在InnoDB里面提交之后再進(jìn)行binlog的同步確認(rèn).兩者確認(rèn)的時(shí)間點(diǎn)不同,after_sync要早于after_commit.
接下來,我們來看repl_semisync.commitTrx 這個(gè)函數(shù),這個(gè)函數(shù)有兩個(gè)傳入?yún)?shù),一個(gè)是binlog文件,一個(gè)binlog文件的位移.我們來看這個(gè)函數(shù)的含義吧.算了,還是直接用源碼的注釋來解釋吧.
上面的注釋說得相當(dāng)清楚,就是該commiTRX函數(shù)會(huì)等待binlog-dump返回已經(jīng)同步到該位置的報(bào)告,如果還沒有同步到該位置,則繼續(xù)等待,直到超時(shí)返回.
當(dāng)會(huì)話線程收到該函數(shù)的返回時(shí),事務(wù)的提交過程繼續(xù)往下走,直至在InnoDB真正提交.
通過上述對MySQL的事務(wù)提交過程中的前段分析,應(yīng)該可以了解Semi-sync的同步機(jī)制與異步機(jī)制的區(qū)別.
Semi-sync的主從同步機(jī)制與異步機(jī)制在同步的處理方式上無任何區(qū)別,唯一的區(qū)別就是Semi-sync在事務(wù)提交中段(假如設(shè)置為after_sync)或者提交后的階段(after_commit), 有一個(gè)驗(yàn)證該事務(wù)涉及的binlog是否已經(jīng)同步到從庫.而這個(gè)同步驗(yàn)證,會(huì)拉長整個(gè)事務(wù)的提交時(shí)間,因?yàn)槭聞?wù)提交在數(shù)據(jù)庫中幾乎是串行(如果按Group Commit為一個(gè)單位,就算是完全地串行),這是影響MySQL吞吐量的關(guān)鍵點(diǎn),當(dāng)這個(gè)關(guān)鍵點(diǎn)被拉長,對全局的影響就被放大.雖然僅僅多了這么一個(gè)確認(rèn)的動(dòng)作,但主庫處于Semi-sync的同步狀態(tài)與異步狀態(tài)的吞吐量相比,相差了好幾倍.
上述解釋就是其真正的原因.
作者介紹:
徐春陽,筆名happypig.曾任職于阿里、百度、京東、即刻搜索,目前供職民生銀行總行科技部,從事數(shù)據(jù)庫運(yùn)維工作.對開源數(shù)據(jù)庫MySQL有較深入的研究,個(gè)人博客:xuchunyang.com.
轉(zhuǎn)載請注明本頁網(wǎng)址:
http://www.fzlkiss.com/jiaocheng/4281.html