杭州湖畔網絡技術有限公司是一家專業提供SaaS化電商ERP服務的創業公司,主要用戶羣體爲經營淘寶、天貓、京東等主流電商平臺、自建商城、線下渠道的商家及中小企業。做爲SaaS服務提供商,服務數萬乃至數十萬級用戶是業務架構初期就必須考慮的問題。龐大的用戶羣以及海量的用戶數據意味着基礎設施的構建必須兼顧高效與穩定,而按照通用的基礎設施建設方案的話,須要面對成本太高、實現複雜、須要投入太多精力等問題,這對當時的湖畔網絡這樣的初創公司來講,徹底不能承受。所以,更經濟、更方便擴展的雲服務平臺成爲首選。在對比現有各家雲服務後,咱們選擇了穩定性與成熟度都通過大量用戶檢驗的阿里雲。算法
但要構建高性能的SaaS應用,僅憑雲服務基礎設施是不夠的。如何基於雲服務平臺設計並實施符合自身業務特色的系統架構,也是決定產品性能的關鍵。本文將講述咱們如何利用雲服務,使用相對經濟的方案,解決海量用戶的數據庫使用問題。數據庫
架構後端
咱們的SaaS化電商ERP服務的總體架構是基於阿里雲服務平臺實施的,如圖1所示。緩存
圖1 系統架構精簡示意圖安全
經過該方案,不只發揮了阿里雲的優點(不涉及物理機器的維護和折損,靈活地配置升級,成熟的備份與快照方案),並且經過集羣,避免了系統可能會遇到的單點故障,提升了系統彈性擴容的靈活性和可用性。性能優化
做爲一個SaaS化、數據更集中、數據體量龐大的企業應用,數據庫是咱們總體架構中的關鍵節點,如何保證其穩定與性能,是本文講述的重點。服務器
當用戶進入快速增加期後,隨着業務量迅速增長,核心業務表的存量數據和增加速度絕對不是單個DB所能承受的(幾乎全部單DB配置都存在性能物理上限瓶頸,即便選擇升級配置也會受到成本和資源上限的約束)。所以,咱們一開始就將數據庫分庫分片(Sharding)做爲一個可行方案優先考慮,主要分析以下。網絡
考慮到業務特性,咱們最終採用了行業比較通用的水平拆分+垂直拆分策略,並自主完成DAO與JDBC之間的數據訪問封裝層開發工做。數據結構
水平拆分:按用戶將數據拆分到多個庫的相同表中架構
水平拆分的思路,就是將本來存放在單個RDS數據庫中的數據,根據業務ID不一樣,拆分到多個數據庫中(參見圖2)。拆分後,各庫的表數量及表結構都保持一致。水平拆分首先須要確立惟一的業務主表,即其餘全部表的數據都與主表ID(前文所說的業務ID)存在直接或間接的主從關係,能夠經過主表ID對所有數據作很好的切分。咱們選擇的業務主表爲用戶表,其餘業務表或表的父表都包含一個用戶ID。所以,咱們切分的目標就是將不一樣用戶數據存放到不一樣的數據庫中。
圖2 水平拆分示意圖
肯定了拆分規則後,下一步是着手封裝Sping數據訪問封裝層(DBWrapper)。DBWrapper介於DAO與JDBC之間,每一個業務DAO進行數據庫基本操做,都會通過DBWrapper。它的主要做用是將數據庫架構的變化對業務層透明,業務層能夠如同操做單個DB同樣,調用DBWrapper提供的數據庫操做接口,而判斷操做哪一個數據庫的邏輯,則所有交由DBWrapper封裝完成(參見圖3)。
圖3 水平拆分架構示意圖
DBWrapper主要提供新用戶初始化和數據庫操做接口。在新增用戶初始化到系統時,需先動態判斷系統各庫的負載分佈狀況。粗略一點的算法就是判斷各庫的用戶數,如共有4個庫,能夠根據user_id%4的狀況決定目標庫;再精細一點能夠挖掘下核心業務數據的分佈狀況,具體分配算法須要基於業務設定(如考慮不一樣用戶的平均訂單量)。經過各庫壓力綜合計算後,分析出壓力最小的目標數據庫,並將該新增用戶數據存放到指定的目標庫,同時更新路由信息(Router)。
當用戶完成初始化進行業務操做時,則需由業務層調用DBWrapper的操做接口。DBWrapper接收到請求後,會根據業務層傳入的User_id匹配Router,判斷最終須要操做的RDS實例和數據庫。判斷完成後,只須要循序漸進地開鏈接執行就能夠了。具體的代碼實現,須要結合自身的持久層框架,找一名研究過持久層框架實現的開發人員便可完成。
這樣就將系統用戶總體數據壓力,相對均勻地分佈到多個RDS實例與數據庫上。事實證實,這確實是一個很是有效的方案,尤爲是對於數據量大、增加迅猛的表。只是在後續實施過程當中,咱們發現有時會有單個用戶的業務壓力比較突出,針對這種狀況,咱們能夠經過一些人工干預(如遷移數據到單獨的庫)進行微調,固然最終的解決方案仍是要不斷調優路由算法。
切分後,不可避免地須要考慮數據字典(DD)和數據路由(Router)的處理。暫時咱們採用的方法是將全部數據字典與路由放入獨立的庫,這也是後文中垂直拆分的一種應用。須要說明的是,數據庫僅是這兩個業務的一種實現方式,通常還能夠經過或結合分佈式緩存來處理這些業務(咱們選用了OCS)。而對於可能出現的單點障礙,預留的擴展方案爲水平拆分或建立只讀節點(只讀節點可使用RDS最新提供的只讀實例,目前還在內測階段)。
垂直拆分:按業務將表分組拆分到多個庫中
與水平拆分相比,垂直拆分要更簡單一些。其基本思路就是將存放在單個數據庫的表分組,把其中業務耦合度較高、聯繫緊密的表分爲一組,拆分到其餘DB中(參見圖4)。拆分後,各庫的表結構及其業務意義將徹底不一樣。雖然規則簡單、實施方便,但垂直拆分老是需打斷些關聯,由於實際操做中,基礎資源經常出如今各個業務場景,在切分時又不得不切分到兩個庫中,此時就須要業務層屢次查詢後,在內存處理數據,實現數據庫Join的效果。
圖4 垂直拆分示意圖
垂直拆分一樣須要DBWrapper,但封裝規則與水平拆分略有不一樣,須要針對不一樣的業務,創建不一樣的DBWrapper。此時再也不是徹底業務層無感知,須要業務層根據業務場景有針對性使用。單個DBWrapper的實現與水平拆分一致。
垂直拆分的好處在於,將總體業務數據切分紅相對獨立的幾塊,隔離了不一樣業務之間的性能影響。而因爲拆分後的數據庫業務比較集中,也更容易找到業務主表,更有利於水平拆分。
對於垂直拆分,目前咱們主要用於解決數據路由(包含了用戶的基本信息)、數據字典模塊,以及常見的冷數據問題。冷數據的處理一直是行業的常見問題(其實對於冷數據的劃分,也是水平拆分),目前咱們採用的方案是集中存儲,即按本身的冷數據切分方式,經過自行開發的遷移程序將斷定的冷數據增量遷移到一個庫中。這個方案既可以分離冷數據對熱點數據的操做影響,也能夠爲大數據的挖掘提供比較便利的條件。使用相對獨立的冷數據存儲結構,能方便之後採用更高效、成本更低廉的存儲介質。固然該方案存在一些潛在問題,若是冷數據庫滿了該怎麼辦?目前咱們預留的設計方案是,歷史庫的水平拆分,也能夠考慮其餘存儲形式。
水平拆分與垂直拆分組合使用
拆分一直是數據庫優化的關鍵詞(不管是庫表結構仍是SQL寫法),它是每一個高併發產品最終都要經歷的一步。拆分方案的核心主要在於能夠經過添加更多RDS實例和數據庫(經常爲了節約成本,多個數據庫能夠部署在一個RDS實例上),靈活擴容系統的負載能力。在數據庫架構中,水平拆分和垂直拆分通常都是搭配使用的,二者的前後順序視具體狀況而定。通常而言,垂直拆分更容易,也能夠爲水平拆分作鋪墊,一是業務集中,便於提取主表,二是垂直拆分後,能夠只水平拆分壓力高的表,而業務增加緩慢的表則能夠保留單DB,從而提升拆分效率以及下降實施成本(參見圖5)。
圖5 數據庫Sharding方案示意圖
咱們之因此優先水平拆分,主要緣由仍是成本和效率及當時的一些侷限性。只按業務ID(用戶)作好路由配置,這樣各個庫中的結構徹底一致,保留了本來的業務邏輯與實現,避免了跨庫關聯,能大大節省實現成本。
儘管拆分有種種好處,但因爲分佈式事務及跨庫Join的實現複雜度較高及可用性較差,因此分佈式事務通常都經過業務層使用樂觀鎖控制。而跨庫的表間關聯必定要打斷,不然性能和實現複雜度都會超出可接受範圍。對於跨庫的Join、Group by等問題,都須要在業務層處理。目前咱們採用的是分批查詢,在業務層組裝結果的方式。
有些遺憾的是,因爲咱們早期使用RDS時,阿里雲還沒有推出DRDS(分佈式數據庫)產品,因此上述拆分的數據庫底層架構均是由咱們自行研發的,投入了大量的精力。而如今有了DRDS,正準備作拆分的團隊,則無需再本身造輪子,直接拿來用便可,這樣團隊能夠將更多的精力放在業務上。
小處大有可爲
雖然咱們在架構上作了優化,但在產品發展過程當中仍是會出現性能不太理想的狀況。在阿里雲支持中心和論壇上,也能夠看到其餘業務型團隊反饋使用RDS時遇到相似的狀況。最初你們都懷疑是否是RDS的底層資源隔離有問題,多個用戶共享資源時發生爭搶,致使RDS的性能問題。但在阿里雲DBA的指導和協助下,發現是因爲產品設計時對數據庫的使用太「不拘小節」,而隨着併發壓力與數據量增長,大量細小的性能問題被放大,集中暴露出來。
解決燈下黑:修正業務層的數據庫操做陋習
在數據庫的優化過程當中,研發團隊最容易忽視的每每是業務層中的數據庫使用。一些優化方案能夠做爲開發的常態化準則。下面僅列舉幾個經常使用的優化方案。
擠掉海綿裏的水:優化數據庫執行計劃
因爲執行計劃的優化每每涉及到數據庫的運行機制與底層設計,此處實難三言兩語說清。因此下面僅列舉幾個咱們受益頗深的優化方案。建議你們優化執行計劃時,多關注、分析iDB Cloud控制檯中的性能報告和建議,也儘可能多向阿里雲DBA們請教,通常能夠經過提工單的方式。有條件或興趣的話,DBA能夠經過預定到阿里雲現場學習。另外,執行計劃的優化須要大量的調試工做,經過在阿里雲控制檯建立生產數據庫的臨時實例,能夠準確模擬當前系統的數據結構、分佈與壓力。
字段類型選擇
選擇合理的字段,每每能夠大大減小數據庫行數據的大小,並提升索引匹配的效率,進而大大提高數據庫性能。使用更小的數據類型,如日期採用date代替datetime、類型或標記使用tinyint代替smallint和int、使用定長字段代替非定長字段(如char代替varchar),都能或多或少減小數據行大小,提升數據庫緩衝池的命中率。而做爲表字段中特殊的一員—主鍵,其選型更會對錶索引的穩定和效率帶來很大的影響,通常建議考慮數據庫自增或自主維護的惟一數值。
高分離度字段創建索引
對於查詢來說,高分離度字段每每意味着精準或部分精準的條件。相對來說是最好優化的一種場景,只須要對分離度較高的字段單獨創建索引便可。固然實際使用中會有更多細節須要摸索。精確條件在各業務中基本都會用到,在越複雜的業務場景中,精確條件優先的原則,將是最有效的優化方案。須要注意的是,儘管高分離度字段單獨創建索引效率很高,但過多的索引會影響表寫入的效率,因此須要謹慎添加。這一點iDB Cloud中有大表索引的建議能夠參考。
覆蓋索引(Covering Index)
通俗一點理解,就是執行計劃能夠經過索引完成數據查找和結果集獲取,而無需回表(去緩衝池或磁盤查找數據)。而因爲MySQL的索引機制限制,一次查詢時,將只用到一個索引或將兩個索引聚合(index_merge)起來使用,因此意味着複雜的業務場景中,單獨對每一個字段創建索引可能沒有什麼用處。因此對於一些特定的查詢場景,創建合適的組合索引,應用覆蓋索引方法能夠避免大量隨機I/O,是更爲推薦的優化方案(若是執行計劃Explain的Extra中有Using Index,就說明使用了覆蓋索引)。但實際業務老是會比索引自己更復雜,業務中須要查找或者獲取的字段信息每每是不少的,而組合索引並不能涵蓋全部的字段(不然咱們將擁有一個比數據還要龐大的索引)。此時,爲了應用覆蓋索引,就須要使用主鍵延遲關聯(Deferred Join),即先經過組合索引中包含的字段條件,初步查詢出相對較小的結果集(面向結果集原則),該結果集只包含主鍵字段;而後經過獲取到的這個主鍵隊列,再對數據表作關聯。
適當妥協
見招拆招:升配置
通常業務型的研發團隊,很難有額外的精力投入到數據庫方面,也沒有專業的DBA來不斷調優數據庫配置、優化數據庫服務器性能。因此早期團隊能夠選擇的方案很少,也很難在技術上深挖下去,只能用成本換時間:性能配置不夠,那就升級服務器配置。
那麼問題來了:本身部署的數據庫要升級配置,除了調整數據庫配置參數,還會受到物理機的限制,所以就要考慮更加複雜的數據庫備份和同步策略。但這是業務團隊所不能接受,甚至短時間內沒法實現的,升配置也就變成了一個複雜的問題。不過咱們使用了RDS,其彈性升級策略,正是這個問題的最佳解決方案。
二八原則
在長期的數據庫乃至整個產品的優化過程當中,我感覺最深入的就是:完美的方案可遇不可求。選擇方案時,若是能解決80%的問題,並規避或保留剩下的20%,則將大大提高團隊的總體效率。產品與架構都是在不斷優化演變的,咱們要按部就班、不斷努力,將今天的終點留做明天的起點。
總結與展望
做爲一位創業公司的技術開發人員,經過實際使用阿里雲產品,我總結了幾點關於使用雲計算產品的優點。
1. 便利的服務器彈性升級功能,可隨時應付像「雙十一」這樣的大促。而經過使用傳統IDC託管模式,物理機的維護、升級以及升級後的數據遷移都是比較頭疼的。
2. 成熟可靠的數據備份與快照、數據庫主從分離與同步的底層方案。創業團隊無須承受本身造輪子的代價,可專一於業務開發。
3. 雲計算產品通過檢驗、值得信賴的安全防禦。
4. 精簡了創業團隊人員規模。雲計算平臺具有專業的技術支持與服務,使得創業團隊再也不須要數據庫和服務器管理員。
除了使用雲產品的心得,數據庫調優實踐是本文的重點。在數據庫的架構設計與性能優化方面,我秉承的原則是解決主要問題,按先分而擊之、再挖掘細節的步驟,周返往復不斷進行,同時系統架構也在這個過程當中不斷演變。相信隨着時間推移,會有更多優秀的方案出現。尤爲隨着雲服務不斷髮展,業務研發團隊投入到基礎設施的精力與成本,將會無限減小。會有愈來愈多專一於業務研發的團隊,推出更多優秀的互聯網產品,用互聯網服務推進企業創新,重塑中小企業信息化形態。
轉載:http://blog.sina.com.cn/l1pe1