一、讀寫分離:
1、基本結構:
?<1>、客戶端直連:

?<2>、帶proxy的讀寫分離架構:

1、目的:
??分攤主庫的壓力。
??對于不帶proxy的讀寫分離架構,客戶端(client)主動做負載均衡,這種模式下一般會把數據庫的連接信息放在客戶端的連接層。也就是說,由客戶端來選擇后端數據庫進行查詢。
??對于帶proxy的讀寫分離架構,客戶端只連接proxy, 由proxy根據請求類型和上下文決定請求的分發(fā)路由。
2、客戶端直連和帶proxy的讀寫分離架構,各自特點:
?<1>、客戶端直連方案:
?①、少了一層proxy轉發(fā),所以查詢性能稍微好一點兒。
?②、并且整體架構簡單,排查問題更方便。
?③、由于要了解后端部署細節(jié),所以在出現主備切換、庫遷移等操作的時候,客戶端都會感知到,并且需要調整數據庫連接信息。
?④、一般采用這樣的架構,一定會伴隨一個負責管理后端的組件,比如Zookeeper,盡量讓業(yè)務端只專注于業(yè)務邏輯開發(fā)。
?<2>、帶proxy的架構:
?①、對客戶端比較友好。客戶端不需要關注后端細節(jié),連接維護、后端信息維護等工作,都是由proxy完成的。
?②、對后端維護團隊的要求會更高。
?③、proxy需要有高可用架構。帶proxy架構的整體就相對比較復雜。
二、過期讀:
?1、過期讀的含義:
??由于主從可能存在延遲,客戶端執(zhí)行完一個更新事務后馬上發(fā)起查詢,如果查詢選擇的是從庫的話,就有可能讀到剛剛的事務更新之前的狀態(tài)。這種“在從庫上會讀到系統(tǒng)的一個過期狀態(tài)”的現象,稱之為“過期讀”。
?2、過期讀的處理方案:
??<1>、強制走主庫方案:
??強制走主庫方案其實就是將查詢請求做分類:
??①、對于必須要拿到最新結果的請求,強制將其發(fā)到主庫上。比如,在一個交易平臺上,賣家發(fā)布商品以后,馬上要返回主頁面,看商品是否發(fā)布成功。那么,這個請求需要拿到最新的結果,就必須走主庫。
??②、對于可以讀到舊數據的請求,才將其發(fā)到從庫上。在這個交易平臺上,買家來逛商鋪頁面,就算晚幾秒看到最新發(fā)布的商品,也是可以接受的。那么,這類請求就可以走從庫。
注意:有時候會碰到“所有查詢都不能是過期讀”的需求,這時就要放棄讀寫分離,所有讀寫壓力都在主庫,等同于放棄了擴展性。
??<2>、sleep方案:
??類似于執(zhí)行一條select sleep(1)命令。這個方案的假設是,大多數情況下主備延遲在1秒之內,做一個sleep可以有很大概率拿到最新的數據。
注意:
??如果這個查詢請求本來0.5秒就可以在從庫上拿到正確結果,也會等1秒;
??如果延遲超過1秒,還是會出現過期讀。
??<3>、判斷主備無延遲方案:
??①、每次從庫執(zhí)行查詢請求前,先判斷seconds_behind_master是否已經等于0。如果還不等于0 ,那就必須等到這個參數變?yōu)?才能執(zhí)行查詢請求。
??②、對比位點確保主備無延遲:
????、Master_Log_File和Read_Master_Log_Pos,表示的是讀到的主庫的最新位點;
????、Relay_Master_Log_File和Exec_Master_Log_Pos,表示的是備庫執(zhí)行的最新位點。
??如果Master_Log_File和Relay_Master_Log_File、Read_Master_Log_Pos和Exec_Master_Log_Pos這兩組值完全相同,表示接收到的日志已經同步完成。
??③、對比GTID集合確保主備無延遲:
????、Auto_Position=1 ,表示這對主備關系使用了GTID協(xié)議。
????、Retrieved_Gtid_Set,是備庫收到的所有日志的GTID集合;
????、Executed_Gtid_Set,是備庫所有已經執(zhí)行完成的GTID集合。
??如果這兩個集合相同,表示備庫接收到的日志都已經同步完成。
注意:還是有可能出現過期讀的。
??<4>、配合semi-sync方案:
???(1)、semi-sync replication(半同步復制)原理:
????、事務提交的時候,主庫把binlog發(fā)給從庫;
????、從庫收到binlog以后,發(fā)回給主庫一個ack,表示收到了;
????、主庫收到這個ack以后,才能給客戶端返回“事務完成”的確認。
也就是說,如果啟用了semi-sync,就表示所有給客戶端發(fā)送過確認的事務,都確保了備庫已經收到了這個日志。
???(2)、semi-sync+位點判斷的方案在一主多從場景中,主庫只要等到一個從庫的ack,就開始給客戶端返回確認。這時,在從庫上執(zhí)行查詢請求,就有兩種情況:
????、如果查詢是落在這個響應了ack的從庫上,是能夠確保讀到最新數據;
????、但如果是查詢落到其他從庫上,它們可能還沒有收到最新的日志,就會產生過期讀的問題。
如果在業(yè)務更新的高峰期,主庫的位點或者GTID集合更新很快,那么上面的兩個位點等值判斷就會一直不成立,很可能出現從庫上遲遲無法響應查詢請求的情況。
???(3)、semi-sync配合判斷主備無延遲的方案,存在的問題:
????、一主多從的時候,在某些從庫執(zhí)行查詢請求會存在過期讀的現象;
????、在持續(xù)延遲的情況下,可能出現過度等待的問題。
??<5>、等主庫位點方案:
???(1)、命令:select master_pos_wait(file, pos[, timeout]);的邏輯:
????、它是在從庫執(zhí)行的;
????、參數file和pos指的是主庫上的文件名和位置;
????、timeout可選,設置為正整數N表示這個函數最多等待N秒。
???(2)、命令:select master_pos_wait(file, pos[, timeout]);的返回結果:
????、正常返回結果是一個正整數M,表示從命令開始執(zhí)行,到應用完file和pos表示的binlog位置,執(zhí)行了多少事務。
????、如果執(zhí)行期間,備庫同步線程發(fā)生異常,則返回NULL;
????、如果等待超過N秒,就返回-1;
????、如果剛開始執(zhí)行的時候,就發(fā)現已經執(zhí)行過這個位置了,則返回0。
??<6>、等GTID方案:
???(1)、命令:select wait_for_executed_gtid_set(gtid_set, 1);的邏輯:
????、等待,直到這個庫執(zhí)行的事務中包含傳入的gtid_set,返回0;
????、超時返回1。
???(2)、MySQL在執(zhí)行事務后,返回包中如何帶上GTID:
???將參數session_track_gtids設置為OWN_GTID,然后通過API接口mysql_session_track_get_first從返回包解析出GTID的值即可。