分佈式環境中分庫分表、讀寫分離相關問題總結

1、爲何要用分庫分表

當不使用分庫分表的狀況下,系統的性能瓶頸主要體如今:mysql

  • 當面臨高併發場景的時候,爲了不Mysql崩潰(MySql性能通常的服務器建議2000/s讀寫併發如下),只能使用消息隊列來削峯。
  • 受制於單機限制。數據庫磁盤容量吃緊。
  • 數據庫單表數據量太大,sql越跑越慢

而分庫分表正是爲了解決這些問題,提升數據庫讀寫併發量,磁盤容量大大提升,單表數據量下降,提升查詢效率。redis

2、垂直拆分和水平拆分

以表的維度來講:

  • 垂直拆分 指根據表的字段進行拆分,其實很常見,有時候在數據庫設計的時候就完成了,屬於數據庫設計範式,如訂單表、訂單支付表、商品表。
  • 水平拆分 表結構同樣,數據進行拆分。如本來的t_order表變爲t_order_0,t_order_1,t_order_3

以庫的維度來講:

  • 垂直拆分 指把本來的大庫,按業務不一樣拆到不一樣的庫(微服務通常都是這麼設計的,即專庫專用)
  • 水平拆分 一個服務對應多個庫,每一個庫有相同的業務表,如庫1有t_order表,庫2也有t_order表。業務系統經過數據庫中間件或中間層操做t_order表,分庫操做對於業務代碼透明。

因此,咱們日常說的分庫分表,通常都是指的水平拆分算法

3、分庫分表工具

主要關注MyCat和sharding-jdbc。sql

二者對比:數據庫

  • MyCat:基於中間件的形式,提供讀寫分離、分庫、分表功能。只不過很久都沒更新了,我使用的是1.6版本,並不支持一個邏輯表的分庫、分表同時存在。
  • sharding-jdbc:基於jar包中間層的形式,提供讀寫分離、分庫、分表功能。社區較活躍。支持功能強大。

4、分庫分表的兩種策略

  • hash分法,按一個鍵進行hash取模,而後分發到某張表或庫。優勢是能夠平攤每張表的壓力,缺點是擴容時會存在數據遷移問題。
  • range分法,按範圍或時間分發,好比按某個鍵的值區間、或建立時間進行分發,優勢是能夠很方便的進行擴容,缺點是會形成數據熱點問題。從分表上說還好,若是是分庫,將致使某一個庫節點壓力過大,節點間負載不均。

這裏,我認爲最好的分法是hash分庫、range分表。由於對庫來講重要得是負載要均衡,對錶來講重要的是能夠動態擴容。緩存

5、分庫分表數據遷移方案

停機分庫分表

方案很簡單,就是停機維護時,用後臺臨時程序基於數據庫中間件將老庫數據直接分發到須要遷移的數據庫安全

此方案的主要缺點是必定會出現幾個小時的停機,若是沒搞定要回滾,次日繼續搞,開發人員心是慌得。服務器

不停機雙寫方案

主要步驟:網絡

  • 修改系統中的全部寫庫的代碼,同時寫老庫和數據庫中間件(包括新增、更新、刪除操做)
  • 而後後臺用工具將老庫以前的老庫數據遷移到新的數據庫中間件,注意比對修改時間,若id同樣,按修改時間決定是否覆蓋。
  • 使用後臺工具比對一次數據,看是否徹底同樣,不同,則後臺再用工具進行一次遷移。(避免有些數據由於網絡問題沒有遷移成功,或業務上的bug致使),這個過程一般須要好幾天。
  • 以此循環幾回,數據徹底同樣時,就切換爲只讀寫新庫

該方案解決了停機形成的服務不可用。多線程

6、分庫分表下的動態擴容問題

在分庫分表的狀況下,如何在已經分庫分表的基礎上進一步分庫分表提升系統效率,是一個麻煩的問題。特別是基於hash分片的服務器,再次分庫分表,通常只能對服務器進行停機,而後將全部數據又基於新的規則插入到不一樣的庫與表。爲了不這種問題,能夠在第一次分庫分表的時候就將庫切分的較細,避免二次擴容。好比:

  • 最開始就將庫分爲32個庫,最開始業務量沒那麼大,能夠將多個庫放在同一臺機器上,之後按照2-4-8-16-32來進行擴容,如最開始2臺機器可以知足數據庫讀寫併發,此時一臺服務器上有16個庫,後來不夠了,就擴容爲4臺機器,每臺機器8個庫。。。依次類推,這樣作的好處是隻須要遷移須要遷移的庫,而且是按照整個庫進行遷移,不須要從新進行分發,同時分庫分表的分片機制也不用修改,只須要修改其數據源就好了。
  • 對於分表來講,也能夠分的多一些,推薦分爲32張表。
  • 以上,分爲了32個庫32張表,總共數據量能夠達到32 * 32 = 1024張表,按每張表500萬正常的容量來算,能夠容納約50億數據,足以知足大部分過擴容需求。
  • 另外這種方案推薦擴容方案爲2-4-8-16-32倍數進行擴容,深層緣由是32是這些數的公倍數,按照約數進行擴容更容易讓每一個機器負載的庫都同樣。
  • 須要注意的是若是按照同一分片鍵進行一樣的分片策略分庫分表,會致使數據只會達到某庫的某表好比1庫的1表,2庫的2表,(由於庫和表的數量也是同樣)因此分庫咱們能夠按32取模策略,分表的話咱們能夠按整除以後的餘數再對32取模進行分表。

7、全局id的生成策略

幾種生成id的方式對比:

  • 經過數據庫自增

往公用的一張表(這張表是自增主鍵)插入一條數據,獲取id的返回值,用這個id再去插入中間件當中去。oracle能夠經過自增序列。

缺點:不適合併發高的場景,畢竟不論是自增序列仍是採起自增鍵的方式來生成,會併發競爭寫鎖,效率過低。

  • UUID

缺點:uuid太長了,不規則

  • 時間戳

通常聯合其餘業務字段拼接做爲一個Id,如時間戳+用戶id+業務含義編碼

缺點:併發高容易重複

  • 雪花算法

原理:前面1位爲定值0+41位爲時間戳+5位機房id+5位爲機器id+12位爲序號,惟一須要保證同步的地方是生成一個序號,鎖粒度較低。另外這個算法可用於分佈式環境中。最大的優勢是不須要依賴任何中間件,核心原理是用5位機房id,5位機器id標誌了惟一一臺機器,因此不須要分佈式鎖去保證不一樣機器生成id的同步性,只須要在當前機器保證生成的序號不同就好了。

  • redis中間件生成

原理:利用redis單線程工做線程屬性去維護一個自增變量。

8、讀寫分離

爲何要讀寫分離

  • 理論上來講讀寫請求不要超過2000/s,若是加了緩存以後,到數據庫請求仍是超過2000以上考慮讀寫分離
  • 使得讀請求能夠在不一樣機器併發,用了讀寫分離以後能夠經過動態擴展讀服務器增長讀效率,這與redis中的主從架構讀寫分離、copyOnWrite機制的併發容器、以及數據庫MVCC機制有點相識,都是經過讀請求的數據備份增長讀寫併發效率。
  • 適用於業務場景中,讀請求大於寫請求的狀況,讀寫分離使得系統可以更多的容納讀請求併發。

讀寫分離的實現方式

通常來講是基於mysql自帶的主從複製功能。mysql主從複製的流程圖以下:

總結mysql的主從複製過程大致是主庫有一個進程專門是將將記錄的Binlog日誌發送到從庫,從庫有一個io線程(5.6.x以後IO線程能夠多線程寫入relay日誌)將收到的數據寫入relay日誌當中,另外還有一個SQL進程專門讀取relay日誌,根據relay日誌重作命令(5.7版本以後,從能夠並行讀取relay log重放命令(按庫並行,每一個庫一個線程))。

主從同步的三種模式:

  • 異步模式(mysql async-mode)

異步模式以下圖所示,這種模式下,主節點不會主動push bin log到從節點,這樣有可能致使failover的狀況下,也許從節點沒有即時地將最新的bin log同步到本地。

  • 半同步模式(mysql semi-sync)

這種模式下主節點只須要接收到其中一臺從節點的返回信息,就會commit;不然須要等待直到超時時間而後切換成異步模式再提交;這樣作的目的可使主從數據庫的數據延遲縮小,能夠提升數據安全性,確保了事務提交後,binlog至少傳輸到了一個從節點上,不能保證從節點將此事務更新到db中。性能上會有必定的下降,響應時間會變長。以下圖所示:

  • 同步模式(mysql semi-sync)

全同步模式是指主節點和從節點所有執行了commit並確認纔會向客戶端返回成功。

讀寫分離場景下主從延遲可能致使的問題

在代碼中插入以後,又查詢這樣的操做是不可靠,可能致使插入以後,查出來的時候尚未同步到從庫,因此查出來爲null。如何應對這種狀況了?其實並不能從根本上解決這種狀況的方案。只能必定程度經過下降主從延遲來儘可能避免。

下降主從延遲的方法有:

  • 拆主庫,下降主庫併發,下降主庫併發,此時主從延遲能夠忽略不計,但並不能保證必定不會出現上述狀況。
  • 打開並行複製-但這個效果通常不大,由於寫入數據可能只針對某個庫併發高,而mysql的並行粒度並不小,是以庫爲粒度的。

但這並不能根本性解決這個問題,其實面對這種狀況最好的處理方式是:

  • 重寫代碼,插入以後不要更新
  • 若是確實是存在先插入,立馬就能查詢到,而後立馬執行一些操做,那麼能夠對這個查詢設置直連主庫(經過中間件能夠辦到)

參考:深度探索MySQL主從複製原理

相關文章
相關標籤/搜索