分布式事務原理與實踐


事務簡介

事務的核心是鎖和並發,采用同步控制的方式保證並發的情況下性能盡可能高,且容易理解。這種方式的優勢是方便理解;它的劣勢是性能比較低。
計算機可以簡單的理解為一個標准的打字機,盡管看起來計算機可以並行處理很多事情,但實際上每個CPU單位時間內只能做一件事,要么讀取數據、要么計算數據、要么寫入數據,所有的任務都可以看成這三件事的集合。計算機的這種特性引出了一個問題:當多個人去讀、算、寫操作時,如果不加訪問控制,系統勢必會產生沖突。而事務相當於在讀、算、寫操作之外增加了同步的模塊,進而保證只有一個線程進入事務當中,而其他線程不會進入。

單個事務單元

事務的四大特性分別是:原子型、一致性、隔離性和持久性。其中原子性指的是事務中包含的所有操作要么全做,要么全不做;一致性是指在事務開始以前,數據庫處於一致性的狀態,事務結束后,數據庫也必須處於一致性的狀態;隔離性要求系統必須保證事務不受其他並發執行的事務的影響;持久性是指一個事務一旦成功完成,它對數據庫的改變必須是永久的,即使是在系統遇到故障的情況下也不會丟失,數據的重要性決定了事務的持久性的重要。

事務單元是通過Begin-Traction,然后Commit(Begin-Traction、Commit和Rollback之間所有針對數據的寫入、讀取的操作都應該添加同步訪問),Begin和Commit之間就是一個同步的事務單元。例如,Bob給Smith 100塊錢就是一個事務單元,這個過程中有很多步操作,具體如上圖所示;但對業務來說,僅是一個轉賬的操作。

一組事務單元

當三個賬戶都在進行轉賬操作時,每個操作都涉及Smith賬戶,所有的事務都會排隊,形成一組事務單元。
事務單元之間的Happen-Before關系中的四種可能性:讀寫、寫讀、讀讀、寫寫。所有事務之間的關系都可以抽象成這四種之一,來對應現在所有的業務邏輯處理。在此基礎之上,需要用最快的速度處理多個事務單元之間的關系,同時還能保障這四種操作的邏輯順序。

單個事務單元的其他例子

除了轉賬操作是事務單元外,諸如商品要建立一個基於GMT_Modified的索引、從數據庫中讀取一行記錄、向數據庫中寫入一行記錄,同時更新這行記錄的所有索引、刪除整張表等都是一個事務單元。

事務單元的實現方式

Two Phase Lock(2PL)是數據庫中非常重要的一個概念。數據庫操作Insert、Update、Delete都是先讀再寫的操作,例如Insert操作是先讀取數據,讀取之后判讀數據是否存在,如果不存在,則寫入該數據,如果數據存在,則返回錯誤。假設在該場景下沒有讀操作,只是單純寫入數據,則數據本身並沒有事務操作,Delete、Update操作與之類似。數據庫利用這些操作的特性,在每一次查詢過程中,只要查到數據,就會在該數據上加鎖。理論上,所有被讀取的數據都已加鎖,不會再被其他人讀到,也就是說對數據進行的中間操作狀態對所有人都不可見,當所有中間狀態完成后,提交操作時,解開鎖,此時數據對所有系統可見,例如在轉賬過程中,所有人只能看到兩種狀態:開始時,A有錢,B沒錢;結束時,B有錢,A沒錢,而中間A減掉錢,B尚未加上錢的狀態被鎖隱藏掉了,這個操作就是數據庫中處理事務的最標准的方式。如上圖所示:事務中的Trx2(JoeLock)與其他事務不相關,因此可以並行執行;Trx1需要Lock兩個數據Boblock和Smithlock,而Trx3同樣需要Lock這兩個數據,因此Trx3必須等待,且等待在Boblock上;Joe事務會先結束,Trx3會等到Trx1完成后才會開始。

處理事務的常見方法

處理事務的常見方法有排隊法、排他鎖、讀寫鎖、MVCC等方式,下面來一一解析。

排隊法

事務處理中最重要也是最簡單的方案是排隊法,單線程地處理一堆數據。在Redis中,如果數據全部在內存中,則單線程處理所有Put、Get操作效率最高。這是因為多線程本質是CPU模擬多個線程,這種模擬是以上下文切換為代價,而對於內存的數據庫來說,沒有上下文切換時效率最高。因此,單個CPU綁定一塊內存的數據,針對這塊數據做多次讀寫操作時都是在單個CPU上完成的,單線程處理方式在內存的情況是效率是最優的。
那么什么時候事務需要用到多線程呢?這個問題的本質取決於下層所使用的存儲,如果是內存操作,則可以動態地申請和銷毀內存塊;而磁盤的IOPS很低,但吞吐量很高。如果一個場景涉及多次讀寫操作,單線程可以很高的效率對於內存進行讀寫操作;但是,由於磁盤的IOPS僅為內存的幾千分之一,如果依舊用操作內存的方式操作磁盤,那系統的整體性能將會很低,這意味着必須將大量的讀寫操作聚合成一個Batch后再提交時才能達到較好的性能。而將大量請求攢到一起的方式一是異步,也就是請求本身和線程不綁定,線程可以不Block(本質來說還是一種多線程的方式),處理完一個線程后再處理其他線程。這種做法的核心是將大量不同的請求提交到一個Buff

設備中,多線程或異步非常常見,在設計系統時,面對磁盤、網絡、SSD等慢速設備必須考慮使用多線程。

排他鎖

有些場景不適合用單線程操作,可以利用排他鎖的方式來快速隔離並發讀寫事務。數據庫中有一些事務單元是共享的,如圖中的事務單元1是共享的,事務單元2/3共享數據;針對事務單元2/3共享數據的所有讀寫Block住,事務單元1單獨用一個鎖來控制,用這種方式完成系統的訪問控制。

讀寫鎖

如果是一個只讀的事務,例如只對數據進行查詢操作,在該過程中數據一定不被修改,因此多個查詢操作可以並行執行,因此一種針對讀讀場景的優化自然而然產生——讀寫鎖。讀寫鎖的核心是在多次讀的操作中,同時允許多個讀者來訪問共享資源,提高並發性。

MVCC

在最初的數據庫事務實現中是不存在MVCC的,它是Oracle在八十年代新加的功能,本質是Copy On Write,也就是每次寫都是以重新開始一個新的版本的方式寫入數據,因此,數據庫中也就包含了之前的所有版本。在數據讀的過程中,先申請一個版本號,如果該版本號小於正在寫入的版本號,則數據一定可以查詢到,無需等到新版本完全寫完即可返回查詢結果。這種方式可以在讀讀不阻塞的前提下,實現讀寫/寫讀不阻塞,盡可能保證所有的讀操作並行,而寫操作串行。

事務的調優原則

事務的調優的思路是在不影響業務應用的前提下:
第一,盡可能減少鎖的覆蓋范圍,例如Myisam表鎖到Innodb的行鎖就是一個減少鎖覆蓋范圍的過程;對於原位鎖(排他鎖、讀寫鎖等)可變為MVCC多版本(本質仍然是減少鎖的范圍)。
第二,增加鎖上可並行的線程數,例如讀鎖和寫鎖的分離,允許並行讀取數據。
第三,選擇正確鎖類型,其中悲觀鎖適合並發爭搶比較嚴重的場景;樂觀鎖適合並發爭搶不太嚴重的場景。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM