java分布式事務
1. Java分布式系統處理分布式事務有哪些經典解決方
當我們在生產線上用一台伺服器來提供數據服務的時候,我會遇到如下的兩個問題:
1)一台伺服器的性能不足以提供足夠的能力服務於所有的網路請求。
2)我們總是害怕我們的這台伺服器停機,造成服務不可用或是數據丟失。
於是我們不得不對我們的伺服器進行擴展,加入更多的機器來分擔性能上的問題,以及來解決單點故障問題。 通常,我們會通過兩種手段來擴展我們的數據服務:
1)數據分區:就是把數據分塊放在不同的伺服器上(如:uid % 16,一致性哈希等)。
2)數據鏡像:讓所有的伺服器都有相同的數據,提供相當的服務。
對於第一種情況,我們無法解決數據丟失的問題,單台伺服器出問題時,會有部分數據丟失。所以,數據服務的高可用性只能通過第二種方法來完成——數據的冗餘存儲(一般工業界認為比較安全的備份數應該是3份,如:Hadoop和Dynamo)。 但是,加入更多的機器,會讓我們的數據服務變得很復雜,尤其是跨伺服器的事務處理,也就是跨伺服器的數據一致性。這個是一個很難的問題。 讓我們用最經典的Use Case:「A帳號向B帳號匯錢」來說明一下,熟悉RDBMS事務的都知道從帳號A到帳號B需要6個操作:
從A帳號中把余額讀出來。
對A帳號做減法操作。
把結果寫回A帳號中。
從B帳號中把余額讀出來。
對B帳號做加法操作。
把結果寫回B帳號中。
為了數據的一致性,這6件事,要麼都成功做完,要麼都不成功,而且這個操作的過程中,對A、B帳號的其它訪問必需鎖死,所謂鎖死就是要排除其它的讀寫操作,不然會有臟數據的問題,這就是事務。那麼,我們在加入了更多的機器後,這個事情會變得復雜起來:
1)在數據分區的方案中:如果A帳號和B帳號的數據不在同一台伺服器上怎麼辦?我們需要一個跨機器的事務處理。也就是說,如果A的扣錢成功了,但B的加錢不成功,我們還要把A的操作給回滾回去。這在跨機器的情況下,就變得比較復雜了。
2)在數據鏡像的方案中:A帳號和B帳號間的匯款是可以在一台機器上完成的,但是別忘了我們有多台機器存在A帳號和B帳號的副本。如果對A帳號的匯錢有兩個並發操作(要匯給B和C),這兩個操作發生在不同的兩台伺服器上怎麼辦?也就是說,在數據鏡像中,在不同的伺服器上對同一個數據的寫操作怎麼保證其一致性,保證數據不沖突?
同時,我們還要考慮性能的因素,如果不考慮性能的話,事務得到保證並不困難,系統慢一點就行了。除了考慮性能外,我們還要考慮可用性,也就是說,一台機器沒了,數據不丟失,服務可由別的機器繼續提供。 於是,我們需要重點考慮下面的這么幾個情況:
1)容災:數據不丟、節點的Failover
2)數據的一致性:事務處理
3)性能:吞吐量 、 響應時間
前面說過,要解決數據不丟,只能通過數據冗餘的方法,就算是數據分區,每個區也需要進行數據冗餘處理。這就是數據副本:當出現某個節點的數據丟失時可以從副本讀到,數據副本是分布式系統解決數據丟失異常的唯一手段。所以,在這篇文章中,簡單起見,我們只討論在數據冗餘情況下考慮數據的一致性和性能的問題。簡單說來:
1)要想讓數據有高可用性,就得寫多份數據。
2)寫多份的問題會導致數據一致性的問題。
3)數據一致性的問題又會引發性能問題
這就是軟體開發,按下了葫蘆起了瓢。
一致性模型
說起數據一致性來說,簡單說有三種類型(當然,如果細分的話,還有很多一致性模型,如:順序一致性,FIFO一致性,會話一致性,單讀一致性,單寫一致性,但為了本文的簡單易讀,我只說下面三種):
1)Weak 弱一致性:當你寫入一個新值後,讀操作在數據副本上可能讀出來,也可能讀不出來。比如:某些cache系統,網路游戲其它玩家的數據和你沒什麼關系,VOIP這樣的系統,或是網路搜索引擎(呵呵)。
2)Eventually 最終一致性:當你寫入一個新值後,有可能讀不出來,但在某個時間窗口之後保證最終能讀出來。比如:DNS,電子郵件、Amazon S3,Google搜索引擎這樣的系統。
3)Strong 強一致性:新的數據一旦寫入,在任意副本任意時刻都能讀到新值。比如:文件系統,RDBMS,Azure Table都是強一致性的。
從這三種一致型的模型上來說,我們可以看到,Weak和Eventually一般來說是非同步冗餘的,而Strong一般來說是同步冗餘的,非同步的通常意味著更好的性能,但也意味著更復雜的狀態控制。同步意味著簡單,但也意味著性能下降。 好,讓我們由淺入深,一步一步地來看有哪些技術:
Master-Slave
首先是Master-Slave結構,對於這種加構,Slave一般是Master的備份。在這樣的系統中,一般是如下設計的:
1)讀寫請求都由Master負責。
2)寫請求寫到Master上後,由Master同步到Slave上。
從Master同步到Slave上,你可以使用非同步,也可以使用同步,可以使用Master來push,也可以使用Slave來pull。 通常來說是Slave來周期性的pull,所以,是最終一致性。這個設計的問題是,如果Master在pull周期內垮掉了,那麼會導致這個時間片內的數據丟失。如果你不想讓數據丟掉,Slave只能成為Read-Only的方式等Master恢復。
當然,如果你可以容忍數據丟掉的話,你可以馬上讓Slave代替Master工作(對於只負責計算的節點來說,沒有數據一致性和數據丟失的問題,Master-Slave的方式就可以解決單點問題了) 當然,Master Slave也可以是強一致性的, 比如:當我們寫Master的時候,Master負責先寫自己,等成功後,再寫Slave,兩者都成功後返回成功,整個過程是同步的,如果寫Slave失敗了,那麼兩種方法,一種是標記Slave不可用報錯並繼續服務(等Slave恢復後同步Master的數據,可以有多個Slave,這樣少一個,還有備份,就像前面說的寫三份那樣),另一種是回滾自己並返回寫失敗。(註:一般不先寫Slave,因為如果寫Master自己失敗後,還要回滾Slave,此時如果回滾Slave失敗,就得手工訂正數據了)你可以看到,如果Master-Slave需要做成強一致性有多復雜。
Master-Master
Master-Master,又叫Multi-master,是指一個系統存在兩個或多個Master,每個Master都提供read-write服務。這個模型是Master-Slave的加強版,數據間同步一般是通過Master間的非同步完成,所以是最終一致性。 Master-Master的好處是,一台Master掛了,別的Master可以正常做讀寫服務,他和Master-Slave一樣,當數據沒有被復制到別的Master上時,數據會丟失。很多資料庫都支持Master-Master的Replication的機制。
另外,如果多個Master對同一個數據進行修改的時候,這個模型的惡夢就出現了——對數據間的沖突合並,這並不是一件容易的事情。看看Dynamo的Vector Clock的設計(記錄數據的版本號和修改者)就知道這個事並不那麼簡單,而且Dynamo對數據沖突這個事是交給用戶自己搞的。就像我們的SVN源碼沖突一樣,對於同一行代碼的沖突,只能交給開發者自己來處理。(在本文後後面會討論一下Dynamo的Vector Clock)
Two/Three Phase Commit
這個協議的縮寫又叫2PC,中文叫兩階段提交。在分布式系統中,每個節點雖然可以知曉自己的操作時成功或者失敗,卻無法知道其他節點的操作的成功或失敗。當一個事務跨越多個節點時,為了保持事務的ACID特性,需要引入一個作為協調者的組件來統一掌控所有節點(稱作參與者)的操作結果並最終指示這些節點是否要把操作結果進行真正的提交(比如將更新後的數據寫入磁碟等等)。
2. 資料庫架構選型與落地,看這篇就夠了
隨著時間和業務的發展,資料庫中的數據量增長是不可控的,庫和表中的數據會越來越大,隨之帶來的是更高的 磁碟 、 IO 、 系統開銷 ,甚至 性能 上的瓶頸,而單台伺服器的 資源終究是有限 的。
因此在面對業務擴張過程中,應用程序對資料庫系統的 健壯性 , 安全性 , 擴展性 提出了更高的要求。
以下,我從資料庫架構、選型與落地來讓大家入門。
資料庫會面臨什麼樣的挑戰呢?
業務剛開始我們只用單機資料庫就夠了,但隨著業務增長,數據規模和用戶規模上升,這個時候資料庫會面臨IO瓶頸、存儲瓶頸、可用性、安全性問題。
為了解決上述的各種問題,資料庫衍生了出不同的架構來解決不同的場景需求。
將資料庫的寫操作和讀操作分離,主庫接收寫請求,使用多個從庫副本負責讀請求,從庫和主庫同步更新數據保持數據一致性,從庫可以水平擴展,用於面對讀請求的增加。
這個模式也就是常說的讀寫分離,針對的是小規模數據,而且存在大量讀操作的場景。
因為主從的數據是相同的,一旦主庫宕機的時候,從庫可以 切換為主庫提供寫入 ,所以這個架構也可以提高資料庫系統的 安全性 和 可用性 ;
優點:
缺點:
在資料庫遇到 IO瓶頸 過程中,如果IO集中在某一塊的業務中,這個時候可以考慮的就是垂直分庫,將熱點業務拆分出去,避免由 熱點業務 的 密集IO請求 影響了其他正常業務,所以垂直分庫也叫 業務分庫 。
優點:
缺點:
在資料庫遇到存儲瓶頸的時候,由於數據量過大造成索引性能下降。
這個時候可以考慮將數據做水平拆分,針對數據量巨大的單張表,按照某種規則,切分到多張表裡面去。
但是這些表還是在同一個庫中,所以庫級別的資料庫操作還是有IO瓶頸(單個伺服器的IO有上限)。
所以水平分表主要還是針對 數據量較大 ,整體業務 請求量較低 的場景。
優點:
缺點:
四、分庫分表
在資料庫遇到存儲瓶頸和IO瓶頸的時候,數據量過大造成索引性能下降,加上同一時間需要處理大規模的業務請求,這個時候單庫的IO上限會限制處理效率。
所以需要將單張表的數據切分到多個伺服器上去,每個伺服器具有相應的庫與表,只是表中數據集合不同。
分庫分表能夠有效地緩解單機和單庫的 性能瓶頸和壓力 ,突破IO、連接數、硬體資源等的瓶頸。
優點:
缺點:
註:分庫還是分表核心關鍵是有沒有IO瓶頸 。
分片方式都有什麼呢?
RANGE(范圍分片)
將業務表中的某個 關鍵欄位排序 後,按照順序從0到10000一個表,10001到20000一個表。最常見的就是 按照時間切分 (月表、年表)。
比如將6個月前,甚至一年前的數據切出去放到另外的一張表,因為隨著時間流逝,這些表的數據被查詢的概率變小,銀行的交易記錄多數是採用這種方式。
優點:
缺點:
HASH(哈希分片)
將訂單作為主表,然後將其相關的業務表作為附表,取用戶id然後 hash取模 ,分配到不同的數據表或者資料庫上。
優點:
缺點:
講到這里,我們已經知道資料庫有哪些架構,解決的是哪些問題,因此, 我們在日常設計中需要根據數據的特點,數據的傾向性,數據的安全性等來選擇不同的架構 。
那麼,我們應該如何選擇資料庫架構呢?
雖然把上面的架構全部組合在一起可以形成一個強大的高可用,高負載的資料庫系統,但是架構選擇合適才是最重要的。
混合架構雖然能夠解決所有的場景的問題,但是也會面臨更多的挑戰,你以為的完美架構,背後其實有著更多的坑。
1、對事務支持
分庫分表後(無論是垂直還是水平拆分),就成了分布式事務了,如果依賴資料庫本身的分布式事務管理功能去執行事務,將付出高昂的性能代價(XA事務);如果由應用程序去協助控制,形成程序邏輯上的事務,又會造成編程方面的負擔(TCC、SAGA)。
2、多庫結果集合並 (group by,order by)
由於數據分布於不同的資料庫中,無法直接對其做分頁、分組、排序等操作,一般應對這種多庫結果集合並的查詢業務都需要採用數據清洗、同步等其他手段處理(TIDB、KUDU等)。
3、數據延遲
主從架構下的多副本機制和水平分庫後的聚合庫都會存在主數據和副本數據之間的延遲問題。
4、跨庫join
分庫分表後表之間的關聯操作將受到限制,我們無法join位於不同分庫的表(垂直),也無法join分表粒度不同的表(水平), 結果原本一次查詢就能夠完成的業務,可能需要多次查詢才能完成。
5、分片擴容
水平分片之後,一旦需要做擴容時。需要將對應的數據做一次遷移,成本代價都極高的。
6、ID生成
分庫分表後由於資料庫獨立,原有的基於資料庫自增ID將無法再使用,這個時候需要採用其他外部的ID生成方案。
一、應用層依賴類(JDBC)
這類分庫分表中間件的特點就是和應用強耦合,需要應用顯示依賴相應的jar包(以Java為例),比如知名的TDDL、當當開源的 sharding-jdbc 、蘑菇街的TSharding等。
此類中間件的基本思路就是重新實現JDBC的API,通過重新實現 DataSource 、 PrepareStatement 等操作資料庫的介面,讓應用層在 基本 不改變業務代碼的情況下透明地實現分庫分表的能力。
中間件給上層應用提供熟悉的JDBC API,內部通過 sql解析 、 sql重寫 、 sql路由 等一系列的准備工作獲取真正可執行的sql,然後底層再按照傳統的方法(比如資料庫連接池)獲取物理連接來執行sql,最後把數據 結果合並 處理成ResultSet返回給應用層。
優點
缺點
二、中間層代理類(Proxy)
這類分庫分表中間件的核心原理是在應用和資料庫的連接之間搭起一個 代理層 ,上層應用以 標準的MySQL協議 來連接代理層,然後代理層負責 轉發請求 到底層的MySQL物理實例,這種方式對應用只有一個要求,就是只要用MySQL協議來通信即可。
所以用MySQL Navicat這種純的客戶端都可以直接連接你的分布式資料庫,自然也天然 支持所有的編程語言 。
在技術實現上除了和應用層依賴類中間件基本相似外,代理類的分庫分表產品必須實現標準的MySQL協議,某種意義上講資料庫代理層轉發的就是MySQL協議請求,就像Nginx轉發的是Http協議請求。
比較有代表性的產品有開創性質的Amoeba、阿里開源的Cobar、社區發展比較好的 Mycat (基於Cobar開發)等。
優點
缺點
JDBC方案 :無中心化架構,兼容市面上大多數關系型資料庫,適用於開發高性能的輕量級 OLTP 應用(面向前台)。
Proxy方案 :提供靜態入口以及異構語言的支持,適用於 OLAP 應用(面向後台)以及對分片資料庫進行管理和運維的場景。
混合方案 :在大型復雜系統中存在面向C端用戶的前台應用,也有面向企業分析的後台應用,這個時候就可以採用混合模式。
JDBC 採用無中心化架構,適用於 Java 開發的高性能的輕量級 OLTP 應用;Proxy 提供靜態入口以及異構語言的支持,適用於 OLAP 應用以及對分片資料庫進行管理和運維的場景。
ShardingSphere是一套開源的分布式資料庫中間件解決方案組成的生態圈,它由 Sharding-JDBC 、 Sharding-Proxy 和 Sharding-Sidecar (計劃中)這3款相互獨立的產品組成,他們均提供標准化的數據分片、分布式事務和資料庫治理功能,可適用於如Java同構、異構語言、容器、雲原生等各種多樣化的應用場景。
ShardingSphere提供的核心功能:
Sharding-Proxy
定位為透明化的 資料庫代理端 ,提供封裝了 資料庫二進制協議的服務端版本 ,用於完成對 異構語言的支持 。
目前已提供MySQL版本,它可以使用 任何兼容MySQL協議的訪問客戶端 (如:MySQL Command Client, MySQL Workbench, Navicat等)操作數據,對DBA更加友好。
向 應用程序完全透明 ,可直接當做MySQL使用。
適用於任何兼容MySQL協議的客戶端。
Sharding-JDBC
定位為 輕量級Java框架 ,在Java的JDBC層提供的額外服務。 它使用客戶端直連資料庫,以jar包形式提供服務,無需額外部署和依賴,可理解為 增強版的JDBC驅動,完全兼容JDBC和各種ORM框架 。
以電商SaaS系統為例,前台應用採用Sharding-JDBC,根據業務場景的差異主要分為三種方案。
分庫(用戶)
問題解析:頭部企業日活高並發高,單獨分庫避免干擾其他企業用戶,用戶數據的增長緩慢可以不分表。
拆分維度:企業ID分庫
拆分策略:頭部企業單獨庫、非頭部企業一個庫
分庫分表(訂單)
問題解析:訂單數據增長速度較快,在分庫之餘需要分表。
拆分維度:企業ID分庫、用戶ID分表
拆分策略:頭部企業單獨庫、非頭部企業一個庫,分庫之後用戶ID取模拆分表
單庫分表(附件)
問題解析:附件數據特點是並發量不大,只需要解決數據增長問題,所以單庫IO足以支撐的情況下分表即可。
拆分維度:用戶ID分表
拆分策略:用戶ID取模分表
問題一:分布式事務
分布式事務過於復雜也是分布式系統最難處理的問題,由於篇幅有限,後續會開篇專講這一塊內容。
問題二:分布式ID
問題三:跨片查詢
舉個例子,以用戶id分片之後,需要根據企業id查詢企業所有用戶信息。
sharding針對跨片查詢也是能夠支持的,本質上sharding的跨片查詢是採用同時查詢多個分片的數據,然後聚合結果返回,這個方式對資源耗費比較大,特別是對資料庫連接資源的消耗。
假設分4個資料庫,8個表,則sharding會同時發出32個SQL去查詢。一下子消耗掉了32個連接;
特別是針對單庫分表的情況要注意,假設單庫分64個表,則要消耗64個連接。如果我們部署了2個節點,這個時候兩個節點同時查詢的話,就會遇到資料庫連接數上限問題(mysql默認100連接數)
問題四:分片擴容
隨著數據增長,每個片區的數據也會達到瓶頸,這個時候需要將原有的分片數量進行增加。由於增加了片區,原先的hash規則也跟著變化,造成了需要將舊數據做遷移。
假設原先1個億的數據,hash分64個表,現在增長到50億的數據,需要擴容到128個表,一旦擴容就需要將這50億的數據做一次遷移,遷移成本是無法想像的。
問題五:一致性哈希
首先,求出每個 伺服器的hash值 ,將其配置到一個 0~2^n 的圓環上 (n通常取32)
其次,用同樣的方法求出待 存儲對象的主鍵 hash值 ,也將其配置到這個圓環上。
然後,從數據映射到的位置開始順時針查找,將數據分布到找到的第一個伺服器節點上。
一致性hash的優點在於加入和刪除節點時只會影響到在哈希環中相鄰的節點,而對其他節點沒有影響。
所以使用一致性哈希在集群擴容過程中可以減少數據的遷移。
好了,這次分享到這里,我們日常的實踐可能只會用到其中一種方案,但它不是資料庫架構的全貌,打開技術視野,才能更好地把存儲工具利用起來。
老規矩,一鍵三連,日入兩千,點贊在看,年薪百萬!
本文作者:Jensen
7年Java老兵,小米主題設計師,手機輸入法設計師,ProcessOn特邀講師。
曾涉獵航空、電信、IoT、垂直電商產品研發,現就職於某知名電商企業。
技術公眾號 【架構師修行錄】 號主,專注於分享日常架構、技術、職場干貨,Java Goals:架構師。
交個朋友,一起成長!
