之所以進行這塊內容的研究,主要針對先前遇到的兩個未解的疑惑:
a. 線上有個系統,半同步狀態常從半同步變成異步,然後又馬上恢復為半同步,具體原因未明,雖然之前也猜得八九不離十,但還是未完全確定。
b. 前段時間因為有業務場景需求,做了跨機房非同步複製測試。當mysql寫qps非常高的時候,發現很多日誌還沒來得及發送到從庫,也就是binlog日誌在主庫的產生速度大於傳送到從庫的速度,這個速度差一直存在,因此當主庫持續高壓力地產生binlog的時候,越來越多的binlog沒有傳送到從庫,但當時的網路流量也才18M/S左右(一主一從),從常規的知識認為,千兆的網路傳送的速度可以達到100M,而目前的主從之間的binlog傳送速度只達到了18M左右,原因是什麼?是網路問題?還是其他原因。
主從複製原理 Dump線程與io線程當主從複製關係建立之後,主庫上有個dump線程,用來傳送在主庫產生的binlog日誌的,而從庫上的io線程,則用來接收由dump線程通過網絡傳送到從庫的binlog日誌,並負責將其寫入relay log 中去。這就是主從複製的機制, 同時,由於是非同步複製,傳送過程不需要ack的確認。
疑問也正在這裡——因為是非同步傳輸,如果單純地理解為binlog檔案直接網路傳送,這個速度應該很快才是,但實際情況:在我們的測試環境中,binlog日誌的傳送速度才18M/s ,小於日誌產生的22M/s左右的速率。為什麼只有這個速度,而沒有把網路頻寬用滿?原因是什麼?
日誌傳送細節主從複製的結構中,主庫上的dump線程跟從庫上的io線程各自有一個,所以不存在多線程地並發發送跟接收的情況,只需要了解binlog dump線程的工作機制,就能了解所有的細節。
透過解析binlog文件,我們可以知道,一個事務可以包含多個event, 下面是一個最簡單的事物的在binlog中所記錄的資訊:
# at 33580 #170531 17:22:53 server id 153443358 end_log_pos 33645 CRC32 0x4ea17869 GTID last_committed=125 sequence_number=126 SET @@SESSION.GTID_NEXT= ‘e1028e43-4123-11e7-a3c2-005056aa17e6:198’/*!*/; # at 33645 #170531 17:22:53 server id 153443358 end_log_pos 33717 CRC32 0x66820e00 Query thread_id=4 exec_time=0 error_code=0 SET TIMESTAMP=1496222573/*!*/; BEGIN /*!*/; # at 33717 #170531 17:22:53 server id 153443358 end_log_pos 33770 CRC32 0x22ddf25e Table_map: `test`.`xcytest` mapped to number 222 # at 33770 #170531 17:22:53 server id 153443358 end_log_pos 33817 CRC32 0x61051ea0 Write_rows: table id 222 flags: STMT_END_F BINLOG ‘ bYsuWRMeXCUJNQAAAOqDAAAAAN4AAAAAAAEABHRlc3QAB3hjeXRlc3QAAgMPAlgCAl7y3SI= bYsuWR4eXCUJLwAAABmEAAAAAN4AAAAAAAEAAgAC//x9AAAABQBzZGZhc6AeBWE= ‘/*!*/; ### INSERT INTO `test`.`xcytest` ### SET ### @1=125 /* INT meta=0 nullable=0 is_null=0 */ ### @2=’sdfas’ /* VARSTRING(600) meta=600 nullable=1 is_null=0 */ # at 33817 #170531 17:22:53 server id 153443358 end_log_pos 33848 CRC32 0x630805b4 Xid = 303 COMMIT/*!*/;
每一個at xxxxx段,是一個event .
函數Binlog_sender::send_events 就是發送binlog中的event事件的函數:
函數入參:#end_pos,目前讀到的binlog檔案的最末尾位置。
log_cache,記錄是目前傳送的日誌的訊息,包含已經傳送的binlog日誌的位置,以及binlog日誌檔。
函數邏輯分析:#如果目前已傳送的位置log_pos小於已取得的檔案的末尾位置end_pos.則表示還有binlog日誌未傳送,進入循環。
循環體內:
a. 先呼叫函數read_event,取得一個事件event.
b. Log_event_type event_type= (Log_event_type)event_ptr[EVENT_TYPE_OFFSET];
該語句用來取得事件event的型別,然後進行型別檢查
check_event_type(event_type, log_file, log_pos),如果沒有通過檢查,直接傳回1給上層函數。
c. log_pos= my_b_tell(log_cache); 更新log_pos位置,也就是將讀binlog位置的遊標前移到目前位置。
d. 接著呼叫send_packet() 函數進行binlog的傳送。
原來, 不管目前還有多少binlog沒有同步到從庫,主庫發送binlog的粒度依然是一個event的發送,在發送之前,還需要檢查event的型別。因為是小包發送,所以網路的流量並不大。
但需要說明一下出現這個現象的前提條件:我們測試環境中,當時資料庫的寫入qps達到了50000以上,所以需要發送的event特別多,即使是異步,也導致單線程的dump線程來不及發送目前產生的日誌。
當寫的qps巨大的時候,確實存在來不及發送日誌的情景。
總結現在,再來回頭看線上遇到的問題,“同步狀態經常從半同步狀態變成異步狀態,然後又被及時復原到半同步狀態”,原因是該資料庫是一個分析系統,有時會做批量的更新,以及批量的導入。同時,資料庫設定的binlog格式為row模式,對於一個更新多行的事務,裡麵包含很多的event(一行是一個event), 所以發送這個事務的binlog耗時會比較長,無法在1秒鐘內發送完成(半同步的timeout時間設定為1),所以半同步狀態變成了非同步。
以上是分析mysql主從同步異步的使用場景的詳細內容。更多資訊請關注PHP中文網其他相關文章!