依據一些雲廠商的 Benchmark 的結果,在 4 核 8G 的機器上運行 MySQL 5.7 時,大概能夠支撐 500 的 TPS 和 10000 的 QPS。這時,運營負責人說正在準備雙十一活動,而且公司層面會繼續投入資金在全渠道進行推廣,這無疑會引起查詢量驟然增長的問題。那麼當查詢請求增長時,應該如何作主從分離來解決問題。
前端
主從讀寫分離數據庫
其實,大部分系統的訪問模型是讀多寫少,讀寫請求量的差距可能達到幾個數量級。緩存
這很好理解,刷朋友圈的請求量確定比發朋友圈的量大,淘寶上一個商品的瀏覽量也確定遠大於它的下單量。所以,咱們優先考慮數據庫如何抵抗更高的查詢請求,那麼首先你須要把讀寫流量區分開,由於這樣才方便針對讀流量作單獨的擴展,這就是咱們所說的主從讀寫分離。服務器
它實際上是個流量分離的問題,就比如道路交通管制同樣,一個四車道的大馬路劃出三個車道給領導外賓經過,另一個車道給咱們使用,優先保證領導先行,就是這個道理。網絡
這個方法自己是一種常規的作法,即便在一個大的項目中,它也是一個應對數據庫突發讀流量的有效方法。併發
我目前的項目中就曾出現過前端流量突增致使從庫負載太高的問題,DBA 兄弟會優先作一個從庫擴容上去,這樣對數據庫的讀流量就會落入到多個從庫上,從庫的負載就降了下來,而後研發同窗再考慮使用什麼樣的方案將流量擋在數據庫層之上。運維
主從讀寫的兩個技術關鍵點異步
通常來講在主從讀寫分離機制中,咱們將一個數據庫的數據拷貝爲一份或者多份,而且寫入到其它的數據庫服務器中,原始的數據庫咱們稱爲主庫,主要負責數據的寫入,拷貝的目標數據庫稱爲從庫,主要負責支持數據查詢。能夠看到,主從讀寫分離有兩個技術上的關鍵點:ide
1. 一個是數據的拷貝,咱們稱爲主從複製;性能
2. 在主從分離的狀況下,咱們如何屏蔽主從分離帶來的訪問數據庫方式的變化,讓開發同窗像是在使用單一數據庫同樣。
1. 主從複製
MySQL 的主從複製是依賴於 binlog 的,也就是記錄 MySQL 上的全部變化並以二進制形式保存在磁盤上二進制日誌文件。主從複製就是將 binlog 中的數據從主庫傳輸到從庫上,通常這個過程是異步的,即主庫上的操做不會等待 binlog 同步的完成。
主從複製的過程是這樣的:首先從庫在鏈接到主節點時會建立一個 IO 線程,用以請求主庫更新的 binlog,而且把接收到的 binlog 信息寫入一個叫作 relay log 的日誌文件中,而主庫也會建立一個 log dump 線程來發送 binlog 給從庫;同時,從庫還會建立一個 SQL 線程讀取 relay log 中的內容,而且在從庫中作回放,最終實現主從的一致性。這是一種比較常見的主從複製方式。
在這個方案中,使用獨立的 log dump 線程是一種異步的方式,能夠避免對主庫的主體更新流程產生影響,而從庫在接收到信息後並非寫入從庫的存儲中,是寫入一個 relay log,是避免寫入從庫實際存儲會比較耗時,最終形成從庫和主庫延遲變長。
你會發現,基於性能的考慮,主庫的寫入流程並無等待主從同步完成就會返回結果,那麼在極端的狀況下,好比說主庫上 binlog 尚未來得及刷新到磁盤上就出現了磁盤損壞或者機器掉電,就會致使 binlog 的丟失,最終形成主從數據的不一致。不過,這種狀況出現的機率很低,對於互聯網的項目來講是能夠容忍的。
作了主從複製以後,咱們就能夠在寫入時只寫主庫,在讀數據時只讀從庫,這樣即便寫請求會鎖表或者鎖記錄,也不會影響到讀請求的執行。同時呢,在讀流量比較大的狀況下,咱們能夠部署多個從庫共同承擔讀流量,這就是所說的「一主多從」部署方式,在你的垂直電商項目中就能夠經過這種方式來抵禦較高的併發讀流量。另外,從庫也能夠當成一個備庫來使用,以免主庫故障致使數據丟失。
那麼你可能會說,是否是我無限制地增長從庫的數量就能夠抵抗大量的併發呢?實際上並非的。由於隨着從庫數量增長,從庫鏈接上來的 IO 線程比較多,主庫也須要建立一樣多的 log dump 線程來處理複製的請求,對於主庫資源消耗比較高,同時受限於主庫的網絡帶寬,因此在實際使用中,通常一個主庫最多掛 3~5 個從庫。
固然,主從複製也有一些缺陷,除了帶來了部署上的複雜度,還有就是會帶來必定的主從同步的延遲,這種延遲有時候會對業務產生必定的影響,我舉個例子你就明白了。
在發微博的過程當中會有些同步的操做,像是更新數據庫的操做,也有一些異步的操做,好比說將微博的信息同步給審覈系統,因此咱們在更新完主庫以後,會將微博的 ID 寫入消息隊列,再由隊列處理機依據 ID 在從庫中獲取微博信息再發送給審覈系統。此時若是主從數據庫存在延遲,會致使在從庫中獲取不到微博信息,整個流程會出現異常。
這個問題解決的思路有不少,核心思想就是儘可能不去從庫中查詢信息,純粹以上面的例子來講,我就有三種解決方案:
第一種方案是數據的冗餘。你能夠在發送消息隊列時不只僅發送微博 ID,而是發送隊列處理機須要的全部微博信息,藉此避免從數據庫中從新查詢數據。
第二種方案是使用緩存。我能夠在同步寫數據庫的同時,也把微博的數據寫入到 Memcached 緩存裏面,這樣隊列處理機在獲取微博信息的時候會優先查詢緩存,這樣也能夠保證數據的一致性。
最後一種方案是查詢主庫。我能夠在隊列處理機中不查詢從庫而改成查詢主庫。不過,這種方式使用起來要慎重,要明確查詢的量級不會很大,是在主庫的可承受範圍以內,不然會對主庫形成比較大的壓力。
另外,主從同步的延遲,是咱們排查問題時很容易忽略的一個問題。有時候咱們遇到從數據庫中獲取不到信息的詭異問題時,會糾結於代碼中是否有一些邏輯會把以前寫入的內容刪除,可是你又會發現,過了一段時間再去查詢時又能夠讀到數據了,這基本上就是主從延遲在做怪。因此,通常咱們會把從庫落後的時間做爲一個重點的數據庫指標作監控和報警,正常的時間是在毫秒級別,一旦落後的時間達到了秒級別就須要告警了。
2. 如何訪問數據庫
咱們已經使用主從複製的技術將數據複製到了多個節點,也實現了數據庫讀寫的分離,這時,對於數據庫的使用方式發生了變化。之前只須要使用一個數據庫地址就行了,如今須要使用一個主庫地址和多個從庫地址,而且須要區分寫入操做和查詢操做,若是結合下一節課中要講解的內容「分庫分表」,複雜度會提高更多。爲了下降實現的複雜度,業界涌現了不少數據庫中間件來解決數據庫的訪問問題,這些中間件能夠分爲兩類。
第一類以淘寶的 TDDL( Taobao Distributed Data Layer)爲表明,以代碼形式內嵌運行在應用程序內部。你能夠把它當作是一種數據源的代理,它的配置管理着多個數據源,每一個數據源對應一個數據庫,多是主庫,多是從庫。當有一個數據庫請求時,中間件將 SQL 語句發給某一個指定的數據源來處理,而後將處理結果返回。
這一類中間件的優勢是簡單易用,沒有多餘的部署成本,由於它是植入到應用程序內部,與應用程序一同運行的,因此比較適合運維能力較弱的小團隊使用;缺點是缺少多語言的支持,目前業界這一類的主流方案除了 TDDL,還有早期的網易 DDB,它們都是 Java 語言開發的,沒法支持其餘的語言。另外,版本升級也依賴使用方更新,比較困難。
另外一類是單獨部署的代理層方案,這一類方案表明比較多,如早期阿里巴巴開源的 Cobar,基於 Cobar 開發出來的 Mycat,360 開源的 Atlas,美團開源的基於 Atlas 開發的 DBProxy 等等。
這一類中間件部署在獨立的服務器上,業務代碼如同在使用單一數據庫同樣使用它,實際上它內部管理着不少的數據源,當有數據庫請求時,它會對 SQL 語句作必要的改寫,而後發往指定的數據源。它通常使用標準的 MySQL 通訊協議,因此能夠很好地支持多語言。因爲它是獨立部署的,因此也比較方便進行維護升級,比較適合有必定運維能力的大中型團隊使用。它的缺陷是全部的 SQL 語句都須要跨兩次網絡:從應用到代理層和從代理層到數據源,因此在性能上會有一些損耗。
總結
這些中間件,對你而言,可能並不陌生,可是我想讓你注意到是,在使用任何中間件的時候必定要保證對於中間件有足夠深刻的瞭解,不然一旦出了問題無法快速地解決就悲劇了。
附
一直使用自研的一個組件來實現分庫分表,後來發現這套組件有必定概率會產生對數據庫多餘的鏈接,因而團隊討論後決定替換成 Sharding-JDBC。本來覺得是一次簡單的組件切換,結果上線後發現兩個問題:一是由於使用姿式不對,會偶發地出現分庫分表不生效致使掃描全部庫表的狀況,二是偶發地出現查詢延時達到秒級別。因爲對 Sharding-JDBC 沒有足夠了解,這兩個問題咱們都沒有很快解決,後來不得已只能切回原來的組件,在找到問題以後再進行切換