高性能MySQL架構

關注公衆號:xy的技術圈算法

在前面的文章裏,分享了MySQL索引的原理及使用技巧、MySQL查詢語句的優化等方面的知識。這些都是針對單個庫的性能優化。在微服務和容器、雲的時代,應用層能夠很方便地水平擴展,用於支撐更大的併發量。數據庫

大多數開發人員都知道,數據庫是性能上比較大的一個瓶頸。因此如何架構和設計數據庫,使它可以支持高併發,就變得很是重要。本文主要介紹MySQL的一些高性能架構方面的知識點。好比主從、讀寫分離、分庫分表等。緩存

雖然MySQL原生並非原生的分佈式數據庫,須要客戶端或者數據庫中間件配合來使用集羣。但學習瞭解它們的原理也是很是重要的,由於一樣的原理也能夠用於其它廠商的關係型數據庫。對本身理解分佈式架構也有很大的幫助。性能優化

下面以一個小應用的規模發展來一步步介紹這個應用的MySQL架構演變。服務器

單體應用和單體數據庫

最開始,應用比較小,訪問量比較小。事實上絕大多數應用的訪問量都是一開始比較小,後來再慢慢擴展增長的。架構

這個時候全部的請求都到一個應用,而後到同一個數據庫:併發

單體

在這個階段,若是併發量緩慢增長,能夠暫時經過增長硬件性能來解決。好比更高性能的CPU、更多的內存等。分佈式

分佈式應用和讀寫分離

隨着業務的擴展,系統的併發量愈來愈大,原有的單體應用和單體數據的架構已經不能很好地支撐如今的併發量了。這個時候須要把應用橫向擴展成分佈式的應用。但他們最終都是要持久化到數據庫的,因此數據庫也須要做出必定的調整來支撐當前併發量。函數

正常的業務流程,對數據庫的操做無非是兩種狀況,讀和寫。從前面介紹的文章能夠了解到,若是創建索引能夠顯著提升讀的性能,但會影響寫的性能。而在大多數互聯網項目中,讀操做通常是遵循寫操做的。由二八定律來假設,寫操做大約佔20%,讀操做大約佔80%。這個時候若是把做用的讀操做和寫操做所有都請求到同一個數據庫,數據庫的壓力是比較大的。因此咱們能夠對數據庫作讀寫分離架構:微服務

主從同步

能夠根據本身的需求,配置多個從數據庫。

這個時候須要作主從同步,即把主庫的數據,同步到從庫上。在MySQL中,這個過程叫作「複製」。

MySQL複製原理

MySQL是基於二進制日誌bin log來進行復制的。Master服務器將數據的改變寫進二進制日誌中,主庫發送更新事件到從庫。

收到事件後,Slave會開啓一個I/O線程。I/O線程會在Master上開啓一個普通鏈接,而後Master開啓一個dump線程來配合作複製操做。

I/O線程讀取Master上面的bin log到Slave結點的中繼日誌relay log裏。而後Slave會開啓一個SQL線程,用於從中繼日誌讀取事件,並重放其中的事件而更新slave的數據,使其與master中的數據一致。只要該線程與I/O線程保持一致,中繼日誌一般會位於操做系統的緩存中,因此中繼日誌的開銷很小。

MySQL支持如下三種複製方式:

  • 基於語句的複製:優勢是實現簡單,日誌數據量也比較小,佔用帶寬少。缺點是可能有些數據可能會出問題,好比主從的時區不一樣、存儲過程和觸發器不一樣等。
  • 基於行的複製:優勢是對任何數據都能正常工做,缺點是二進制日誌可能會比較大。
  • 混合模式:默認採用基於語句的複製,若是發現基於語句沒法精確複製時,就採用基於行的複製。

MySQL的複製支持不少種模式,包括「主-主複製」等,但實際使用時仍是建議使用一主多從的模式,雙主架構可能會有ID衝突或事務等問題。

須要注意的是從數據庫仍然會有寫壓力的。主從架構只能分擔讀壓力,但不能分擔寫壓力。由於全部的寫操做最終都要同步到全部的從節點,因此若是寫操做過多,不只會影響主庫的性能,也會影響從庫的性能。

那若是主庫不能支撐全部的寫操做了怎麼辦呢?這個時候就要使用分庫分表了。

分庫分表

前面提到若是寫操做成爲性能瓶頸,那讀寫分離並不能很好的解決這個問題,能夠用分庫分表來作。分庫分表在必定程度上能夠解決寫操做和讀操做的性能瓶頸,但也會爲寫操做和讀操做帶來更多的複雜性。

數據庫分佈式核心內容無非就是數據切分(Sharding),以及切分後對數據的定位、整合。數據切分就是將數據分散存儲到多個數據庫中,使得單一數據庫中的數據量變小,經過擴充主機的數量緩解單一數據庫的性能問題,從而達到提高數據庫操做性能的目的。

若是一個表過大,有兩種切分方式——垂直切分和水平切分。垂直切分即把一些列切分出去,在微服務場景下,其實咱們的數據庫通常只爲一個微服務服務,至關於已經切分到比較契合業務了。因此基本上不用考慮垂直切分。

而水平切分有兩種方式:

  • 庫內分表:即把一個表切分紅多個表,但仍然放在同一個數據庫。好比按照id範圍或者按照時間範圍來切分。這種方式適用於熱點數據的場景,好比「朋友圈」,通常近期的新數據訪問量比較大,而較久的數據訪問量比較小。這樣就能夠基於時間範圍來切分。
  • 分庫分表:分庫分表即把一個表切分到不一樣的數據庫,這些數據庫通常是在不一樣的節點上。也就造成了咱們常說的「分佈式數據庫」。

下面介紹兩種分庫分表的切分方式。第一種是像庫內分表同樣,按照id的取值範圍或者時間區間來切分。一樣適用於熱點數據,而且自然便於水平擴展,後期若是想對整個分片集羣擴容時,只須要添加節點便可,無需對其餘分片的數據進行遷移。

另外一種是根據id來取模,通常是使用一致性hash算法來作,能夠較好地支持水平擴展。這種方式數據分配比較均勻,不容易出現熱點和併發訪問的瓶頸,適用於大多數業務場景。

分庫分表能夠跟主從配合,每一個分片均可以使用主歷來緩解讀壓力。

分庫分錶帶來的問題

分庫分表之後,會下面的一些問題:

事務

當更新內容同時分佈在不一樣庫中,不可避免會帶來跨庫事務問題。可以使用主流的分佈式事務解決方案,好比XA協議、兩階段提交等。分佈式事務能最大限度保證了數據庫操做的原子性。但在提交事務時須要協調多個節點,推後了提交事務的時間點,延長了事務的執行時間。致使事務在訪問共享資源時發生衝突或死鎖的機率增高。隨着數據庫節點的增多,這種趨勢會愈來愈嚴重,從而成爲系統在數據庫層面上水平擴展的枷鎖。

對於強一致性要求不那麼高的數據,能夠只須要保證最終一致性便可。

join

分庫分表後,join操做將變得很是麻煩。下面列舉一些比較常見的解決方案:

  • 全局表:也就是系統中全部模塊均可能依賴的一些表,爲了不跨庫join查詢,每一個數據庫都保存一份,這些數據一般不多會進行修改,因此也不擔憂一致性的問題。

  • 反範式設計:用空間換時間,數據冗餘。能夠避免join查詢,但可能會帶來數據同步的問題。好比賣家修改了名字,須要同步到訂單表裏嗎?這個根據實際的業務場景來考慮是否使用反範式設計。

  • 數據組裝:在應用層面來組裝數據,第一次查詢結果找到關聯的id,在應用層用這個id去二次請求獲得關聯的數據,而後在應用層拼裝數據。

  • ER分片:簡單來講,就是把存在關聯關係的記錄放在同一個節點上,這樣就能局部join,避免了跨分配join。這個時候關聯表在和主表1:1或者n:1的狀況下,一般按照主表的id主鍵進行切分。

分頁、排序、和函數

這種狀況下,須要先在不一樣的分片節點中將數據進行排序並返回,而後將不一樣分片返回的結果集進行彙總和再次排序,最終返回給用戶。

這個事情在應用層來作比較麻煩,而MySQL數據庫也不能很好的支持,因此比較適合使用插件或者數據庫中間件來作這個事情。

主鍵衝突

在分庫分表環境中,因爲表中數據同時存在不一樣數據庫中,主鍵值平時使用的自增加將無用武之地,某個分區數據庫自生成的ID沒法保證全局惟一。所以須要單獨設計全局主鍵,以免跨庫主鍵衝突問題。有一些常見的主鍵生成策略:

  • UUID:UUID實現簡單,但缺點也比較明顯,因爲UUID很是長,會佔用大量的存儲空間;另外,做爲主鍵創建索引和基於索引進行查詢時都會存在性能問題,在InnoDB下,UUID的無序性會引發數據位置頻繁變更,致使分頁。
  • 分佈式ID生成方案:市面上有不少分佈式ID的生成方案,好比Twitter的snowflake算法、美團的Leaf、百度的UidGenerator、Redis生成ID等方式。

解決方案

上面介紹了一個關係型數據庫的架構演變。對於MySQL來講,原生對分佈式的支持並非很好,因此咱們可能須要使用一些數據庫中間件來輔助。好比MyCat、Atlas等。筆者我的對Sharding-JDBC(現已升級爲Sharding-Sphere項目)比較感興趣,後續可能會學習研究一下這個工具。

除此以外,還有一些原生即支持分佈式的數據庫,好比TiDB。TiDB兼容MySQL的語法,能夠很方便地從現有的MySQL數據庫遷移到TiDB。

另外主流的雲平臺也提供了數據庫擴展方面的支持。好比AWS、阿里雲等雲數據庫RDS,就能夠很方便地配置主從、分庫分表等。

認真寫文章,用心作分享。

我的網站:yasinshaw.com

公衆號:xy的技術圈

相關文章
相關標籤/搜索