- 商業銀行數據庫管理實踐
- 王飛鵬 王寧等編著
- 6753字
- 2022-07-28 19:24:50
2.2 從集中式到分布式數據庫——電商管家系統演進之路
2000年以后,互聯網應用如雨后春筍般出現,互聯網應用需要支持大量用戶的并發訪問,且對可用性要求極高,最好永遠在線;另外,隨著互聯網技術發展的風起云涌,使得數據規模越來越大,一天的數據增量甚至達到TB級,這考驗著單機MySQL或者PostgreSQL的性能。
和互聯網類似,商業銀行也有大量的系統需要接受用戶高并發訪問,數據量也在快速增長。商業銀行還有一些內容管理方面的系統,這種系統會有非常高的讀寫比,對數據庫訪問壓力極大。但傳統上,商業銀行使用的仍然是集中式架構,例如,最常用的Oracle、Db2等。從圖2-4可以看出,接收業務訪問的集中式數據庫安裝在單臺服務器上(通常還會有個備機,但不接受業務訪問),供本地用戶和遠程用戶訪問。
互聯網行業和商業銀行的高并發訪問和數據量的快速增長,已經是一種新常態。它對單機數據庫造成了極大的挑戰,包括容量、性能、穩定性、高可用、維護成本等。

圖2-4 集中式數據庫架構
面對傳統集中式架構的性能瓶頸和容量瓶頸,本節將繼續以某商業銀行電商管家系統為例,來說明在不同階段可以采取的技術手段,以及這種技術手段所能達到的效果。
2.2.1 硬件擴容方案——摩爾定律失效導致效果有限
1965年,英特爾聯合創始人戈登·摩爾提出以自己名字命名的摩爾定律,意指集成電路上可容納的元器件的數量每隔18~24個月就會增加一倍,性能也將提升一倍,如圖2-5所示。

圖2-5 摩爾定律
到今天已經五十多年了,摩爾定律過去是每5年增長10倍,每10年增長100倍。而如今,摩爾定律每年只能增長幾個百分點,每10年可能只有2倍。芯片逼近物理和經濟成本上的極限,摩爾定律結束了。硅谷創業教父Steve Blank曾撰文指出,嚴格來說,摩爾定律其實在10年前就已經失效,只是大家沒有意識到。
隨著摩爾定律的失效,帶來的一個效應就是,單機性能的瓶頸也已經出現。想要進一步增加單機的性能,需要的成本已經不是線性的,而是呈幾何倍數的增加。
電商管家系統,某商業銀行曾經嘗試對數據庫服務器縱向擴容,即通過增加CPU、內存等硬件資源提高單機處理性能,但實踐證明這種方式換來的性能提升十分有限,表現出明顯的邊際收益遞減。
這就需要我們去思考,如何用廉價的x86機器,甚至云上的虛擬機集群,通過分布式的方式,來達到同樣的增強整體性能和容量的目的。
2.2.2 Redis緩存方案——解決高并發性能問題
電商管家系統上線后,隨著交易量和數據量的增加,一旦涉及大數據量的需求,例如商品搶購“秒殺”的情景,或者是主頁訪問量瞬間劇增的時候,單機數據庫受制于CPU和磁盤I/O性能而存在嚴重的性能瓶頸;在這種“秒殺”場景中,一瞬間成千上萬的請求到來,需要數據庫在極短的時間內完成成千上萬次的讀/寫操作,這不是單機數據庫能夠承受的,極其容易造成數據庫系統癱瘓,最終導致服務宕機的嚴重生產問題。
這種“秒殺”場景,從Web頁面、緩存層以及數據庫層都有相應的優化手段,本節重點從緩存層給出一種優化方案——Redis緩存方案。
在合適的場景下,Redis的性能十分優越,可以支持每秒萬次以上的讀/寫操作,其性能遠超數據庫,并且還支持集群、分布式、主從同步等配置,原則上可以無限擴展,讓更多的數據存儲在內存中,它還支持一定的事務能力,這保證了高并發場景下數據的一致性。
什么是Redis?
Redis是一個開源的、使用C語言編寫的、支持網絡交互的、可基于內存也可持久化的高性能Key-Value數據庫。它支持字符串(String)、鏈表(List)、集合(Set)、有序集合(SortedSet)、哈希表(Hash)等多種數據類型。
如圖2-6所示,在大并發的情況下,所有的請求直接訪問數據庫,數據庫會出現性能瓶頸。這時,可以使用Redis做一個緩沖操作,讓請求先訪問到Redis,而不是直接訪問數據庫。

圖2-6 應用發起的高并發請求
如圖2-7所示,電商管家系統采用了Redis集群方案,集群共有三個節點。這是一個分布式、容錯的Redis實現。Redis集群中不存在中心節點或者代理節點,集群的主要設計目標是達到線性擴展。

圖2-7 三節點Redis集群方案
在日常對數據庫的訪問中,經常讀操作的次數遠超寫操作,所以讀的可能性是比寫的可能大得多的。當使用SQL語句訪問數據庫進行讀寫操作時,數據庫就會去磁盤把對應的數據取回來,這是一個相對較慢的過程。如果把數據放在Redis中,也就是直接放在內存之中,讓服務端直接去讀取內存中的數據,那么這樣速度明顯就會快不少,并且會極大減小數據庫的壓力。
2.2.3 MySQL讀寫分離方案——解決高讀寫比性能問題
在2.2.2節,面對高并發的“秒殺”場景,電商管家系統采用了Redis緩存方案。但Redis緩存方案也有弊端,那就是使用內存進行數據存儲開銷也是比較大的。限于成本的原因,一般只是使用Redis存儲一些常用和主要的數據,這就意味著無法保證所有數據被緩存,從而限制了它的使用范圍。
采用Redis緩存方案后,電商管家系統面對日益增加的系統訪問量,數據庫的吞吐量仍然面臨著巨大瓶頸,這個時候讀寫分離方案正式登場。
對于同一時刻有大量并發讀操作和較少寫操作類型的應用系統來說,將數據庫拆分為主庫和從庫,主庫負責處理事務性的增刪改操作,從庫負責處理查詢操作,能夠有效地避免由數據更新導致的行鎖,使得整個系統的查詢性能得到極大的改善。
如圖2-8所示,針對電商管家系統,過了一段時間,數據庫的讀壓力太大,又加了一臺從庫,將部分讀請求路由到新搭建的從庫上,從而實現了讀寫分離,以降低主庫讀的壓力。

圖2-8 讀寫分離方案——物理部署架構
通過一主多從的配置方式,可以將查詢請求均勻地分散到多個數據副本,能夠進一步提升系統的處理能力,這樣不但能夠提升系統的吞吐量,還能夠提升系統的可用性,可以達到在任何一個數據庫宕機,甚至磁盤物理損壞的情況下仍然不影響系統的正常運行。
如圖2-9所示,采用讀寫分離方案的邏輯結構,商家表Dealer和商品表Goods在主庫和從庫都會存放,這樣方便去從庫執行只讀查詢。

圖2-9 讀寫分離方案——邏輯結構
讀寫分離雖然可以提升系統的吞吐量和可用性,但主庫與從庫的復制一般不是強一致的,這帶來了數據不一致的問題。例如,先在主庫中插入了一條訂單信息,返回了訂單號;然后執行查詢操作,但是可能因為讀寫分離被路由到了從庫。但是,此時從庫還沒有來得及從主庫同步binlog,導致從庫沒有這條記錄,所以查詢操作返回空。
在讀寫分離的路由實現上,可以在業務系統的代碼中配置多個數據源,并根據自己的需要把應用切換到對應的主庫或某個從庫。但是,顯然這種機制不夠方便,對應用的侵入性較強。
此外,也可以簡單地封裝一個中間層,根據執行的事務是讀操作還是寫操作來判斷,如果是讀操作就路由到從庫執行,否則就路由到主庫執行。這樣就可以對業務系統透明。
2.2.4 分庫分表方案——解決性能和容量瓶頸問題
不論是緩存方案還是讀寫分離方案,都只是基于傳統集中式數據庫方案之上的優化,在容量、性能、可用性和運維成本這幾方面已經難于滿足商業銀行高并發和大數據量訪問場景。
從容量方面,在單庫單表數據量超過一定數值的情況下,索引樹層級增加,磁盤I/O也很可能出現壓力,會導致很多問題。
從性能方面,由于關系數據庫大多采用B+樹類型的索引,在數據量超過閾值的情況下,索引深度的增加也將使得磁盤訪問的I/O次數增加,進而導致查詢性能的下降;同時,高并發訪問請求也使得集中式數據庫成為系統的最大瓶頸。
從可用性方面,服務化的無狀態型,能夠達到較小成本的隨意擴容,這必然導致系統的最終壓力都落在數據庫之上。而單一的數據節點,或者簡單的主從架構,已經越來越難以承擔。數據庫的可用性,已成為整個系統的關鍵。
從運維成本方面,當一個數據庫實例中的數據達到閾值以上,對于DBA的運維壓力就會增大。數據備份和恢復的時間成本都將隨著數據量的增大而增大。
對MySQL數據庫來說,單庫的數據閾值在1TB之內,單表容量在100GB以內,是比較合理的范圍,當單庫單表數據過大時,業界通行的做法是進行拆分,拆分方法主要有以下4種,下面分別介紹。
1.垂直分表
如果單表數據量過大,還可能需要對單表進行拆分。例如,一個200列的商品表,拆分成兩個子表:商品表和商品詳情表。這種拆分方法對業務系統的影響很大,本來從商品表就可以獲取所需數據,但現在需要關聯商品表和商品詳情表,也就是說,應用SQL需要改寫,通常動的越多,出現生產故障的風險就越高。
2.垂直分庫
拆分商家表和商品表的數據,變成兩個獨立的庫,商家表為一個商家數據庫,商品表為另外一個商品數據庫。這種方式對業務系統影響更大,因為數據結構本身發生了變化,SQL語句和關聯關系也必隨之發生了改變。原來一條SQL語句直接把商家表和相關的商品表數據都查了出來,現在這條SQL語句不能用了,需要重新改寫。先查詢商家數據庫,拿到這批商家對應的所有商品id,再根據商品id集合去商品庫查詢所有的商品信息,最后在應用代碼里進行組裝。
3.水平分表
水平分表,是把一張表的數據分到同一個數據庫的多張子表中,每張子表的結構不變,但只有部分數據。這種拆分方式也會對應用產生影響。原來查單表的一條SQL語句,需要轉換為從多個子表中查詢后匯總結果。
4.水平分庫
可以把一個表的數據分到多個不同的子庫(通常稱為分片)。每個分片只有這個表的部分數據,這些分片可以分布在不同服務器,從而使訪問壓力被多服務器負擔,可以大大提升性能。這樣對業務系統本身的代碼邏輯來說,就不需要做特別大的改動,甚至可以基于一些中間件做到透明。
一般情況下,如果數據本身的讀寫壓力較大,磁盤I/O已經成為瓶頸,那么分庫比分表要好。分庫將數據分散到不同的數據庫實例,使用不同的磁盤,從而可以并行提升整個集群的并行處理能力;相反的情況下,可以盡量多考慮分表,降低單表的數據量,從而減少單表操作的時間,同時也能在單個數據庫上使用并行操作多個表來增加處理能力。
一般來說,在系統設計階段就應該根據業務耦合松緊來確定垂直分庫、垂直分表方案,在數據量及訪問壓力不是特別大的情況,首先考慮緩存、讀寫分離、索引技術等方案。若數據量極大,且持續增長,再考慮水平分庫、水平分表方案。
針對本章提到的某商業銀行電商管家系統,在數據容量和性能方面難以滿足要求時,就可以采用水平分庫方案了。如圖2-10所示,商家表Dealer和商品表Goods被拆分到4個分片中,此時可以在Java環境里引入TDDL或者Sharding-JDBC之類的框架,在業務代碼側,通過配置特定的分庫分表規則,以及對分布式事務的控制,在保證數據一致性的情況下,提升數據庫整體集群的容量,保證穩定性和性能。

圖2-10 分庫分表方案——物理部署架構
如圖2-11所示,采用分庫分表方案的邏輯結構,商家表Dealer和商品表Goods被拆分,并被分到4個分片中,從而使訪問壓力被4臺服務器分擔,可以大大提升性能。

圖2-11 分庫分表方案——邏輯結構
2.2.5 分布式數據庫中間件方案——通過中間件透明訪問數據庫
從2.2.4節了解到,分庫分表方案的優勢主要是:可以支持各種常見的數據庫,如MySQL、PostgreSQL,還有SQL Server、Oracle、Db2等,另外性能損耗也較小。但其最大的缺點是對應用有侵入性,需要開發人員直接處理和維護分庫分表邏輯。
為了讓應用透明地訪問,業界通用的做法是采用分布式數據庫中間件解決方案。使用數據庫中間件,可以屏蔽分庫分表后的數據庫復雜性。
本節重點介紹Apache ShardingSphere方案。
Apache ShardingSphere是一套開源的分布式數據庫中間件解決方案組成的生態圈,它由JDBC、Proxy和Sidecar(規劃中)3款相互獨立卻又能夠混合部署配合使用的產品組成。它們均提供標準化的數據分片、分布式事務和數據庫治理功能,可適用于如Java同構、異構語言、云原生等各種多樣化的應用場景。
Apache ShardingSphere定位為關系數據庫中間件,旨在充分合理地在分布式的場景下利用關系數據庫的計算和存儲能力,而并非實現一個全新的關系數據庫。主要考慮到關系數據庫當今依然占有巨大市場,是各個公司核心業務的基石,未來也難以撼動,所以該方案目前階段更加關注在原有基礎上的增量,而非顛覆。
Apache ShardingSphere 5.x版本開始致力于可插拔架構,項目的功能組件能夠靈活地以可插拔的方式進行擴展。目前,數據分片、讀寫分離、多數據副本、數據加密、影子庫壓測等功能,以及MySQL、PostgreSQL、SQL Server、Oracle等SQL與協議的支持,均通過插件的方式植入項目,這樣開發者能夠像使用積木一樣定制屬于自己的獨特系統。
ShardingSphere已于2020年4月16日成為Apache軟件基金會的頂級項目。
1.ShardingSphere-JDBC
定位為輕量級Java框架,在Java的JDBC層提供額外服務。它使用客戶端直連數據庫,以jar包形式提供服務,無須額外部署和依賴,可理解為增強版的JDBC驅動,完全兼容JDBC和各種ORM框架,如圖2-12所示。
(1)適用于任何基于JDBC的ORM框架,如JPA、Hibernate、MyBatis、Spring JDBC Template或直接使用JDBC。
(2)支持任何第三方的數據庫連接池,如DBCP、C3P0、BoneCP、Druid、HikariCP等。
(3)支持任意實現JDBC規范的數據庫。目前支持MySQL、Oracle、SQL Server、PostgreSQL以及任何遵循SQL92標準的數據庫。

圖2-12 ShardingSphere-JDBC方案
2.ShardingSphere-Proxy
如圖2-13所示,定位為透明化的數據庫代理端,提供封裝了數據庫二進制協議的服務端版本,用于完成對異構語言的支持。目前提供MySQL和PostgreSQL版本,它可以使用任何兼容MySQL/PostgreSQL協議的訪問客戶端(如MySQL Command Client、MySQL Workbench、Navicat等)操作數據,對DBA更加友好。
(1)向應用程序完全透明,可直接當作MySQL/PostgreSQL使用。
(2)適用于任何兼容MySQL/PostgreSQL協議的客戶端。

圖2-13 ShardingSphere-Proxy方案
3.ShardingSphere-Sidecar
如圖2-14所示,定位為Kubernetes的云原生數據庫代理,以Sidecar的形式代理所有對數據庫的訪問。通過無中心、零侵入的方案提供與數據庫交互的嚙合層,即Database Mesh,又可稱為數據庫網格。

圖2-14 ShardingSphere-Sidecar方案
Database Mesh的關注重點在于如何將分布式的數據訪問應用與數據庫有機串聯起來,它更加關注的是交互,是將雜亂無章的應用與數據庫之間的交互進行有效的梳理。使用Database Mesh,訪問數據庫的應用和數據庫終將形成一個巨大的網格體系,應用和數據庫只需在網格體系中對號入座即可,它們都是被嚙合層所治理的對象。
4.如何選擇
ShardingSphere-JDBC采用無中心化架構,適用于Java開發的高性能輕量級OLTP應用。
ShardingSphere-Proxy提供靜態入口以及異構語言的支持,適用于OLAP應用以及對分片數據庫進行管理和運維的場景。
Apache ShardingSphere是多接入端共同組成的生態圈。通過混合使用ShardingSphere-JDBC和ShardingSphere-Proxy,并采用同一注冊中心統一配置分片策略,能夠靈活地搭建適用于各種場景的應用系統,使得架構師更加自由地調整適合于當前業務的最佳系統架構。
這三種方案的具體選擇策略如表2-2所示。
表2-2 ShardingSphere三種方案選擇表

對電商管家來說,數據庫負載為典型的OLTP類型,應用采用Java語言編寫,經評估最終選擇了ShardingSphere-JDBC方案。
2.2.6 分布式數據庫方案——通過數據庫解決所有問題
雖然數據分片解決了容量問題,以及部分性能、可用性以及單點備份恢復等問題,但分散的架構在獲得了收益的同時,也引入了新的挑戰。
(1)分布式數據庫需要具備容錯處理能力。
單機是一個由底層系統包括硬件、BIOS和操作系統組成的整體。從集中式數據庫角度看,對這臺機器的狀態無須過分關注。換句話說,在單機環境,操作系統不會向數據庫軟件報告有一個CPU或內存被拔出插槽,當然應用也不需要設計成在這種情況下還要繼續工作。
但是,分布式數據庫就需要處理部分節點失效,有以下兩點原因。
① 傳統集中式數據庫部署的服務器通常是IBM小型機,IBM小型機可靠性高,分布式數環境所用的x86服務器可靠性稍差,需組成集群提升可靠性。
② 分布式數據庫由多個節點組成,單個節點發生故障的情況時有發生。出現故障時,并沒有分布式操作系統幫助處理,這是采用分布式數據庫的最大挑戰。
(2)運行在集中式數據庫中的SQL語句,在分布式環境中不一定能夠正確運行,例如,分頁、排序、聚合分組等操作的不正確處理,這是第二個挑戰。
(3)跨庫的分布式事務也是要面對的棘手問題。在這種場景下,需要保持事務的強一致性。基于XA的分布式事務由于在并發度高的場景中性能無法滿足需要,并未被大規模使用,它們大多采用最終一致性的柔性事務代替強一致事務。
上述挑戰,正是分布式數據庫所要解決的,分布式數據庫這個概念也因此破繭而出了!
在本章前言中提到了Google的Spanner、阿里巴巴的OceanBase和PolarDB、AWS的Aurora等分布式數據庫,它們的架構既有相似之處,也有很多差異,本節將給出一種新的分布數據庫架構。
如圖2-15所示,這是一種理想中的分布式數據庫原型圖。它首先是一個完備的數據庫,支持分布式事務,可線性擴展,具備高可用能力,具體功能如下。
(1)數據按照某種規則(例如哈希算法)拆分到多個分片中,完成水平分庫。
(2)數據分片數能夠按照需求變化進行動態伸縮,支持橫向與縱向擴展。
(3)每個數據分片包括一個主庫和多個從庫,從庫越多,就意味著當少量從庫數據丟失或宕機以后,整個系統對外提供的服務不受影響。
(4)取決于對一致性的要求,主庫和從庫之間可以采用Paxos/raft協議保證強一致性。
(5)應用程序以標準ANSI SQL語句訪問中間件集群(圖2-15中為DBProxy集群)。
(6)DBProxy對收到的SQL請求進行處理,將請求路由到一個或者多個數據庫分片中,對分片返回的結果進行再次加工后,最終返回給應用程序。
(7)分布式數據庫集群由專門的節點管理(圖2-15中為manager節點),負責集群管理、元數據管理、自動化切換等功能,從而無須DBA在集群管理上耗費額外精力。
(8)分布式事務由專門的全局事務管理節點負責(圖2-15中為GTM),控制分布式事務執行。

圖2-15 分布式數據庫原型方案
迄今為止,一個分布式數據庫原型成功建立,2.3節將正式進入本章主題:金融級分布式數據庫——GoldenDB。