1. 程式人生 > >Amazon Aurora: 如何不使用一致性協議實現分布式數據庫

Amazon Aurora: 如何不使用一致性協議實現分布式數據庫

multiple ble 個人觀點 裏的 每一個 轉換 min 題目 transacti

摘要: 這個是Amazon Aurora 發的第二篇文章, 發在2018 年SIGMOD上, 題目很吸引人避免在I/O, commit, 成員變更的過程使用一致性協議. 在大家都在使用一致性協議(raft, multi-paxos)的今天, Aurora 又提出來了不用一致性協議來做, 主要觀點是現有這些協.

這個是Amazon Aurora 發的第二篇文章, 發在2018 年SIGMOD上, 題目很吸引人避免在I/O, commit, 成員變更的過程使用一致性協議. 在大家都在使用一致性協議(raft, multi-paxos)的今天, Aurora 又提出來了不用一致性協議來做, 主要觀點是現有這些協議太重, 而且會帶來額外的網絡開銷, 也可以理解, 畢竟Aurora 是6副本, 主要的瓶頸是在網絡上. 那麽他是怎麽做的?

因為Aurora 很多細節還是沒有揭露, 所以很多內容是我自己的解讀, 以及問的作者, 如果錯誤, 歡迎探討

這篇文章也主要回答這個問題.

Aurora is able to avoid distributed consensus during writes and commits by managing consistency points in the database instance rather than establishing consistency across multiple storage nodes.

在Aurora 中, storage tier 沒有權限決定是否接受write, 而是必須去接受database 傳過來的write. 然後都是由database tier 去決定是否這個 SCL, PGCL, VCL 是否可以往前推進, 也就是說 storage tier 本身並不是一個強一致的系統, 而僅僅是一個quorum 的系統, 需要database tier 來配合實現強一致.

這個也是與當前大部分的系統設計不一樣的地方, 當前大部分的系統都是基於底層強一致, 穩定的KV(當然也可以叫Block storage) 存儲, 然後在上層計算節點去做協議的解析和轉換. 而Aurora 提出底層的系統只需要是一個quorum 的系統, storage tier + database tier 實現一個強一致的方案.

比如像Spanner 裏面, 每一個spanservers 本身是多副本, 多副本之間通過multi-paxos 來保證數據的一致性, 然後上層的F1 這一層主要做的協議轉換, 把SQL 協議轉換成kv 請求去請求spanserver.

我們的PolarDB 也是這樣的一套系統, 底層的存儲節點 polarstore 是一個穩定可靠的強一致系統, 上層的計算節點PolarDB 是一個無狀態的節點.

接下來具體的 Aurora 是如何實現的呢?

Term:

LSN: log sequence number
每一條redo log 有一個唯一的單調遞增的 Log Sequence Number(LSN), 這個LSN 是由database 來生成, 由於Aurora 是一寫多讀的結構, 很容易滿足單調遞增

SCL: segment complete LSN
SCL(segment complete LSN) 表示的是當前這個segment 所知道的最大的LSN, 在這個SCL 之前的所有記錄當前這個節點已經收到, 到SCL 位置的數據都是連續的. 這裏與VCL 的區別是, VCL 是所有節點確認的已經提交的LSN, 而SCL 是自己認為確認已經提交的LSN, VCL 可以認為是storage node 的commit index, 而SCL只是記錄當前節點的LastLogIndex Aurora 也會使用這個SCL來進行節點間交互去補齊log.

VCL: volume complete LSN
這個VCL 就是storage node 認為已經提交的LSN, 也就是storage node 保證小於等於這個VCL 的數據都已經確認提交了, 一旦確認提交, 下次recovery 的時候, 這些數據是保證有的. 如果在storage node recovery 階段的時候, 比VCL 大於的數據就必須要刪除, VCL 相當於commit Index. 這個VCL 只是在storage node 層面保證的, 有可能後續database 會讓VCL 把某一段開始的 log 又都刪掉.

這裏VCL 只是storage node 向database 保證說, 在我storage node 這一層多個節點已經同步, 並且保證一致性了.這個VCL 由storage node 提供.

PGCL: Protection Group Complete LSN
每一個分片都有自己的SCL, 這個SCL 就叫做PGCL. 等於說SCL 是database 總的SCL, 每一個分片有各自的PGCL, 然後這個database 的SCL 就等於最大的這個PGCL

CPL: consistency point LSN
CPL 是由database 提供, 用來告訴storage node 層哪些日誌可以持久化了, 其實這個和文件系統裏面保證多個操作的原子性是一樣的方法.

為什麽需要CPL, 可以這麽理解, database 需要告訴storage node 我已經確認到哪些日誌, 可能有些日誌我已經提交給了storage node了, 但是由於日誌需要truncate 進行回滾操作, 那麽這個CPL就是告訴storage node 到底哪些日誌是我需要的, 其實和文件系統裏面保證多個操作原子性用的是一個方法, 所以一般每一個MTR(mini-transactions) 裏面的最後一個記錄是一個CPL.

VDL: volume durable LSN
因為database 會標記多個CPL, 這些CPL 裏面最大的並且比VCL小的CPL叫做VDL(Volume Durable LSNs). 因為VCL表示的是storage node 認為已經確認提交的LSN, 比VCL小, 說明這些日誌已經全部都在storage node 這一層確認提交了, CPL 是database 層面告訴storage node 哪些日誌可以持久化了, 那麽VDL 表示的就是已經經過database 層確認, 並且storage node層面也確認已經持久化的Log, 那麽就是目前database 確認已經提交的位置點了.

所以VDL 是database 這一層已經確認提交的位置點了, 一般來說VCL 都會比VDL 要來的大, 這個VDL 是由database 來提供的, 一般來說VDL 也才是database 層面關心的, 因為VCL 中可能包含一個事務中未提交的部分.

MTR: mini transaction
那麽事務commit 的過程就是這樣, 每一個事務都有一個對應"commit LSN", 那麽這個事務提交以後就去做其他的事情, 什麽時候通知這個事務已經提交成功呢? 就是當VDL(VDL 由databse 來發送, storage service來確認更新) 大於等於"commit LSN" 以後, 就會有一個專門的線程去通知這個等待的client, 你這個事務已經提交完成了.

如果這個事務提交失敗, 那麽接下來的Recovery 是怎麽處理的呢?

首先這個Recovery 是由storage node 來處理的, 是以每一個PG 為維度進行處理, 在database 起來的時候通過 quorum 讀取足夠多的副本, 然後根據副本裏面的內容得到VDL, 因為每一個時候最後一條記錄是一個CPL, 這些CPL 裏面最大的就是VDL, 然後把這個VDL 發送給其他的副本, 把大於VDL 的redo log 清除掉, 然後恢復這個PG的數據

SCN: commit redo record for the transaction
也就是一個transaction 的 commit redo record, 每一個transaction 生成的redo record 裏面最大commit LSN. 主要用於檢查這個事務是否已經被持久化了

這裏就是通過保證SCN 肯定小於VCL 來進行保證提交的事務是一定能夠持久化的, 所以Aurora 一定是將底下的VCL 大於當前這個transaction 的SCN 以後才會對客戶端進行返回

PGM-RPL: Protection Group Minimum Read Point LSN
這個LSN 主要是為了回收垃圾使用, 表示的是這個database 上面讀取的時候最低的LSN, 低於這個LSN 的數據就可以進行清理了. 所以storage node 只接受的是PGMRPL -> SCL 之間的數據的讀請求

那麽寫入流程是怎樣?

在database tier 有一個事務需要提交, 那麽這個事務可能涉及多個分片(protection group), 那麽就會生成多個MTRs, 然後這些MTRs 按照順序提交log records 給storage tier. 其中每一個MTR 中有可能包含多條log records, 那麽這多條log records 中最後一條的LSN, 也稱作CPL. storage tier 把本地的SCL 往前移. database tier 在接收到超過大多數的storage node 確認以後, 就把自己的VCL 也往前移. 下一次database tier 發送過來請求的時候, 就會帶上這個新的VCL 信息, 那麽其他的storage node 節點就會去更新自己的VCL 信息.

那麽讀取的流程是怎樣?

在aurora 的quorum 做法中, 讀取的時候並沒有走quorum.

從master 節點來說, master 在進行quorum 寫入的時候是能夠獲得並記錄每一個storage node 當前的VDL, 所以讀取的時候直接去擁有最新的VDL 的storage node 去讀取即可.

對於slave 節點, master 在向storage node 寫redo record 的同時, 也異步同步redo log給slave 節點, 同時也會更新VDL, VCL, SCL 等等這些信息給從節點, 從節點本身構造本地的local cache. 並且slave 節點也擁有全局的每一個storage node 的VDL 信息, 因此也可以直接訪問到擁有最新的storage node 的節點.

個人觀點:

這篇文章開頭講的是通過 quorum I/O, locally observable state, monotonically increasing log ordering 三個性質來實現Aurora, 而不需要通過一致性協議. 那我們一一解讀

技術分享圖片

這裏的monotonically increasing log ordering 由LSN 來保證, LSN 類似於Lamport 的 logic clock(因為這裏只有一個節點是寫入節點, 並且如果寫入節點掛了以後有一個恢復的過程, 因此可以很容易的保證這個LSN 是遞增的)

locally observable state 表示當前節點看到的狀態, 也就是每一個節點看到的狀態是不一樣的, 每一個節點(包含database node 和 storage node) 都有自己認為的 SCL, VCL, VDL 等等信息, 這些信息表示的是當前這個節點的狀態, 那麽就像Lamport logic clock 文章中所說的一樣, 在分布式系統中沒有辦法判斷兩個不同節點中的狀態的先後順序, 只有當這兩個狀態發生消息傳遞時, 才可以確定偏序關系. 那麽在這裏就是通過quorum I/O 確定這個偏序關系

quorum I/O 在每一次的quorum IO達成確認以後, 就相當於確認一次偏序關系. 比如在一次寫入成功以後, 那麽我就可以確定當前的data node 的狀態一定是在其他的storage node 之前. 在一次gossip 節點間互相確認信息以後, 主動發起確認信息的節點的狀態也一定在其他節點之前. 所以整個系統在寫入之後或者在重啟之後的gossip 一定能夠存在一個在所有節點最前面的狀態的節點, 那麽這個節點的狀態就是當前這個系統的狀態, 這個狀態所包含的SCL, VCL, VDL 信息就是一致性信息

在每一次讀取的時候, 他所在的節點一定是當前這個偏序關系最前面的位置, 因為讀取操作之前一定是write 或者 recovery 操作, 在write, recovery 操作的過程中, 就與超過半數的節點達成一致, 走在了偏序關系的最前面. 獲得了當前節點的local observable state. 所以讀到的一定是最新的內容

Aurora 系統很容易實現的一個重要原因是他是只有一個writer 存在, 這樣保證了同一時刻只可能在發生一個事件

剛開始看會覺得這個系統比較瑣碎, 不像Paxos/raft 那樣有一個比較完備的理論證明, 不過問過作者, 實現這一套過程也是經過TLA+的證明

閱讀原文?請添加鏈接描述

本文為雲棲社區原創內容,未經允許不得轉載。

Amazon Aurora: 如何不使用一致性協議實現分布式數據庫