——陳默(墨城)
阿里雲資料庫技術專家
瞭解更多PolarDB-X 內容:
https://developer.aliyun.com/topic/polardbx_release
在剛剛釋出的
PolarDB-X 2.1.0
版本中,開源了透明分散式能力,能帶給使用者完全不同的透明分散式資料庫使用體驗。其中,一個最明顯的不同,就是使用者不再需要關注分割槽健這個概念,這也是副標題《
再見,分割槽健
》的來由。
一、從“分割槽”開始
1970
年代末,分割槽的概念首先在並行資料庫系統中出現,用來代表一組記錄的集合。在並行資料庫中,表首先按照某種規則被切分為若干分割槽,每個分割槽存入資料庫節點,每個資料庫節點都有獨立的
CPU
記憶體和儲存節點,節點通過網路交換資料。由於沒有共享應硬體資源,該結構也被稱為
share
d-
nothing
架構。
引入分割槽和
shared-nothing
架構,是為了通過增加節點的方式來提升儲存和計算能力,也稱為水平擴充套件。
從水平擴充套件的角度出發,使用者肯定希望儘可能均勻地將資料分散到各個分割槽中,比如隨機分配。但這會導致查詢時需要掃描全部分片,效能上無法接受。而可行的辦法是按照某些列的值來確定一行資料應該落在哪個分片,這些用來確定一行資料應該落在哪個分片的列,就是分割槽鍵。
2000
年以後,隨著網際網路的普及,需要處理的資料量越來越大,超過了單機系統能夠承載的容量上限,分割槽+
share
d-
nothing
帶來的水平擴充套件能力重新受到重視。
這一階段的實現大致可以分為兩類:
第一類是
NoSQL
系統,通常是從
零
開始實現的全新系統,強調高可用和可擴充套件性。引入分割槽之後,
由於
事務和
SQL
的執行代價升高,通常這些系統不支援跨行事務和
SQL
介面,因此也被稱為
NoSQL
系統。代表性的實現有
Google
的
BigT
able
和
Amazon
的
Dynamo
。
第二類是中介軟體
。
通過在系統和單機資料庫之間增加中間層,將查詢請求路由到資料所在的資料庫節點。這類系統通常不支援跨分片事務和全域性索引,支援
SQL
介面,但要求
SQL
語句中必須包含分割槽鍵上的條件。
整體上看,這一階段資料庫將重心放在瞭解決高可用和擴充套件性問題上,代價是放棄了對
事務
和
SQL
的支援。
從開發人員的角度來看,最直觀的感受是設計業務邏輯時需要考慮分割槽鍵如何選擇。
但是,開發者真的願意設計
分割槽鍵
嗎?開發者真的能設計
分割槽鍵
嗎?如果系統有數十甚至上百張表,又該怎麼做?除此之外,缺少分散式事務也不符合很多使用者的使用習慣。
Google
在論文中總結道,許多工程師將過多精力放在處理資料一致性上,原本封裝在資料庫內部的邏輯溢位到應用程式碼中,大幅提高了應用程式碼的複雜度。因此,不支援分散式事務和要求開發者選擇
分割槽鍵
是使用分散式資料庫最主要的障礙。
接下來,
一起看看
PolarDB-X
在支援分散式事務中使用的關鍵技術,全域性索引如何提升查詢效能、以及如何為使用者提供無需關注分割槽健的透明分散式體驗。
二、分散式事務
資料庫系統需要面對各種各樣的複雜情況,硬體故障可能導致系統在寫入資料的任何階段崩潰,比如應用系統可能在一系列連續操作中突然退出,多個客戶端可能併發地修改同一條記錄等。
出現異常時,資料庫需要提供可靠性保證,而事務就是對可靠性保證的簡化描述。
事務
是一系列讀寫操作的集合,對讀寫作業系統提供四個方面的保障:
①
原子性:如果出現異常,使用者可以通過重試事務來解決,無需擔心失敗的
事務
對資料產生影響。
②
一致性:使用者無需擔心資料操作會違反約束定義,比如唯一約束外來鍵等。
③
隔離性:使用者可以認為只有自己在運算元據庫,無需擔心多個客戶端併發讀寫相同的資料會產生異常。
④
永續性:一旦事務提交成功,使用者就無需擔心事務產生的變更會因為其他異常而丟失。
分散式事務中由於存在多個分割槽,原子性和隔離性受到影響。
對於原子性,需要協調所有分割槽在提交階段的行為,保證一起提交
或
一起回滾。業界通常使用兩階段提交協議來解決此問題。常見的實現有
Per
colator
和 XA 協議。
Per
colator
在提交階段延遲較高,只在提交階段彙報衝突錯誤,且
僅
支援樂觀鎖場景,與傳統的關係數型資料庫基於悲觀
事務
的模型有較大區別。因此
PolarDB-X
選擇通過 XA 協議支援兩階段提交。
對於隔離性,要求能夠對不同分割槽上發生的單分割槽事務進行全域性排序。業界常見的實現方法有基於
GTM
和
TSO
兩種實現。
GTM
方案過於依賴中心化的事務管理器,容易出現系統瓶頸。因此
PolarDB-X
選擇通過基於
TSO
的
MVCC
方案來實現隔離性。
上圖為分散式事務具體的執行過程。
啟動事務後,首先向
TSO
獲取一個
star
_ts
,
作為讀取的快照,接收並處理使用者請求。過程中根據資料對應的事務狀態、快照時間戳和資料提交時間戳來判斷資料是否可見,以保證隔離性。在提交過程中,
CN
節點先通知所有參與
寫
操作的分割槽執行
prepare
,
記錄事務狀態,最後通知所有參與者
commit
。
在記錄事務狀態成功之前產生
的
異常都會導致事務退出,以此保證原子性。
採用
2PC
和
TSO
+
MVCC
方案實現的分散式事務經常被質疑的問題是提交階段延遲增加和
TSO
單點問題。
針對上述兩個問題,PolarDB-X 都進行了工程上的實現優化。
兩階段提交由於增加了
prepare
階段,延遲高於單分片事務。實際上,對於單分片寫多分片讀的
事務
,無論讀取是否跨分割槽,依然可以使用一階段提交來保證原子性。
PolarDB-X
支援自動識別此類情況,能夠顯著減少這類場景下的提交延遲。
TSO
方案採用單點授時,潛在問題是存在單點故障和單點效能瓶頸。
PolarDB-X
的
GMS
服務部署在三節點叢集上,通過 X-Paxos
協議保證服務高可用。同時對多種場景進行了優化,使得帶分割槽條件的點查、點寫可不依賴
GMS
,
提升查詢效能的同時也降低了
GMS
的壓力。
另外,單個
CN
程式預設採用
grouping
的方式,將同一時間發生的多個
TSO
請求合併為
batch
操作一次性獲取,進一步保證
GMS
不會成為系統瓶頸。
下面通過
Flashback Q
uery
示例來展示
MVCC 帶來的特殊能力。
三、全域性索引
上圖為一張按照主鍵拆分的表,
t1
表
partition by
Hash(
ID
)。
當提供的查詢
條件是
id
上的等值查詢時,比如
id
=14
,算出
14
的分割槽雜湊值之後,即可快速定位到
p3
分片。
但是
如果按照
name
進行查詢應該怎麼處理?如果只能全分片掃描,代價過大,不可接受。為了找到答案,先看單機資料庫如何解決此問題。
在
MySQL
中,同樣的表結構是一棵 B+ 樹,按照主鍵
id
有序。當以
id
為條件進行查詢時,資料庫會在 B+ 樹上做二分查詢,從而快速定位到資料所在葉子節點。同樣當以
name
為條件進行查詢時,則無法進行二分查詢,只能將整棵 B+ 樹都
進行遍歷
,即全表掃描,代價較高。
在
MySQL
中,為了避免全表掃描,會在
name
上建立二級索引,即建立了另一棵 B+ 樹,按照索引列有序,此處為按照
name
列有序,其葉子節點記錄了
name
對齊以及其對應的主鍵
id
。
對於
name
上的等值查詢,資料庫會先在二級索引的 B+ 樹上進行二分查詢,找到對應的
id
之後再使用此
id
在主鍵 B+ 樹上進行查詢,即回表操作。
二級索引的理念在計算機中非常常見,其本質
是
用空間換時間。在
MySQL
中設計
主鍵
更多的是考慮其業務上的唯一性,而不在意某列是否為查詢使用最多的列,
背後的原因是
建立二級索引的成本非常低。
分散式資料庫中,按照主鍵分割槽後如何進行
name
列查詢,同時還能避免全表掃描?參考空間換時間的思路,以
name
為分割槽
鍵
,再將資料做一份冗餘,將
name
對映到
id
,
這份冗餘的資料稱為全域性二級索引。
如上圖所示,按照
name=Megan
做查詢時,先通過全域性二級索引定位到
Me
gan
所在分片,找到
Megan
的
id
值,用
id
值到主表上查詢整條記錄所在的分割槽,此過程也稱為回表。
那麼,如果在分散式資料庫上建立全域性二級索引能夠像在單機資料庫上一樣方便,則無須再關注分割槽鍵。因此,關鍵就在於全域性二級索引。全域性索引要儘可能地做到與單機資料庫一樣的相容性,相容度越高就越透明,開發者就能夠使用單機資料庫的經驗來使用分散式資料庫。此處相容性體現在很多方面,包括一致性、建立方式、使用方式和相容其他 DDL。
首先是資料的一致性。
PolarDB-X
支援強一致事務,通過事務來保證主表和索引表的資料一致,所有對包含索引表的表進行寫入的操作都會預設包裝在強一致的分散式事務中。
分散式資料庫中,實現
O
nline Schema
C
hange
需要面臨的主要挑戰是:
schema
變更過程
中
,不同的
CN
節點上,不同事務看到的
元
資料版本並不一致。
單機據資料庫解決此問題的辦法是通過對
元
資料加鎖,保證任意時刻所有事務都只能看到同一版本的
元
資料。但分散式系統中,由於網路延遲存在不確定性,跨節點實現加鎖可能導致明顯的讀寫卡頓,因此並不符合
online
的定義。
PolarDB-X 參考了
Google F1
實現的
O
nline
A
synchronous
S
chema Change
,
通過增加兩個互相相容的中間狀態,允許系統中同時存在至多兩個
元
資料版本,使得
DDL
過程中無需加鎖,不會阻塞任何讀寫請求。有了
O
nline Schema
C
hange
的支撐,在
PolarDB-X
中使用
create index
語句即可輕鬆建立全域性索引,無需依賴任何第三方元件。
在各種
DDL
操作中維護索引表資料一致,工作量很大。
PolarDB-X 支援無鎖化的
O
nline DDL
,
根據
DDL
型別可以自動選擇合適的執行方式,且能自動維護全域性二級索引。
建立完索引後,如果需要手動指定
SQL
使用全域性二級索引,也不是一種友好的使用體驗。合理的方式應該類似於單機資料庫,由資料庫自動選擇使用全域性二級索引。
由於
索引本身也是
一張邏輯
表,且回表操作可以理解為索引表和主表在主鍵上做
J
oin,可以複用大部分分割槽表上執行計劃的代價估計邏輯,使用估算
主
表上的執行計劃
代價的
邏輯來估算索引表上的執行計劃代價。
難點在於
,
兩個表做
J
oin,一個包含三個全域性索引,而另一個沒有全域性索引,執行計劃的空間有非常大差異,需要更復雜的執行計劃列舉演算法以及效能更加強勁的
CBO
設計。
PolarDB-X
支援基於代價的優化器,可以自動完成索引選擇。
下面通過
一個
Demo 來展示如何通過全域性索引優化查詢效能。
從上面演示可以看到,有了全域性二級索引的支援,可以像使用單機資料庫一樣,建表時無須指定額外的拆分鍵,之後通過
MySQL
原生語法建立索引來提升查詢效能。
四、透明分散式
除了分散式事務和全域性索引,分割槽演算法的設計也是提供透明分散式體驗的另一個關鍵。
單機資料庫中,索引可以很好地支援字首查詢。而全域性索引應該如何解這類問題?
建立多列索引時,PolarDB-X
會分別對每一列進行雜湊,在匹配到字首的情況下依然能做一定的分割槽裁剪。
在單機資料庫中建立索引較為隨意,比如在性別列上建立索引,索引的區分度較低,一般不會被使用,寫入時也會有一定爭搶,但不會導致太大的問題;如果是分散式資料庫,索引只有兩個值,按照雜湊進行分割槽,則只會分佈在兩個節點上。假如業務是以寫入為主,則無論叢集有多少臺機器,最後瓶頸都在這兩個節點上,也就失了分散式資料庫擴充套件性的初衷。
因此全域性索引一定要解決
BigKey
問題,才能降低使用門檻。
PolarDB-X
通過將主鍵和索引
key
一起作為分割槽鍵,使得熱點出現時依然能夠按照主鍵進行進一步分裂,從而消除熱點。
對於拆分演算法和分割槽分佈相同的兩張表
或
索引,
PolarDB-X
支援將 Join 下推到儲存節點上執行,在
OLTP
場景下可以顯著降低網路開銷,提升查詢效能。但如果不對分割槽操作進行限制,可能會由於分割槽的分裂合併
或
分割槽遷移,導致兩張表分割槽的分佈變得不同,使得 Join 無法下推。從使用者角度看,可能是出現了一些分割槽操作後反而導致查詢執行效能下降,導致使用者產生一些使用上的疑惑。
為了解決上述問題,
PolarDB-X
在後臺引入了兩個概念:
Table
G
roup
和
Partition Group
。
Table
G
roup
是一組
G
lobal Index
的集合,系統會確保
Table Group
中的索引具備相同的資料分佈。當發生分割槽分裂合併時,
Table Group
中的索引會一起進行分裂和合並,保證數量一致。
處於同一個
Table Group
中的
G
lobal Index
,
相同的分割槽會組成一個
Partition Group
。
PolarDB-X
會保證相同
Partition Group
中的分割槽始終落在相同的
DN
上,保證分割槽遷移操作不會影響 Join 的下推。
合理規劃
Table Group
可以降低分割槽遷移等操作的代價。
PolarDB-X
使用
Table Group
與
Partition Group
來對全域性索引上的 Join 下推進行優化。
分割槽+
shared-nothing
架構帶來了良好的可擴充套件性,但需要額外支援分散式事務和解決跨分割槽查詢效能問題。
PolarDB-X
的分散式事務基於
2PC
和
TSO+
MVCC
,
通過
1
PC
優化降低提交階段的延遲,通過
TSO
合併優化確保
GMS
不成為瓶頸。透明全域性索引良好地相容了單機資料庫上的索引使用體驗,顯著提升跨分割槽查詢的效能。
PolarDB-X
以分散式事務和透明全域性索引為核心的透明分散式技術,顯著地降低使用者使用分散式資料庫的門檻。