MySQL學習之如何快速擴展數量

不少時候因爲MySQL技術致使的災難性的後果,同時大量用於讀的MySQL從屬服務器,產生了大量使人惱火的Bug,特別是緩存。爲了解決這個問題咱們就須要從新架構整個存儲模型,今天和你們分享的就是如何快速擴展MySQL數據容量的相關操做,但願能夠幫助你們更好的學習MySQL ,一塊兒來看看吧。數據庫

業務需求緩存

·系統整體要很是穩定,便於操做,便於拓展。咱們想讓數據庫能從開始小存儲量,能隨着業務發展而拓展;服務器

·Pin友生成的內容必須能永久訪問;架構

·(支持)請求的N個Pin在板塊中以肯定的順序(像按照建立時間倒序,或是按照用戶特定的排序)(顯示)。對於喜歡的Pin友,發Pin的Pin友列表等,也必須以特定的順序;app

·爲了簡單,更新通常而言要保證最好的性能,爲了獲取最終一致性,須要額外的東西,如分佈式事物日誌。這是一個有趣(但不太容易)的事情!負載均衡

設計原理與筆記分佈式

  因爲咱們想要的這些數據是橫跨多個數據庫的,咱們不能使用數據庫的join,外鍵或者收集全部數據的索引,不過他們能夠被用於不能橫跨數據庫的子查詢。性能

  咱們也須要支持負載均衡咱們的數據。咱們厭惡來回移動數據,尤爲是逐項移動,由於容易出錯,也容易讓系統變得沒必要要的複雜。若是咱們必須移動數據,最好移動一整個虛擬節點到物理節點。學習

  爲了實現快速成型,咱們須要一個簡單可用的解決方案,而且在咱們的分佈式數據平臺上,節點要很是穩定。spa

  全部的數據都須要被備份到從節點進行高可用,併爲 MapReduce 轉存到 S3。 在生產中,咱們只與主節點交互。在生產中,你不能在從節點上讀/寫。從節點是滯後的,它會引起奇怪的bug。若是你共享數據,通常來講在生產中與從節點交互是沒有優點的。

  最終,咱們須要一個好的方式來生成一個統一且惟一的ID(UUID)分配給全部咱們的對象。

咱們如何切分數據

不管咱們如何構建系統,都須要知足咱們的業務要求並保證系統穩定,具備高性能,易於維護。換言之,咱們須要系統不糟糕 ,所以咱們選擇成熟的技術-MySQL做爲咱們構建系統的基礎。咱們有意避免使用具備自動擴展功能的新技術,例如MongoDB,Cassandra 和Membase等,由於他們還不夠成熟(而且他們會以沒法預知的方式崩潰)。

悄悄話:我依舊建議初創公司避免使用花哨的新事物——嘗試使用徹底可以正常運行的MySQL。相信我,我有不少錯誤的實踐(創傷)來證實這一點。

MySQL是成熟的,穩定的而且可以正常運行的。不只咱們使用MySQL,還有大量公司在普遍的使用。MySQL支持咱們所須要的數據排序、範圍查詢功能而且具有行級事務等功能。它還有不少的特性,可是咱們不須要或使用這些。儘管MySQL很是適合咱們,但MySQL是單一解決方案,所以須要咱們對數據進行分片。下面是咱們的解決方案:

  最開始時咱們有8個EC2 服務,每一個服務對應一個MySQL實例:

 

爲了應對MySQL服務崩潰,每個MySQL服務都是應用主複製模式構建,即每個MySQL服務都將數據備份到一個備份主機上。咱們的應用只讀寫主服務。我建議你也這樣作。它簡化了一切操做而且避免了複製延遲問題。

  每一個MySQL數據庫能夠有多個庫:

 

 

那這個Pin對象保存在3429分片上,它的類型是1(如‘Pin’),及它在pin表的所在行是7075733。 舉個例子,讓咱們假設這個分片是在MySQL012A.上,咱們可能經過下面來訪問到這個對象:

 

  這些數據有兩個類型:objects(對象)和mappings但每一個庫的命名惟一,如db00000, db00001, …,dbNNNNN。 每一個庫都是咱們數據的一個分片。咱們的決計決策是一塊數據只在一個分片中,而不會在其它片中。然而你能夠經過增長容量,以便將片移到其它機器上(後續將討論這個)。

  咱們維護了一個配置表,以記錄這些數據分片在什麼機器上:

 

  這個版本僅在咱們須要移動分片或是替換某臺機器時修改。若是主機掛掉了,咱們能夠將某臺從屬服務器升級爲主機,而後再加臺新的從屬服務器。其配置保存在Zookeeper,更新時,配置發送到其它維護分片的MySQL服務器上。

  每一個分片包含相同的表:pins, boards, users_has_pins, users_likes_pins, pin_liked_by_user等等。後面我會再詳細講解。

那怎樣將數據部署到這些分片上呢?

  咱們建立了一個64位的ID,包含了分片ID,包含數據的類型,及數據在那臺服務器上(本地ID)。分片ID是10位,本地ID是36位。精明的專家對此提示只能加到62位。之前編譯器和芯片設計的經驗已經告誡我,預留位的長度是很重要的,因此咱們有兩個(設置爲0)。

ID = (shard ID

  給定這個Pin: https://www.pinterest.com/pin/241294492511762325/,讓咱們開發剖析這個Pin ID 241294492511762325:

(映射)。objects包含了具體的數據,例如Pin的數據。

對象表

  對象表存儲瞭如Pin數據、用戶數據、看板(boards)數據以及評論數據。每一個對象都包含一個ID列(本地的ID,自增主鍵),和一個blob字段包含整個對象的JSON數據。

 

例如一個Pin數據:

 

  在建立新Pin的時候,須要將全部的對象數據收集起來並生成一個JSON blob。而後,須要肯定一個分片(shard)ID(咱們偏向於選擇與它關聯(inserted)的看板的分片ID相同的ID,可是沒有必要)。Pin的類型碼爲1。連上數據庫並在Pin對象表中插入JSON數據。MySQL會返回一個本地自增ID。一旦生成了分片ID、類型碼和本地ID,就能夠生成完整的64位ID!

  編輯Pin的時候能夠在一個MySQL事務中以讀-修改-寫的方式修改JSON數據:

 

  刪除Pin的時候能夠刪除MySQL中的一行。可是更好的方式是在JSON數據中添加一個‘active’字段,設置值爲false,而且在客戶端上過濾到這些信息。

映射表

  映射表用於創建對象之間的鏈接,如看板(board)和Pin之間的鏈接。MySQL中創建的映射表包含3列,一個64位的來源對象ID‘from’、一個目的對象ID ‘to’ 以及一個序列ID ‘sequence’ 。在(from, to, sequence)上創建聯合索引,而且每條記錄以 ‘from’ 字段的分片ID分片。

 

  映射表中保存單向映射關係,如看板(board)到Pin的映射表‘board_has_pins’。若是須要反向的映射關係,須要創建一個獨立的反向映射表‘pin_owned_by_board’。‘sequence’ 主要用於排序(分片之間是不能夠比較的,由於本地ID可能相互是衝突的)。當插入一個新的Pin到看板的時候,一般能夠用unix本地的時間戳做爲序列號(sequence ID = unix timestamp)。序列號能夠是任意的數字,可是unix時間戳是一種使新增的列處於更大的數值的便捷方式,由於時間是單調遞增的。查詢映射表的方式以下:

 

  這樣能夠返回至多50個pin_id,再經過它們獲取Pin對象。

  以上的方法即是一次應用層的鏈接(board_id -> pin_ids -> pin objects)。應用層鏈接的一個傑出的特性是能夠在獨立於對象數據的狀況下緩存映射數據。在memcache集羣中緩存pin_id到pin對象的映射數據的同時還在Redis集羣中緩存board_id到pin_ids的映射數據。這樣就能夠實現與緩存數據更匹配的緩存技術。

擴容

  咱們系統有三種主要的擴容方式。最簡單的就是升級機器(容量更大、速度更快的硬盤,更多內存,任何成爲瓶頸的部分)。

第二種方式是開啓新的分片範圍。雖然咱們的分片ID是16位的(最多64k個分片),可是一開始咱們只建立了4096個分片。新建的對象只能存儲在開頭的4k個分片。某天咱們決定添加新的MySQL服務器用來存儲4096到8191的分片,而且開始填充數據。

最後一種方式是將一些分片移動到新的機器上。例如須要對MySQL001A(存儲着0到511分片)擴容是,先新建一對主從節點(譯者注:此處原文爲「a new master-master pair」,可能爲筆誤),命名爲最大的編號(好比是MySQL009A和MySQL009B),而後開始從MySQL001A複製數據。

 

當複製完成時,改變配置,使MySQL001A只存儲0到255分片,MySQL009A只存儲256到511分片。如今每一個服務器只處理原先通常的數據量。

 

 

一些優勢

  若是你曾構建過生成UUIDs的系統,你會發現這個系統能夠輕鬆地實現這個需求!當你建立一個新的對象並插入到數據庫的時候,會獲得一個本地ID。這個本地ID和分片ID、類型ID組合即可獲得一個UUID。

  若是你曾用ALTER命令爲MySQL數據表添加過字段,你確定知道這種操做十分緩慢並且十分痛苦。本文的方法不須要任何級別的MySQL Alter操做。在Pinterest的過去三年中,咱們可能完成了一次Alter操做。給對象添加新的字段只須要讓那些服務知道JSON模式中添加了幾個新的字段。當反序列化一個JSON對象的時候若是沒有某些字段,此時即可以返回預設的默認值。若是須要新的映射表,只須要建立一個,而後在須要的時候填充數據就能夠。完成這些事情就能夠發佈新產品了。

取模分片(Mod Shard)

  取模分片和 Mod Squad(譯者注:一部電影)是同樣的,惟一的區別是它們徹底不一樣。

  有一些對象並非經過ID查詢的。好比一個Pin友用Facebook帳號登陸的時候,咱們須要將Facebook的ID映射到Pinterest的ID。Facebook的ID在咱們看來就是比特位,因此咱們將他們存儲到一個獨立的分片系統中,並命名爲取模分片。包括其餘諸如IP地址、用戶名、郵箱地址等。

  取模分片正如和上文中的分片系統相似,除了它們能夠以任何數據查詢。對查詢輸入取hash而後以系統中總分片數取模。結果就是數據將被存儲或已經存儲的位置,以下:

shard = md5(「1.2.3.4") % 4096

  此例中shard 值會被賦爲1524。相似ID分片,須要維護一個配置文件,以下:

 

則,查詢一個IP地址1.2.3.4的方式,以下:

 

  可是會所以而失去一些ID分片優點,好比空間分佈(spacial locality)。一開始就必須建立好全部分片,而且手動建立分片ID(並不能自動生成)。系統中的全部對象都最好有一個不變的ID。只有這樣當一個用戶修改他的用戶名的時候纔不用去更新那些關於這個用戶名的引用。

最後的一些想法

  至今爲止,這個系統在Pinterest已經在線上使用了3.5年了,彷佛還會一直用下去。實施這樣一套系統相對比較直截了當,可是保持運行並將所有數據從舊機器遷移過來仍是至關困難的。若是你在初創公司而且承受着數據增加的痛苦,而後你構建了新的分片機制,而且考慮構建一套集羣用於在後臺執行腳本把舊數據庫的數據遷移到新的分片機器(提示:能夠用pyres)。能夠確定的是,不管你作怎樣的努力,數據丟失都是不可避免的(我敢確定它是系統中的搗蛋鬼),因此要一遍一遍重複的遷移數據直到寫入新系統的遷移數據爲0或者數量小到必定程度。

這套系統是最大努力的交付它沒有保證原子性、隔離性和一致性。聽起來很糟糕,可是不用着急,由於這些保證可能並非必須的。若是須要的話,能夠在其餘的過程或系統中實現這些特性,可是你能直接得到的好處是,這個系統剛恰好能夠正常運行。經過簡潔能夠獲得好的可靠性,而且運行起來至關快速。若是你仍是擔憂原子性、隔離性和一致性,請聯繫我。我能夠幫助你解決這些問題。

  關於故障恢復怎麼樣呢?咱們構建了一個維護MySQL分片的服務。咱們把分片配置信息存儲到了ZooKeeper。若是一臺master服務器宕機了,能夠執行一些腳本,將slave升級爲master,而後再開啓一臺替補服務器(包括數據同步)。直到如今咱們也沒有使用自動的故障恢復。

文章來源:開源中國

相關文章
相關標籤/搜索