作者簡介

 

瑞華,攜程高級後端開發工程師,關注係統架構、分庫分錶、微服務、高可用等。



一、前言


隨著國際火車票業務的高速發展,訂單量快速增長,單數據庫瓶頸層麵的問題逐漸顯露,常規的數據庫優化已無法達到期望的效果。同時,原先的底層數據庫設計,也存在一些曆史遺留問題,比如存在部分無用字段、錶通過自增主鍵關聯和各個應用直連數據庫等問題。


為此,經過討論後,我們決定對訂單庫進行分庫分錶,同時對訂單錶進行重構,進而從根本上解決這些問題。


二、問題挑戰


目標確定後,實踐起來可不輕鬆,齣現瞭很多的問題和挑戰。這裏列舉一些典型問題,大緻可以分為兩大類:分庫分錶通用問題、具體業務關聯問題。


分庫分錶通用問題


  • 如何切分,垂直分還是水平分?分片的鍵,如何選取?

  • 如何根據鍵值路由到對應庫、對應錶?

  • 采用什麼中間件,代理方式還是中間件的方式?

  • 跨庫操作等問題,如跨庫事務和跨庫關聯?

  • 數據擴容問題,後續如何進行擴容?


具體業務關聯問題


  • 各個應用直連數據如何解決?

  • 如何進行平滑過渡?

  • 曆史數據如何恰當遷移?


三、方案選型


3.1 如何切分


切分方式,一般分為垂直分庫、垂直分錶、水平分庫和水平分錶四種,如何選擇,一般是根據自己的業務需求決定。


我們的目標是要從根本上解決數據量大、單機性能問題等問題,垂直方式並不能滿足需求,所以我們選取瞭水平分庫+水平分錶的切分方式。


3.2 分片鍵選取


一般是根據自己的實際業務,來選擇字段來作為分片的鍵,同時可以結閤考慮數據的熱點問題 、分布問題。比如訂單係統,不能根據國傢字段進行分片,否則可能會齣現某些國傢很多的訂單記錄,某些國傢幾乎沒有訂單記錄,進而數據分布不均。相對正確的方式,比如訂單類係統,可以選擇訂單ID;會員係統,可以選擇會員ID。


3.3 如何路由


選定瞭分片的鍵之後,接下來需要探討的問題,就是如何路由到具體的數據庫和具體的錶。以分片鍵路由到具體某一個數據庫為例,常見的路由方式如下:


映射路由


映射路由,即新增一個庫,新建一個路由映射錶,存儲分片鍵值和對應的庫之間的映射關係。比如,鍵值為 1001,映射到 db01 這個數據庫,如下圖所示:



映射方式,優點是映射方式可任意調整,擴容簡單,但是存在一個比較嚴重的不足,就是映射庫中的映射錶的數據量異常巨大。我們本來的目標是要實現分庫分錶的功能,可是現在,映射庫映射錶相當於迴到瞭分庫分錶之前的狀態。所以,我們在實踐中,沒有采取這種方式。


分組路由


分組路由,即對分片的鍵值,進行分組,每組對應到一個具體的數據庫。比如,鍵值為 1000到2000,則存儲到 db01 這個數據庫,如下圖所示:



分組方式,優點是擴容簡單,實現簡單,但是也存在一個比較嚴重的不足,是數據分布熱點問題,比如在某一個時間內,分片鍵值為2001,則在將來一段時間內,所有的數據流量,全部打到某一個庫(db02)。這個問題,在互聯網環境下,也比較嚴重,比如在一些促銷活動中,訂單量會有一個明顯的飆升,這時候各個數據庫不能達到分攤流量的效果,隻有一個庫在接收流量,會迴到分庫分錶之前的狀態。所以,我們也沒有采取這種方式。


哈希路由


哈希路由,即對分片的鍵值,進行哈希,然後根據哈希結果,對應到一個具體的數據庫。比如,鍵值為 1000,對其取哈希的結果為 01,則存儲到 db01 這個數據庫,如下圖所示:



哈希方式,優點是分布均勻,無熱點問題,但是反過來,數據擴容比較麻煩。因為在擴容過程中,需要調整哈希函數,隨之帶齣一個數據遷移問題。互聯網環境下,遷移過程中往往不能進行停服,所以就需要類似多庫雙寫等方式進行過渡,比較麻煩。所以,在實踐中也沒有采取這種方式。


分組哈希路由


分組哈希路由,即對分片的鍵值,先進行分組,後再進行哈希。如下圖所示:



在實踐中,我們結閤瞭前麵的幾種方式,藉鑒瞭他們的優點不足,而采用瞭此種方式。因為分組方式,能很方便的進行擴容,解決瞭數據擴容問題;哈希方式,能解決分布相對均勻,無單點數據庫熱點問題。


3.4 技術中間件


分庫分錶的中間件選取,在行業內的方案還是比較多的,公司也有自己的實現。根據實現方式的不同,可以分為代理和非代理方式,下麵列舉瞭一些業界常見的中間件,如下錶(截至於2021-04-08):




我們為什麼最終選擇瞭 Sharding-Sphere 呢?主要從這幾個因素考慮:


技術環境


  • 我們團隊是Java體係下的,對Java中間件有一些偏愛
  • 更偏嚮於輕量級組件,可以深入研究的組件
  • 可能會需要一些個性定製化


專業程度


  • 取決於中間件由哪個團隊進行維護,是否是名師打造,是否是行業標杆
  • 更新迭代頻率,最好是更新相對頻繁,維護較積極的
  • 流行度問題,偏嚮於流行度廣、社區活躍的中間件
  • 性能問題,性能能滿足我們的要求


使用成本


  • 學習成本、入門成本和定製改造成本
  • 弱浸入性,對業務能較少浸入
  • 現有技術棧下的遷移成本,我們當前技術棧是SSM體係下


運維成本


  • 高可用、高穩定性
  • 減少硬件資源,不希望再單獨引入一個代理中間件,還要考慮運維成本
  • 豐富的埋點、完善的監控


四、業務實踐


在業務實踐中,我們經曆瞭從新庫新錶的設計,分庫分錶自建代理、服務收口、上遊訂單應用遷移,曆史數據遷移等過程。


4.1 新錶模型


為瞭建立分庫分錶下的關聯關係,和更加閤理有效的結構,我們新申請瞭訂單分庫分錶的幾個庫,設計瞭一套全新的錶結構。錶名以年份結尾、規範化錶字段、適當增刪瞭部分字段、不使用自增主鍵關聯,采用業務唯一鍵進行關聯等。


錶結構示例如下圖:



4.2 服務收口


自建瞭一個分庫分錶數據庫的服務代理 Dal-Sharding。每一個需要操作訂單庫的服務,都要通過代理服務進行操作數據庫,達到服務的一個收口效果。同時,屏蔽瞭分庫分錶的復雜性,規範數據庫的基本增刪改查方法。



4.3 平滑過渡


應用遷移過程中,為瞭保證應用的平滑過渡,我們新增瞭一些同步邏輯,來保證應用的順利遷移,在應用遷移前後,對應用沒有任何影響。未遷移的應用,可以讀取到遷移後應用寫入的訂單數據;遷移後的應用,能讀取到未遷移應用寫入的訂單數據。同時,統一實現瞭此邏輯,減少各個應用的遷移成本。


新老庫雙讀


顧名思義,就是在讀取的時候,兩個庫可能都要進行讀取,即優先讀取新庫,如果能讀到記錄,直接返迴;否則,再次讀取老庫記錄,並返迴結果。


雙讀的基本過程如下:



新老庫雙讀,保證瞭應用遷移過程中讀取的低成本,上遊應用不需要關心數據來源於新的庫還是老的庫,隻要關心數據的讀取即可,減少瞭切換新庫和分庫分錶的邏輯,極大的減少瞭遷移的工作量。


實踐過程中,我們通過切麵實現雙讀邏輯,將雙讀邏輯放入到切麵中進行,減小新庫的讀取邏輯的侵入,方便後麵實現對雙讀邏輯的移除調整。


同時,新增一些配置,比如可以控製到哪些錶需要進行雙讀,那些錶不需要雙讀等。


新老庫雙寫


新老庫雙寫,就是在寫入新庫成功後,異步寫入到老庫中。雙寫使得新老庫都同時存在這些訂單數據,尚未遷移通過代理服務操作數據庫的應用得以正常的運作。


雙寫的基本過程如下:



雙寫其實有較多的方案,比如基於數據庫的日誌,通過監聽解析數據庫日誌實現同步;也可以通過切麵,實現雙寫;還可以通過定時任務進行同步;另外,結閤到我們自己的訂單業務,我們還可以通過訂單事件(比如創單成功、齣票成功、退票成功等),進行雙寫,同步數據到老庫中。


目前,我們經過考慮,沒有通過數據庫日誌來實現,因為這樣相當於把邏輯下沉到瞭數據庫層麵,從實現上不夠靈活,同時,可能還會涉及到一些權限、排期等問題。實踐中,我們采取其他三種方式,互補形式,進行雙寫。異步切麵雙寫,保證瞭最大的時效性;訂單事件,保證瞭核心節點的一緻性;定時任務,保證瞭最終的一緻性。


跟雙讀一樣,我們也支持配置控製到哪些錶需要進行雙寫,那些錶不需要雙寫等。


過渡遷移


有瞭前麵的雙讀雙寫作為基礎,遷移相對容易實行,我們采取逐個遷移的方式,比如,按照服務、按照渠道和按照供應進行遷移,將遷移工作進行拆解,減少影響麵,追求穩健。一般分為三步走方式:


1)第一階段,先在新對接的供應商中進行遷移新庫,因為新上綫的供應商,訂單量最少,同時哪怕齣現瞭問題,不至於影響到之前的業務。
2)再次遷移量比較少的綫上業務,此類訂單,有一些量,但是追求穩定,不能因為切換新庫而産生影響。所以,將此類業務放到瞭第二階段中進行。
3)最後一步是,將量較大的業務,逐漸遷移到新庫中,此類業務,需要在在有前麵的保證後,方能進行遷移,保證訂單的正常進行。



4.4 數據遷移


數據遷移,即將數據,從老庫遷移到新庫,是新老庫切換的一個必經過程。遷移的常規思路,一般是每個錶一個個進行遷移,結閤業務,我們沒有采取此做法,而是從訂單維度進行遷移。


舉個例子:假如訂單庫有Order錶、OrderStation錶、OrderFare錶三個錶,我們沒有采取一個一個錶分彆進行遷移,而是根據訂單號,以每一個訂單的信息,進行同步。


大緻過程如下:


1)開啓一個定時任務,查詢訂單列錶,取得訂單號等基本訂單信息。
2)根據這個訂單號,去分彆查詢訂單的其他信息,取得一個完整的訂單信息。
3)校驗訂單是否已經完成同步,之前完成同步瞭則直接跳過,否則繼續執行下一個訂單號。
4)將老庫的完整的訂單信息,映射成新庫的對應的模型。
5)將新的訂單信息,同步寫入到新庫各個錶中。
6)繼續執行下一個訂單號,直到所有的訂單號都完全同步結束。



4.5 完成效果


訂單庫經過一個全新的重構,目前已經在綫上穩定運行,效果顯著,達到瞭我們想要的效果。


  • 服務收口,將分庫分錶邏輯,收口到瞭一個服務中;
  • 接口統一管理,統一對敏感字段進行加密;
  • 功能靈活,提供豐富的功能,支持定製化;
  • 庫分錶路由透明,且基於主流技術,易於上手;
  • 完善的監控,支持到錶維度的監控;


五、常見問題總結


5.1 分庫分錶典型問題


問題1:如何進行跨庫操作,關聯查詢,跨庫事務?


迴答:對於跨庫操作,在訂單主流程應用中,我們目前是禁止瞭比如跨庫查詢、跨庫事務等操作的。對於跨庫事務,因為根據訂單號、創建年份路由,都是會路由到同一個數據庫中,也不會存在跨庫事務。同樣對於跨庫關聯查詢,也不會存在,往往都是根據訂單來進行查詢。同時,也可以適當進行冗餘,比如存儲車站編碼的同時,多存儲一個車站名稱字段。


問題2:如何進行分頁查詢?


迴答:目前在訂單主流程應用中的分頁查詢,我們直接采用瞭Sharding-JDBC提供的最原始的分頁方式,直接按照正常的分頁SQL,來進行查詢分頁即可。理由:主流程訂單服務,比如齣票係統,往往都是查詢前麵幾頁的訂單,直接查詢即可,不會存在很深的翻頁。當然,對於要求較高的分頁查詢,可以去實現二次查詢,來實現更加高效的分頁查詢。


問題3:如何支持很復雜的統計查詢?


迴答:專門增加瞭一個寬錶,來滿足那些很復雜查詢的需求,將常用的查詢信息,全部落到此錶中,進而可以快速得到這些復雜查詢的結果。


5.2 API方法問題


問題:服務收口後,如何滿足業務各種不同的查詢條件?


迴答:我們的API方法,相對固定,一般查詢類隻有兩個方法,根據訂單號查詢,和根據Condition查詢條件進行查詢。對於各種不同的查詢條件,則通過新增Condition的字段屬性來實現,而不會新增各種查詢方法。


5.3 均勻問題


問題:在不同group中,數據會存在分布不均勻,存在熱點問題?


迴答:是的,比如運行5年後,我們拓展成瞭3個group,每一個group中存在3個庫,那麼此時,讀寫最多的應該是第三個group。不過這種分布不均勻問題和熱點問題,是可接受的,相當於前麵的兩個group,可以作為曆史歸檔group,目前主要使用的group為第三個group。


隨著業務的發展,你可以進行調配,比如業務發展迅速,那麼相對閤理的分配,往往不會是每個group是3個庫,更可能是應該是,越往後group內的庫越多。同時,因為每個group內是存在多個庫,與之前的某一個庫的熱點問題是存在本質差彆,而不用擔心將單數據庫瓶頸問題,可以通過加庫來實現擴展。


5.4 Group內路由問題


問題:對於僅根據訂單號查詢,在group內的路由過程是讀取group內所有的錶嗎?


迴答:根據目前的設計,是的。目前是按年份分組,訂單號不會存儲其他信息,采用攜程統一方式生成,也就是如果根據訂單號查詢,我們並不知道是存在於哪個錶,則需要查詢group內所有的錶。對於此類問題,通常推薦做法是,可以適當增加因子,在訂單號中,存儲創建年份信息,這樣就可以知道對應那個錶瞭;也可以年份適當進行延伸,比如每5年一次分錶,那麼這樣調整後,一個group內的錶應該相對很少,可以極大加快查詢效能。


5.5 異步雙寫問題


問題:為什麼雙寫過程,采用瞭多種方式結閤的方式?


迴答:首先,切麵方式,能最大限度滿足訂單同步的時效性。但是,在實踐過程中,我們發現,異步切麵雙寫,會存在多綫程並發問題。因為在老庫中,錶的關聯關係依賴於數據庫的自增ID,依賴於錶的插入順序,會存在關聯失敗的情況。所以,單純依靠切麵同步還不夠,還需要更加穩健的方式,即定時任務(訂單事件是不可靠消息事件,即可能會存在丟失情況)的方式,來保證數據庫的一緻性。


參考連接


[1] Sharding-Sphere 概述

https://shardingsphere.apache.org/document/current/cn/overview/

[2] 大眾點評訂單係統分庫分錶實踐

https://tech.meituan.com/2016/11/18/dianping-order-db-sharding.html

[3] Mycat與ShardingSphere如何選擇

https://blog.nxhz1688.com/2021/01/19/mycat-shardingsphere/

[4] 分庫分錶:如何做到永不遷移數據和避免熱點?

https://mp.weixin.qq.com/s/-YNU6wDZ3_lEh7vlsslDfQ


團隊招聘信息

我們是攜程火車票研發團隊,負責火車票業務的開發以及創新。火車票研發在多種交通綫路聯程聯運算法、多種交通工具一站式預定、高並發方嚮不斷地深入探索和創新,持續優化用戶體驗,提高效率,緻力於為全球人民買全球火車票。
 
在火車票研發團隊,你可以和眾多技術大牛一起,真實地讓億萬用戶享受你的産品和代碼,提升全球旅行者齣行體驗和幸福指數。
 
如果你也熱愛技術,並渴望不斷成長,火車票研發團隊期待與你一起高速前行。目前我們前端、後颱、算法、大數據、測試等技術崗位均有職位。
 
簡曆投遞:[email protected]  郵件標題:【姓名】-【攜程火車票】-【投遞職位】


【推薦閱讀】


  • 後微服務時代,領域驅動設計在攜程國際火車票的實踐

  • Reactive模式在Trip.com消息推送平颱上的實踐

  • 攜程最終一緻和強一緻性緩存實踐

  • 秒級上下綫,攜程服務注冊中心架構演進



 “攜程技術”公眾號

  分享,交流,成長