8.1 交易 ID
它被指定為 XID 或 TxID(如果有區別,請告訴我)。時間戳可以用作 TxID,如果我們想將所有操作恢復到某個時間點,它可以發揮作用。如果時間戳不夠精細,就會出現問題——然後事務可以獲得相同的 ID。
因此,最可靠的選擇是生成唯一的 UUID prod ID。在 Python 中這很容易:
>>> import uuid
>>> str(uuid.uuid4())
'f50ec0b7-f960-400d-91f0-c42a6d44e3d0'
>>> str(uuid.uuid4())
'd15bed89-c0a5-4a72-98d9-5507ea7bc0ba'
還有一個選項可以對一組交易定義數據進行哈希處理,並將該哈希值用作 TxID。
8.2 重試
如果我們知道某個函數或程序是冪等的,那麼這意味著我們可以而且應該嘗試在出現錯誤時重複調用它。我們只需要為某些操作會出錯這一事實做好準備 - 鑑於現代應用程序分佈在網絡和硬件上,錯誤不應被視為例外,而應被視為常態。該錯誤可能由於服務器崩潰、網絡錯誤、遠程應用程序擁塞而發生。我們的應用程序應該如何表現?沒錯,嘗試重複操作。
由於一段代碼可以說的不僅僅是一整頁的話,讓我們用一個例子來理解簡單的重試機制應該如何理想地工作。我將使用 Tenacity 庫對此進行演示(它設計得非常好,即使您不打算使用它,該示例也應該向您展示如何設計循環機制):
import logging
import random
import sys
from tenacity import retry, stop_after_attempt, stop_after_delay, wait_exponential, retry_if_exception_type, before_log
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
logger = logging.getLogger(__name__)
@retry(
stop=(stop_after_delay(10) | stop_after_attempt(5)),
wait=wait_exponential(multiplier=1, min=4, max=10),
retry=retry_if_exception_type(IOError),
before=before_log(logger, logging.DEBUG)
)
def do_something_unreliable():
if random.randint(0, 10) > 1:
raise IOError("Broken sauce, everything is hosed!!!111one")
else:
return "Awesome sauce!"
print(do_something_unreliable.retry.statistics)
> 為了以防萬一,我會說:\@retry(...) 是一種特殊的 Python 語法,稱為“裝飾器”。它只是一個 retry(...) 函數,它包裝了另一個函數並在它執行之前或之後做一些事情。
正如我們所見,可以創造性地設計重試:
- 您可以按時間(10 秒)或嘗試次數 (5) 限制嘗試。
- 可以是指數的(即 2 ** 一些遞增的數字 n )。或以其他方式(例如,固定的)增加不同嘗試之間的時間。指數變體稱為“擁塞崩潰”。
- 您只能針對某些類型的錯誤 (IOError) 進行重試。
- 日誌中的一些特殊條目可以在重試嘗試之前或完成。
現在我們已經完成了年輕戰士課程並了解了在應用程序端處理事務所需的基本構建塊,讓我們熟悉兩種允許我們在分佈式系統中實現事務的方法。
8.3 交易愛好者的高級工具
我只會給出相當籠統的定義,因為這個主題值得單獨寫一篇大文章。
兩階段提交 (2pc)。2pc 有兩個階段:準備階段和提交階段。在準備階段,將要求所有微服務為一些可以原子完成的數據更改做準備。一旦它們都準備就緒,提交階段將進行實際更改。為了協調這個過程,需要一個全局協調器,它鎖定必要的對象——也就是說,在協調器解鎖它們之前,它們變得不可訪問以進行更改。如果特定的微服務沒有準備好進行更改(例如,不響應),協調器將中止事務並開始回滾過程。
為什麼這個協議好?它提供原子性。此外,它還保證了寫入和讀取時的隔離。這意味著在協調器提交更改之前,對一個事務的更改對其他事務不可見。但是這些屬性也有一個缺點:由於這個協議是同步的(阻塞的),它會減慢系統的速度(儘管 RPC 調用本身很慢)。再次,存在相互阻塞的危險。
傳奇。在此模式中,分佈式事務由跨所有關聯微服務的異步本地事務執行。微服務通過事件總線相互通信。如果任何微服務未能完成其本地事務,其他微服務將執行補償事務以回滾更改。
Saga 的優點是沒有對像被阻塞。但當然也有缺點。
Saga 很難調試,尤其是當涉及到很多微服務時。Saga 模式的另一個缺點是它缺乏讀取隔離。也就是說,如果ACID中表示的屬性對我們很重要,那麼Saga就不太適合我們。
我們從這兩種技術的描述中看到了什麼?事實上,在分佈式系統中,原子性和隔離的責任在於應用程序。使用不提供 ACID 保證的數據庫時也會發生同樣的事情。也就是說,衝突解決、回滾、提交和釋放空間等事情落在了開發人員的肩上。
8.4 我怎麼知道什麼時候需要 ACID 保證?
當一組特定的用戶或進程很可能同時處理同一數據時。
很抱歉很平庸,但一個典型的例子是金融交易。
當交易執行的順序很重要時。
想像一下,您的公司即將從 FunnyYellowChat 信使切換到 FunnyRedChat 信使,因為 FunnyRedChat 允許您發送 gif,但 FunnyYellowChat 不能。但是,您不僅僅是在更換 Messenger——您是在將貴公司的信件從一個 Messenger 遷移到另一個 Messenger。你這樣做是因為你的程序員懶得在某個地方集中記錄程序和過程,而是他們在 Messenger 的不同頻道中發布了所有內容。是的,您的銷售人員在同一個地方公佈了談判和協議的細節。簡而言之,您公司的整個生命都在那裡,並且由於沒有人有時間將整個事情轉移到文檔服務,而且即時通訊工具的搜索效果很好,您決定不清除廢墟而是簡單地複制所有消息到新位置。消息的順序很重要
順便說一下,對於 Messenger 中的信件,順序通常很重要,但是當兩個人同時在同一個聊天中寫東西時,一般來說誰的消息先出現就不那麼重要了。因此,對於這種特定情況,不需要 ACID。
另一個可能的例子是生物信息學。我完全不明白這一點,但我認為在破譯人類基因組時順序很重要。但是,我聽說生物信息學家通常會使用他們的一些工具來處理所有事情——也許他們有自己的數據庫。
當您無法為用戶提供或處理陳舊數據時。
再次 - 金融交易。老實說,我想不出任何其他的例子。
當未決交易與重大成本相關時。想像一下當醫生和護士都更新患者記錄並同時刪除彼此的更改時可能出現的問題,因為數據庫無法隔離事務。醫療保健系統是除金融之外的另一個領域,其中 ACID 保證往往至關重要。
8.5 什麼時候不需要 ACID?
當用戶只更新他們的一些私人數據時。
例如,用戶在網頁上留下評論或便簽。或在任何服務提供商的個人帳戶中編輯個人數據。
當用戶根本不更新數據,而只是補充新數據(追加)時。
例如,一個正在運行的應用程序可以保存您的跑步數據:您跑了多少、跑了多少時間、路線等。每次新運行都是新數據,舊的完全沒有編輯。也許,基於數據,您可以獲得分析 - 只有 NoSQL 數據庫適合這種情況。
當業務邏輯不確定是否需要執行事務的特定順序時。
或許,對於一個在下一次直播中為製作新素材募捐的 Youtube 博主來說,是誰、在什麼時候、以什麼順序向他投錢並不重要。
當用戶將在同一個網頁或應用程序窗口停留幾秒鐘甚至幾分鐘,因此他們會以某種方式看到過時的數據。
理論上,這些是任何在線新聞媒體,或相同的 Youtube。或“哈布爾”。 當不完整的交易可以暫時存儲在系統中對您來說無關緊要時,您可以忽略它們而不會造成任何損害。
如果您正在聚合來自多個來源的數據,並且更新頻率很高的數據——例如,至少每 5 分鐘更改一次的城市停車位佔用率數據,那麼理論上這不會是一個大問題對你來說,如果某個停車場的交易在某個時候無法完成。雖然,當然,這取決於你想用這些數據做什麼。
GO TO FULL VERSION