1、為什么需要高可用?
高可用HA(High Availability)是為了解決單機故障。
世界上沒有任何服務或者說機器是絕對安全且可靠的,但是我們可以通過一定的方案來實現系統的高可用,從一定程度上**降低單一系統宕機對整體系統帶來毀滅**的這種**無法接受**的風險。這便是高可用所要解決的痛點,通常采用服務集群,即Cluster,我們通過部署一簇相同的服務,確保master節點毀壞后可以推選出另外一個slaver作為主節點。
與分布式系統不同,分布式系統解決的是物理上的不同系統之間的隔離問題。
與高并發不同,高可用關注的是生死存亡,高并發搭配負載均衡提高的是系統整體高性能。
數據庫也不例外,也需要高可用。我們假設系統一直能夠提供服務,那么我們就說系統的可用性是100%。如果系統每運行100個時間單位,會有1個時間單位無法提供服務,我們說系統的可用性是99%。很多公司的高可用目標是4個9,也就是99.99%,這就意味著,系統的年停機時間為8.76個小時。
2、數據庫中間件
2.1、簡介
數據庫中間件是一類連接應用程序和數據庫的計算機軟件,以便于軟件各部件之間額溝通,類似于tomcat等web中間件,只是連接的雙方不一樣而已。
原始的java應用程序直接連接數據庫的方式有以下缺點:
1、java程序與數據庫緊密耦合
2、高訪問量并發量下對數據庫產生很大壓力
3、讀寫未分離
所以中間件看起來就像是一個邏輯數據庫,真實的物理數據對外是隱藏的。這個思想和Nginx是一樣的。
2.2、分類對比,及mycat的重點介紹
Cobar:屬于阿里b2b事業群,始于08年,在阿里服役多年。接管3000+mysql數據庫的schema集群日志處理,在線sql請求50億以上,由于Cobar發起人的離職,停止維護。
Mycat:開源社區在阿里Cobar基礎上進行的二次開發。并且加入新功能。
oneproxy:給予mysql官方的proxy思想,性能很好但是收費的。
kingshared:基于go語言,需要繼續發展,開源的。
當然除此之外,還有很多中間件,例如vitess,atlas,maxscale等等。
可以直觀地看到mycat應該是目前最合適的中間件,社區活躍度高切開源。
mycat的功能包括:讀寫分離、負載均衡、分庫分表(數據分片)、多數據源整合。
Mycat原理:Mycat的原理中最重要的一個概念就是“攔截”,它攔截了用戶發過來的sql語句,首先對sql語句做了一些特定的分析:如分片分析、路由分析、讀寫分離分析、緩存分析等等,然后將此sql發送到后端的真實數據庫,并將返回的結果做適當的處理,最終再返回給用戶。
3、mysql高可用主流架構
3.1、雙機高可用
架構:
一臺機器A作為讀寫庫,另一臺B作為備份庫;A庫故障后B庫作為讀寫庫;A庫恢復后A作為備庫。
配置:
此種情況下,數據源配置中的數據庫IP地址,可采用虛擬的IP地址。虛擬IP地址由兩臺數據庫機器上的keepalive配置,并互相檢測心跳。當其中一臺故障后,虛擬IP地址會自動漂移到另外一臺正常的庫上。數據庫的主備配置、故障排除和數據補全,需要DBA和運維人員來維護。而程序代碼或配置并不需要修改。
適用場景及優缺點:
讀和寫都不高的場景(單表數據低于500萬),雙機高可用。優點是一個機器故障了可以自動切換;缺點是只有一個庫在工作,讀寫并未分離,并發有限制。
3.2、主從讀寫分離
架構:
??一臺機器A作為寫庫,另一臺B作為讀庫;A庫故障后B庫充當讀寫,A修復后,B庫為寫庫,A庫為讀庫。
配置:
?這種方案的實現,要借助數據庫中間件Mycat來實現,項目開發中,要配置Mycat數據源,并實現對Mycat數據源的數據操作。數據庫A和數據庫B應該互為主從。數據庫的主主配置、故障排除和數據補全,依然需要DBA和運維人員來維護。
使用場景及優缺點:
讀和寫都不是非常高的場景(單表數據低于1000萬),高可用。比方案一并發要高很多。優點是一個機器故障了可以自動切換;讀寫分離,并發有了很大的提升。缺點是引入了一個Mycat節點,若要高可用需要引入至少兩個Mycat。常規的解決方案是引入haproxy和keepalive對mycat做集群。
3.3、一主多從+讀寫分離
架構:
一個主寫庫A多個從庫,當主庫A故障時,提升從庫B為主寫庫,同時修改C、D庫為B的從庫。A故障修復后,作為B的從庫。
配置:
項目開發中需要使用Mycat作為中間件,主庫A故障后,Mycat會自動把從B提升為寫庫。而C、D從庫,則可以通過MHA等工具,自動修改其主庫為B。進而實現自動切換的目地。
MHA Manager可以單獨部署在一臺獨立的機器上管理多個master-slave集群,也可以部署在一臺slave節點上。MHA Node運行在每臺MySQL服務器上,MHA Manager會定時探測集群中的master節點,當master出現故障時,它可以自動將最新數據的slave提升為新的master,然后將所有其他的slave重新指向新的master。整個故障轉移過程對應用程序完全透明。
使用場景及優缺點:
該架構適合寫并發不大、但是讀并發大的很的場景。由于配置了多個讀節點,讀并發的能力有了質的提高。理論上來說,讀節點可以多個,可以負載很高級別的讀并發。當然,Mycat依然需要設計高可用方案。
3.4、MariaDB Galera Cluster
架構:
多個數據庫,在負載均衡作用下,可同時進行寫入和讀取操作;各個庫之間以Galera Replication的方法進行數據同步,即每個庫理論上來說,數據是完全一致的。
配置:
數據庫讀寫時,只需要修改數據庫讀寫IP為keepalive的虛擬節點即可;數據庫配置方面相對比較復雜,需要引入haproxy、keepalive、Galaera等各種插件和配置。
使用場景及優缺點:
該方案適合讀寫并發較大、數據量不是非常大的場景。
?優點:
1)可以在任意節點上進行讀
2)自動剔除故障節點
3)自動加入新節點
4)真正并行的復制,基于行級
5)客戶端連接跟操作單數據庫的體驗一致
6)?同步復制,因此具有較高的性能和可靠性。
????缺點:
1) DELETE操作不支持沒有主鍵的表,沒有主鍵的表在不同的節點順序將不同
2)處理事務時,會運行一個協調認證程序來保證事務的全局一致性,若該事務長時間運行,就會鎖死節點中所有的相關表,導致插入卡住(這種情況和單表插入是一樣的)
3)整個集群的寫入吞吐量是由最弱的節點限制,如果有一個節點變得緩慢,那么整個集群將是緩慢的。為了穩定的高性能要求,所有的節點應使用統一的硬件
4)如果DDL語句有問題將破壞集群,建議禁用
5)?Mysql數據庫5.7.6及之后的版本才支持此種方案
3.5、數據庫中間件
架構:
采用Mycat進行分片存儲,可以解決寫負載均衡和數據量過大問題;每個分片配置多個讀從庫,可以減少單個庫的讀壓力。
配置:
此種情況,需要配置Haproxy、keepalive和mycat集群,每個分片上又需要配置一主多從的集群。每個分片上的完整配置,具體請參考方案三,可以簡單地把方案三理解為一個分片結構。因此,配置和維護量都比較大。
場景及優缺點:
?讀寫并發都很大并且數據量非常大的場景。
?優點:終極的解決高并發高數據量的方法。
?缺點:配置和維護都比較麻煩,需要的軟硬件設備資源大。
大多數的高可用架構都需要進行主從復制操作,因為這是確保多節點數據一致性的最佳辦法。
4、主從復制
綜合上述架構,我們可以看到如下場景為我們其實都需要做主從復制的備份:
1、在業務復雜的系統中,有這么一個情景,有一句sql語句需要鎖表,導致暫時不能使用讀的服務,那么就很影響運行中的業務,使用主從復制,讓主庫負責寫,從庫負責讀,這樣,即使主庫出現了鎖表的情景,通過讀從庫也可以保證業務的正常運行。
2、做數據的熱備,主庫宕機后能夠及時替換主庫,保證業務可用性。
3、架構的擴展。業務量越來越大,I/O訪問頻率過高,單機無法滿足,此時做多庫的存儲,降低磁盤I/O訪問的頻率,提高單個機器的I/O性能。
4.1、原理
MySQL主從復制是一個異步的復制過程,主庫發送更新事件到從庫,從庫讀取更新記錄,并執行更新記錄,使得從庫的內容與主庫保持一致。流程大致如下:
1、主庫db的更新事件(update、insert、delete)被寫到binlog
2、主庫創建一個binlog dump thread,把binlog的內容發送到從庫
3、從庫啟動并發起連接,連接到主庫
4、從庫啟動之后,創建一個I/O線程,讀取主庫傳過來的binlog內容并寫入到relay log(中繼日志)
5、從庫啟動之后,創建一個SQL線程,從relay log里面讀取內容,從Exec_Master_Log_Pos位置開始執行讀取到的更新事件,將更新內容寫入到slave
4.2、延遲問題
4.2.1、延遲的原因
1、MySQL數據庫主從同步延遲原理mysql主從同步原理
主庫針對寫操作,順序寫binlog,從庫單線程去主庫順序讀”寫操作的binlog”,從庫取到binlog在本地原樣執行(隨機寫),來保證主從數據邏輯上一致。mysql的主從復制都是單線程的操作,主庫對所有DDL和DML產生binlog,binlog是順序寫,所以效率很高,slave的Slave_IO_Running線程到主庫取日志,效率比較高,下一步,問題來了,slave的Slave_SQL_Running線程將主庫的DDL和DML操作在slave實施。DML和DDL的IO操作是隨即的,不是順序的,成本高很多,還可能可slave上的其他查詢產生lock爭用,由于Slave_SQL_Running也是單線程的,所以一個DDL卡主了,需要執行10分鐘,那么所有之后的DDL會等待這個DDL執行完才會繼續執行,這就導致了延時。有朋友會問:“主庫上那個相同的DDL也需要執行10分,為什么slave會延時?”,答案是master可以并發,Slave_SQL_Running線程卻不可以。
2、MySQL數據庫主從同步延遲是怎么產生的?
當主庫的TPS并發較高時,產生的DDL數量超過slave一個sql線程所能承受的范圍,那么延時就產生了,當然還有就是可能與slave的大型query語句產生了鎖等待。首要原因:數據庫在業務上讀寫壓力太大,CPU計算負荷大,網卡負荷大,硬盤隨機IO太高次要原因:讀寫binlog帶來的性能影響,網絡傳輸延遲。
3、如何查看是否延遲?
首先在服務器上執行show slave satus;可以看到很多同步的參數:
Master_Log_File:? ? ? ? ? ? ? ? ? ? ? SLAVE中的I/O線程當前正在讀取的主服務器二進制日志文件的名稱
Read_Master_Log_Pos:? ? ? ? 在當前的主服務器二進制日志中,SLAVE中的I/O線程已經讀取的位置
Relay_Log_File:? ? ? ? ? ? ? ? ? ? ? ? SQL線程當前正在讀取和執行的中繼日志文件的名稱
Relay_Log_Pos:? ? ? ? ? ? ? ? ? ? ? ? 在當前的中繼日志中,SQL線程已讀取和執行的位置
Relay_Master_Log_File:? ? ? 由SQL線程執行的包含多數近期事件的主服務器二進制日志文件的名稱
Slave_IO_Running:? ? ? ? ? ? ? ? I/O線程是否被啟動并成功地連接到主服務器上
Slave_SQL_Running:? ? ? ? ? ? ? SQL線程是否被啟動
Seconds_Behind_Master:? ? 從屬服務器SQL線程和從屬服務器I/O線程之間的時間差距,單位以秒計
4、從庫同步延遲情況出現的
●?show slave status顯示參數Seconds_Behind_Master不為0,這個數值可能會很大
●?show slave status顯示參數Relay_Master_Log_File和Master_Log_File顯示bin-log的編號相差很大,說明bin-log在從庫上沒有及時同步,所以近期執行的bin-log和當前IO線程所讀的bin-log相差很大
●?mysql的從庫數據目錄下存在大量mysql-relay-log日志,該日志同步完成之后就會被系統自動刪除,存在大量日志,說明主從同步延遲很厲害
4.2.2、解決方案
1. 半同步復制
從MySQL5.5開始,MySQL已經支持半同步復制了,半同步復制介于異步復制和同步復制之間,主庫在執行完事務后不立刻返回結果給客戶端,需要等待至少一個從庫接收到并寫到relay log中才返回結果給客戶端。相對于異步復制,半同步復制提高了數據的安全性,同時它也造成了一個TCP/IP往返耗時的延遲。
2. 主庫配置sync_binlog=1,innodb_flush_log_at_trx_commit=1
sync_binlog的默認值是0,MySQL不會將binlog同步到磁盤,其值表示每寫多少binlog同步一次磁盤。
innodb_flush_log_at_trx_commit為1表示每一次事務提交或事務外的指令都需要把日志flush到磁盤。
注意:將以上兩個值同時設置為1時,寫入性能會受到一定限制,只有對數據安全性要求很高的場景才建議使用,比如涉及到錢的訂單支付業務,而且系統I/O能力必須可以支撐!
其他方案:?
1. 優化網絡
2. 升級Slave硬件配置
3. Slave調整參數,關閉binlog,修改innodb_flush_log_at_trx_commit參數值
4. 升級MySQL版本到5.7,使用并行復制
優酷的解決方案:數據庫分片技術,而拋棄了由于數據量的越來越多導致復制延遲的問題。按照user_id進行分片,這樣必須有一個全局的表來管理用戶與shard的關系,根據user_id可以得到share_id,然后根據share_id去指定的分片查詢指定的數據
淘寶的解決方案:修改源碼,對應的機制是Transfer機制,此處通過對Binlog日志重做采用多線程實現,從而提高slave的QPPS
附加、如何確保mysql與redis的數據一致性?
單一線程下,刪除緩存再去更新數據庫,可以做到redis與mysql的一致性,但是高并發下,存在如下情況:
不一致場景一:A線程需要更新id=1 column1=3,此時數據庫是2,當A刪除緩存,還沒來得及更新數據庫,線程B進行query訪問,查詢id=3的column1的值,發現數據庫是2,然后成功返回,并更新緩存(因為緩存被A已經刪除,所以需要更新),此時A完成了對數據的更新,數據庫的值變成了3。而緩存內仍然是2然后線程C準備查詢,就會查到這個緩存中的臟數據2,而不是真實值3。這就是redis 與數據庫不一致產生的場景。
不一致場景二:雙寫不一致
可能還會有更復雜的場景,但是它們產生的原因是一樣的,緩存系統在承擔了分散數據庫壓力的同時,難免出現這種滯后性,因為傳統單一的數據庫操作是沒有這種風險的。根本在于原先的只需要維護一套系統的簡單操作變成了兩個系統的分離操作。
解決這個問題的思路有如下幾個:
1、利用特殊值進行線程阻塞,當A準備更新數據庫時,先將緩存內的值修改為-9999之類,然后B查詢時,發現如果是約定的特殊值,則進入休眠,直到A完成操作更新cache,這種思路的本質是串行化,
2、或者所有的update操作在java代碼中就進行串行化。
3、經典延時雙刪:如果數據update操作頻繁,仍然會存在臟數據問題。
讀請求和寫請求串行化,串到一個內存隊列里去,這樣就可以保證一定不會出現不一致的情況。所以,如果你的系統不是嚴格要求緩存+數據庫必須一致性的話,緩存可以稍微的跟數據庫偶爾有不一致的情況,最好不要做這個方案。串行化之后,就會導致系統的吞吐量會大幅度的降低,用比正常情況下多幾倍的機器去支撐線上的一個請求。