MySQL云數據庫服務的架構探索

作者: 曹偉  來源: CSDN  發布時間: 2013-09-03 22:31  閱讀: 7114 次  推薦: 8   原文鏈接   [收藏]  
摘要:雖然近兩年來NoSQL發展迅猛,但MySQL憑借其成熟的中間件和運維工具及良性的生態圈,在互聯網企業中的應用仍然非常廣泛,并占有主導地位。作者曹偉將深入探討依托MySQL平臺設計和實現的UMP系統的架構探索經驗。

  MySQL作為一種低成本、高性能、可靠性良好而且開源的數據庫產品,在互聯網企業中應用非常廣泛。例如,淘寶網就有數千臺MySQL服務器。雖然近兩年來NoSQL的發展很快,新產品層出不窮,但在業務中應用NoSQL對開發者來說要求比較高,而MySQL擁有成熟的中間件、運維工具, 已經形成一個良性的生態圈。因此,在現階段的應用中仍然以MySQL為主,NoSQL為輔。

  在過去一年里,我們在MySQL托管平臺方向做了大量工作,設計和實現了一套UMP(Unified MySQL Platform)系統,提供低成本和高性能的MySQL云數據庫服務。開發者從平臺上申請MySQL實例資源,通過平臺提供的單一入口來訪問數據。UMP系統內部維護和管理資源池,以透明的形式提供主從熱備、數據備份、遷移、容災、讀寫分離和分庫分表等一系列服務。平臺通過在一臺物理機上運行多個MySQL實例的方式來降低成本,并且實現了資源隔離,按需分配和限制CPU、內存和I/O資源,同時在不影響提供數據服務的前提下,支持根據用戶業務的發展來動態擴容和縮容。

  架構的演變

  UMP系統第一版基于MySQL Proxy 0.8版修復了若干Bug,并對Proxy插件中管理用戶連接和數據庫連接的狀態機流程進行了修改;編寫了Lua腳本實現到中心數據庫獲取用戶認證信息和后臺數據庫地址,來對用戶進行驗證;建立了到后臺數據庫的連接和轉發數據包等邏輯(如圖1所示)。

圖1 UMP系統的第一版(當時稱作RDS系統)采用MySQL Proxy

  在開發和部署第一版的過程中,我們逐漸認識到幾個問題。

  首先,MySQL Proxy 0.8版對多線程的支持比較簡單粗暴,多個工作線程共享同一個消息隊列,同時監聽著同一個socketpair通道。當有新事件進入消息隊列后,socketpair會被寫入一個字節,所有休眠中的線程都會被喚醒,去競爭一個互斥鎖從消息隊列中取任務。這種實現有幾個問題:一是造成“驚群”現 象,多個線程被喚醒但只有一個線程需要去完成任務;二是任務的CPU親緣性比較差,在同一個狀態機上觸發的事件會在多個處理器上來回切換執行。此外,MySQL Proxy中還使用了全局Lua鎖,同時僅允許一個工作線程執行Lua腳本(計劃在0.9版本中改進)。因此,在多線程模式下,MySQL Proxy的性能遠不能同CPU核數保持線性增長,甚至在16核上的性能還不如4核。而使用單進程模式時,一臺物理機上需要部署多個進程才能有效利用機器的處理能力,但給部署、監控和服務的升級帶來麻煩。

  其次,由于MySQL Proxy的框架在功能上不容易擴展,所以實現用戶的連接數限制、QPS限制及主從切換、讀寫分離、分庫分表等功能比較困難。

  最后,MySQL Proxy的社區近些年并不活躍,且C語言對開發者功底的要求比較高,很難要求團隊所有成員協同開發出兼顧優雅和正確性的代碼。

  因此,我們決定用Erlang語言重新編寫Proxy服務器,替換了原有的MySQL Proxy模塊。目前,整個項目擁有5萬行Erlang源碼,3萬行C/C++源碼,2萬行其他語言源碼。

  為什么選擇Erlang語言

  Erlang是一個結構化的、動態的、函數式的編程語言。常見的一種說法是Erlang是面向并發的(Concurrent-Oriented),這主要指Erlang在語言中定義了Erlang進程的概念和行為(本文中提到的“Erlang進程”都是指Erlang語言中定義的進程,以區分于大家熟悉的操作系統進程)。與操作系統的進程/線程相比,Erlang進程同樣是并發執行的單位,但特別輕量級,它是在Erlang虛擬機內管理和調度的“綠進程”, 即用戶態進程(如圖2所示)。舉個例子,在關閉了HiPE和SMP支持的Erlang虛擬機中,一個新創建的進程占用的內存僅為309個字 (Word,64位服務器上為8個字節)。其中233個字為堆空間(包含棧),創建和結束一個進程約耗時1~3微秒,而一個Erlang虛擬機中可以同時支持幾十萬甚至更多個進程。

圖2 Erlang的輕量級進程

  說到Erlang語言,就必須提及OTP(Open Telecom Platform,開放電信平臺)。OTP是用于開發分布式的、高容錯性的Erlang應用程序的框架與平臺。例如,一個Erlang節點連接并注冊到Erlang集群上,發現集群中的其他節點,并與它們進行RPC通信,這些都在OTP里的Kernel服務中實現。OTP和Erlang語言關系如此緊密,以至于兩者通常合稱為Erlang/OTP,因此從嚴格的意義上來講,應該說我們選擇了Erlang/OTP來構造UMP系統。Erlang/OTP很好地抽象了開發一個分布式的、高容錯性的應用程序所需的要素,包括網絡編程框架、序列化和反序列化、容錯、熱部署。

  為了支持并發,服務器端多采用多進程/多線程模型,即每個進程/線程處理一個客戶端連接。但受限于操作系統資源,每臺服務器可以處理的并發連接數并不高,且由于進程/線程上下文切換開銷,系統性能會受到影響。而開發高并發、高性能服務器一般采用事件驅動的狀態機模型,底層采用非阻塞I/O(Linux中的epoll,BSD系統中的kqueue,Java中的nio)或者異步I/O,或者采用異步的事件通知的I/O框架,例如C/C++下的ACE、boost::asio、 libevent,Java下的MINA等。在業務層則使用狀態機來表示每個客戶端連接,通過I/O事件、超時事件驅動狀態機進行跳轉,每個進程/線程可處理成千上萬個客戶端連接。與多進程/多線程模型相比,雖然事件驅動的狀態機模型并發量更大、性能更好,但把業務邏輯表達成狀態機是一件困難的事情。相比之下,多進程/多線程模型中的業務邏輯可以實現為順序執行的代碼,開發起來要簡單得多。

  Erlang/OTP中的網絡編程模型則結合了兩者的優點,每個Erlang進程處理一個客戶端連接,業務邏輯是順序執行的。Erlang進程是極輕量級的,可以認為每個Erlang進程是一個狀態機,堆和棧上的數據是這個狀態機的狀態。Erlang進程收到數據包或者其他進程發來的消息后執行處理例程,相當于狀態機的跳轉,因此也具有高并發和高性能的優勢。

  Erlang/OTP定義了“External Term Format”協議將Erlang數據結構與二進制字符串相互轉化,并用C實現在Erlang虛擬機中,在進行跨節點通信時遵從這個協議。因此,開發者無須額外考慮序列化和反序列化問題。

  在容錯方面,Erlang進程的數據空間是相互隔離的,沒有共享內存,因此一個Erlang進程崩潰不會影響其他Erlang進程運行,更不會造成Erlang虛擬機崩潰。OTP提供了監督樹機制和heart模塊,前者在監控到Erlang進程崩潰時進行故障恢復,后者在發現Erlang虛擬機失去響應時重啟程序。

  Erlang/OTP提供熱部署方式,可以避免服務升級時造成不可用時間。此外,OTP還提供了一些在系統運行時觀察系統狀態的工具。例如lcnt工具,可以統計虛擬機內部的鎖使用次數和沖突次數,指導系統的優化。

  當前系統架構

  在設計UMP系統時,我們遵循了以下幾條原則:

  • 系統對外保持單一入口,對內維護單一資源池。
  • 保證服務的高可用性,消除單點故障。
  • 保證系統是彈性可伸縮的,可以動態地增加、刪減計算與存儲節點。
  • 保證分配給用戶的資源也是彈性可伸縮的,資源之間相互隔離。

  UMP系統中的角色包括:Controller服務器、Proxy服務器、Agent服務器、API/Web服務器、日志分析服務器和信息統計服務器。圖3是當前UMP系統的架構圖。UMP系統依賴Mnesia、LVS、RabbitMQ、ZooKeeper等開源組件。

圖3 當前UMP系統架構圖

  Mnesia是OTP提供的分布式數據庫,與MySQL NDB出自同門,都是20世紀90年代中期Ericsson為電信業務研發的數據產品。Mnesia支持事務、支持透明的數據分片,利用兩階段鎖實現分布式事務,可以線性擴展到至少50個節點。

  從CAP理論的角度來說,Mnesia更傾向于犧牲可用性來換取強一致性,屬于CP陣營。但它也提供了臟讀、臟寫操作,可以繞過事務管理去操作數據,這時不保證一致性,有點類似于AP的系統。在工程實踐中,我們用事務去修改關鍵數據(例如路由表),而用臟寫接口去寫非關鍵數據(例如用戶的狀態信息),讀取數據用臟讀接口。

  Controller服務器向UMP集群提供各種管理服務,實現元數據存儲、集群成員管理、MySQL實例管理、故障恢復、 備份、遷移和擴容等功能。Controller服務器上運行了一組Mnesia分布式數據庫服務,系統的元數據如集群成員、用戶的配置和狀態信息,以及用戶名到后端MySQL實例地址的映射關系(路由表)等都存儲在Mnesia里,其他服務器組件通過發送請求到Controller服務器獲取用戶數據。

  為了達到高可用性,系統中會部署多臺Controller服務器,它們通過ZooKeeper提供的分布式鎖算法選舉出一個leader,這個leader負責調度和監控各種系統任務,例如創建和刪除數據庫實例、備份和遷移等。這些系統任務可以分成多個步驟,而且會涉及系統中的多個組件,例如主庫、從庫和 Proxy服務器等,還需要提供失敗時回滾的方法。因此,我們采用類似工作流的方式來實現。每個系統任務都分成多個階段的Erlang進程,每執行完一個 步驟跳進下個步驟之前會把中間狀態持久化到Mnesia中。如果任務因為節點故障停止的話,leader能檢測到并重新發起該任務,任務重啟后會從上一次失敗的“斷點”繼續向下執行。

  API/Web服務器向用戶提供了系統管理界面。它們是基于開源項目Mochiweb和Chicago Boss開發的,Mochiweb提供HTTP/HTTPS服務,而Chicago Boss是由Nginx的作者之一Evan Miller開發的,提供類似Rails的MVC框架。與Rails比,Erlang開發的框架天生就對并發有很好的支持,每個請求占用一個輕量級的Erlang進程,而Rails雖然在最近引入了多線程安全,但處理每條請求時仍然是獨占整個進程的,因此需要使用多進程模型處理并發請求,通過 Phusion Passenger等應用服務器進行派發。

  Proxy服務器向用戶提供訪問MySQL數據庫的服務,它完全實現了MySQL協議,用戶可以使用已有的MySQL客戶端連接到Proxy服務器,Proxy服務器通過用戶名獲取到用戶的認證信息、資源配額的限制(例如最大連接數、QPS和IOPS等),以及后臺MySQL實例的地址(列表),再將用戶的SQL查詢請求轉發到正確的MySQL實例上。

  除了數據路由的基本功能外,Proxy服務器中還實現了資源限制、屏蔽MySQL實例故障、讀寫分離、分庫分表、記錄用戶訪問日志等功能。Proxy服務器是無狀態的,服務器宕機不會對系統中其他服務器造成影響,只會造成連接到該Proxy的用戶連接斷開。多臺Proxy服務器采用LVS HA方案實現負載均衡,用戶應用重連后會被LVS定向到其他的Proxy上。

  Agent服務器部署在運行MySQL進程的機器上,用來管理每臺物理機上的MySQL實例,執行創建、刪除、備份、遷移和主從切換等操作,收集和分析MySQL進程的統計信息、bin log和slow query log。

  日志分析服務器會存儲和分析Proxy服務器傳入的用戶訪問日志,并實現了實時索引供用戶查詢一段時間內的慢日志和統計報表。信息統計服務器定期將采集到的用戶連接數、QPS數值,以及MySQL實例的進程狀態用RRDtool進行統計,可畫圖展示到Web界面上,也可為今后實現彈性的資源分配和自動化的 MySQL實例遷移提供依據。

  UMP系統中各節點間的通信(不包括SQL查詢、日志等大數據流的傳輸,這些還是直接走TCP的)都通過RabbitMQ,作為消息通信的中間件來使用,以保證消息發送的可靠性。ZooKeeper則主要發揮配置服務器、分布式鎖,以及監控所有MySQL實例的作用。

  在多個組件的協同作業下,整個系統實現了對用戶透明的容災、讀寫分離、分庫分表功能。系統內部還通過多個小規模用戶共享同一個MySQL實例,中等規模用戶獨占一個MySQL實例,多個MySQL實例共享同一個物理機的方式實現資源的虛擬化,降低整體成本。在資源隔離方面,通過Cgroup限制MySQL進程資源,以及在Proxy服務器端限制QPS相結合的方法,UMP系統能在實現資源虛擬化的同時保障用戶的服務質量。此外,UMP系統綜合運用SSL數據庫連接、數據訪問IP白名單、記錄用戶操作日志、SQL攔截等技術保護用戶的數據安全。

  結束語

  UMP系統的一些組件,例如Proxy服務器和日志分析服務器,目前已經運用在天貓的聚石塔平臺中,為電商和ISV提供安全的數據云服務。此外,UMP系統還運用在淘寶的店鋪裝修平臺中,為開發者提供數據服務。下一階段,我們希望UMP系統能進一步為企業降低數據存儲的成本。

  作者曹偉,花名鳴嵩,淘寶核心系統數據庫組技術專家,目前從事高性能服務器、IM、P2P、微博等各類型分布式系統、海量存儲產品的開發。

8
0
 
標簽:MySQL Erlang
 
 

文章列表

arrow
arrow
    全站熱搜

    大師兄 發表在 痞客邦 留言(0) 人氣()