高層架構#
Cassandra 是一個分散式、去中心化、可擴展且高可用的 NoSQL 資料庫。本章涵蓋 Cassandra 的核心架構、資料分割、複製策略、一致性層級、讀寫路徑,以及壓縮與墓碑等進階機制。
Cassandra 常見術語#
在深入了解 Cassandra 的架構之前,先介紹幾個常見術語:
- 欄(Column):欄是一個鍵值對(Key-Value Pair),是 Cassandra 最基本的資料結構單位。
- 欄鍵(Column Key):在一列中唯一識別一個欄。
- 欄值(Column Value):儲存一個值或一組值。
- 列(Row):列是以主鍵(Primary Key)為參照的欄容器。Cassandra 不會儲存值為 null 的欄,這能節省大量空間。
- 表(Table):表是列的容器。
- 鍵空間(Keyspace):鍵空間是表的容器,可跨越一個或多個 Cassandra 節點。
- 叢集(Cluster):叢集是鍵空間的容器。
- 節點(Node):節點指的是執行 Cassandra 實例的電腦系統,可以是實體主機、雲端機器實例,甚至是 Docker 容器。
Cassandra 是 NoSQL 資料庫,這意味著我們無法進行表之間的聯結(Join),沒有外鍵(Foreign Key),而且在查詢時,除了主鍵之外不能在 WHERE 子句中加入其他欄位。在決定使用 Cassandra 之前,應牢記這些限制。
資料分割#
Cassandra 使用**一致性雜湊(Consistent Hashing)**進行資料分割。所有在 Dynamo 資料分割中描述的一致性雜湊細節同樣適用於 Cassandra。
以下介紹 Cassandra 用來唯一識別列的機制。
Cassandra 的鍵#

主鍵的組成部分
**主鍵(Primary Key)**唯一識別表中的每一列。在 Cassandra 中,主鍵分為兩個部分:
- 分區鍵(Partition Key):決定資料如何在節點之間分佈。
- 叢集鍵(Clustering Key):決定資料在節點內部如何儲存。
以一個主鍵為 PRIMARY KEY (city_id, employee_id) 的表為例:
city_id是分區鍵。這意味著資料會依照city_id欄位進行分區,即所有具有相同city_id的列都會存放在同一個節點上。employee_id是叢集鍵。這意味著在每個節點內,資料會依照employee_id欄位的排序順序儲存。
叢集鍵#

叢集鍵
叢集鍵定義了資料在節點內的儲存方式。我們可以有多個叢集鍵;在分區鍵之後列出的所有欄位都稱為叢集欄位。叢集欄位指定了資料在節點上的排列順序。
分區器#
**分區器(Partitioner)**是負責決定資料如何分佈在一致性雜湊環(Consistent Hash Ring)上的元件。當 Cassandra 向叢集插入資料時,分區器執行的第一步是對分區鍵套用雜湊演算法。此雜湊演算法的輸出決定了資料位於哪個範圍,從而決定資料儲存在哪個節點上。

在一致性雜湊環上分佈資料
Cassandra 預設使用 Murmur3 雜湊函數。Murmur3 對於給定的分區鍵總是會產生相同的雜湊值,這意味著我們總是能找到某個特定列儲存在哪個節點上。Cassandra 允許自訂雜湊函數,但一旦叢集以特定的分區器初始化後,就無法再更改。在 Cassandra 的預設配置中,一個 token 是 64 位元整數,其可能範圍為 -2^63 到 2^63 - 1。
所有 Cassandra 節點透過八卦協定(Gossip Protocol)來了解其他節點的 token 分配情況(稍後討論)。這意味著任何節點都可以處理任何其他節點範圍的請求。接收請求的節點稱為協調者(Coordinator),任何節點都可以擔任此角色。如果某個鍵不屬於協調者的範圍,它會將請求轉發給負責該範圍的副本。
協調者節點#

客戶端連接到協調者節點
客戶端可以連接到叢集中的任何節點來發起讀取或寫入查詢。這個節點被稱為協調者節點(Coordinator Node)。協調者識別負責被寫入或讀取之資料的節點,並將查詢轉發給它們。
複製#
Cassandra 中每個節點都作為不同資料範圍的副本。Cassandra 儲存資料的多個副本並將其分散到不同的副本節點上,這樣即使某個節點當機,其他副本仍能回應該資料範圍的查詢。複製過程取決於兩個因素:
- 複製因子(Replication Factor):決定系統將擁有多少副本。
- 複製策略(Replication Strategy):決定哪些節點負責存放副本。
複製因子#
複製因子是接收相同資料副本的節點數量。例如,如果叢集的複製因子為 3,則每一列將被儲存在三個不同的節點上。Cassandra 中的每個鍵空間可以有不同的複製因子。
複製策略#
擁有分區鍵雜湊值所在範圍的節點將成為第一個副本;所有額外的副本都放置在後續的節點上。Cassandra 以順時針方式將後續副本放置在下一個節點上。Cassandra 有兩種複製策略:
簡單複製策略#

3 個副本的簡單複製
此策略僅用於單一資料中心的叢集。在此策略下,Cassandra 將第一個副本放置在由分區器決定的節點上,後續副本則以順時針方式放置在下一個節點上。
網路拓撲策略#

用於複製的網路拓撲策略
此策略用於多個資料中心。在此策略下,我們可以為不同的資料中心指定不同的複製因子,從而控制每個資料中心放置多少副本。額外的副本始終以順時針方式放置在下一個節點上。
一致性層級#
Cassandra 的一致性層級(Consistency Level)定義為在讀取或寫入操作被視為成功之前,必須完成該操作的最少 Cassandra 節點數量。Cassandra 允許我們為讀取和寫入操作指定不同的一致性層級。此外,Cassandra 具有可調式一致性(Tunable Consistency),即我們可以針對每個請求提高或降低一致性層級。
一致性和效能之間始終存在權衡。較高的一致性層級意味著更多節點需要回應讀取或寫入查詢,這能給使用者更高的保證,確保每個副本上的值都是相同的。
寫入一致性層級#
對於寫入操作,一致性層級指定在寫入被報告為成功之前,必須有多少個副本節點回應。一致性層級由客戶端在每次查詢時指定。由於 Cassandra 是最終一致性的,因此對其他副本節點的更新可能會在背景中繼續進行。以下是 Cassandra 提供的不同寫入一致性層級:
- One / Two / Three:資料必須寫入至少指定數量的副本節點後,寫入才被視為成功。
- Quorum:資料必須寫入至少法定多數(Quorum)的副本節點。Quorum 定義為
floor(RF/2 + 1),其中 RF 為複製因子。例如,在複製因子為 5 的叢集中,如果 3 個節點回應成功,則寫入被視為成功。 - All:確保資料寫入所有副本節點。此一致性層級提供最高一致性但最低可用性,因為如果任何副本當機,寫入就會失敗。
- Local_Quorum:確保資料寫入與協調者同一資料中心的法定多數節點。它不等待其他資料中心的回應。
- Each_Quorum:確保資料寫入每個資料中心的法定多數節點。
- Any:資料必須寫入至少一個節點。在極端情況下,當給定分區鍵的所有副本節點都當機時,寫入仍可在提示移交(Hinted Handoff,下文討論)被寫入後成功。
Any一致性層級提供最低延遲和最高可用性,但一致性最低。如果在寫入時所有副本節點都當機,Any寫入在該分區的副本節點恢復並寫入最新資料之前是不可讀的。
當叢集無法滿足客戶端指定的一致性層級時,Cassandra 會使寫入請求失敗,且不會儲存提示。
Cassandra 執行寫入操作的方式:協調者節點聯繫所有副本(由複製因子決定),當等於一致性層級數量的副本確認寫入後,即視為寫入成功。
提示移交#

提示移交
根據一致性層級,即使節點當機,Cassandra 仍然可以處理寫入請求。例如,如果複製因子為 3 且客戶端使用 Quorum 一致性層級寫入,這意味著即使其中一個節點當機,Cassandra 仍可寫入剩餘的兩個節點來滿足一致性層級,從而使寫入成功。
當原本當機的節點重新上線時,Cassandra 透過**提示移交(Hinted Handoff)**來將資料寫入該節點。其運作方式如下:
- 當某個節點當機或未回應寫入請求時,協調者節點會在本機磁碟上的文字檔中寫入一個提示。此提示包含資料本身以及該資料屬於哪個節點的資訊。
- 當協調者節點透過八卦協定(Gossiper)發現持有提示的目標節點已恢復時,它會將每個提示對應的寫入請求轉發給目標節點。
- 此外,每個節點每十分鐘會檢查其持有提示的故障節點是否已恢復。
使用
Any一致性層級時,如果所有副本節點都當機,協調者節點會為所有節點寫入提示並向客戶端報告成功。但是,在某個副本節點重新上線且協調者節點成功轉發寫入請求之前,這些資料不會出現在任何後續讀取中。這也意味著如果協調者節點死亡且永不恢復,我們可能會丟失資料。因此,應避免使用Any一致性層級。
如果某個節點離線一段時間,其他節點上的提示可能會大量累積。當故障節點重新上線時,其他節點傾向於用大量寫入請求湧入該節點,這可能導致問題。為了解決這個問題,Cassandra 將提示的儲存限制在一個可配置的時間視窗內,也可以完全停用提示移交。
Cassandra 預設儲存提示三小時。三小時後,較舊的提示將被移除,這意味著如果故障節點此時才恢復,它將擁有過時的資料。Cassandra 可以在處理讀取請求時修復此過時資料,透過發起**讀取修復(Read Repair)**來完成(將在讀取路徑中討論)。
sequenceDiagram
participant C as 客戶端(Client)
participant Co as 協調者(Coordinator)
participant B as 節點 B(提示儲存)
participant A as 節點 A(目標節點)
Note over A: 節點 A 當機
C->>Co: 寫入請求
Co->>A: 嘗試寫入
A--xCo: 無回應(節點當機)
Co->>B: 寫入成功
Co->>Co: 儲存提示(Hint)到本機磁碟
Note over Co: 提示包含資料及<br/>目標節點資訊
Co->>C: 寫入成功確認
Note over A: 節點 A 恢復上線
Co->>Co: 透過八卦協定(Gossip)<br/>偵測到節點 A 恢復
Co->>A: 重播提示(Replay Hints)
A->>A: 寫入提示中的資料
A->>Co: 確認寫入完成
Note over A, B: 資料恢復一致讀取一致性層級#

讀取修復
讀取查詢的一致性層級指定在回傳資料之前,必須有多少個副本節點回應讀取請求。例如,對於一致性層級為 Quorum 且複製因子為 3 的讀取請求,協調者會等待至少兩個節點的成功回覆。
Cassandra 的讀取一致性層級與寫入操作相同,但不包括 Each_Quorum(因為成本太高)。
要在 Cassandra 中達到強一致性(Strong Consistency),需滿足
R + W > RF,其中 R 是讀取副本數、W 是寫入副本數、RF 是複製因子。在此情境下,所有客戶端讀取都能看到最近的寫入。
Cassandra 執行讀取操作的方式:協調者總是將讀取請求發送給最快的節點。例如,對於 Quorum=2,協調者向最快的節點發送請求以取得完整資料,並向第二快的節點請求資料的摘要(Digest)。摘要是資料的校驗和(Checksum),用於節省網路頻寬。
如果摘要不匹配,表示某些副本沒有最新版本的資料。在這種情況下,協調者從所有副本讀取資料以確定最新資料,然後將最新資料回傳給客戶端,並發起讀取修復請求,將較新版本的資料推送到擁有較舊版本的節點。
讀取修復#
在討論 Cassandra 的寫入路徑時,我們看到節點可能因網路問題、節點故障、磁碟損壞等原因而不同步。讀取修復操作幫助節點與最新資料重新同步。讀取操作被用作修復跨副本不一致資料的機會。最新的寫入時間戳記被用作正確資料版本的標記。讀取修復操作僅在總讀取的一部分中執行,以避免效能降低。讀取修復是伺機性操作,而非反熵(Anti-Entropy)的主要操作。
讀取修復機率(Read Repair Chance):當讀取一致性層級低於 All 時,Cassandra 以機率方式執行讀取修復。預設情況下,Cassandra 嘗試對 10% 的所有請求進行本地資料中心讀取修復。在這種情況下,Cassandra 在滿足一致性層級後立即發送回應,並在背景非同步執行讀取修復。
Snitch#
Snitch 追蹤 Cassandra 節點的網路拓撲,確定節點屬於哪些資料中心和機架(Rack)。Cassandra 使用此資訊來有效地路由請求。Snitch 在 Cassandra 中有兩個主要功能:
- 確定節點鄰近性並監控讀取延遲:Snitch 確定環中節點的鄰近程度,並監控讀取延遲以避免從速度變慢的節點讀取。每個 Cassandra 節點使用此資訊來有效地路由請求。
- 輔助複製策略:Cassandra 的複製策略使用 Snitch 提供的資訊,在叢集中智慧地分散副本。Cassandra 會盡量避免在同一個「機架」上放置超過一個副本。
以 Cassandra 的讀取操作為例:假設客戶端以 Quorum 一致性層級執行讀取,且資料被複製到五個節點上。為了支援最大讀取速度,Cassandra 選擇一個副本查詢完整物件,並向另外兩個節點請求資料的摘要以確保回傳最新版本的資料。Snitch 幫助識別最快的副本,Cassandra 向該副本請求完整物件。
八卦協定#

八卦協定
Cassandra 使用八卦協定(Gossip Protocol),讓每個節點追蹤叢集中其他節點的狀態資訊。節點之間共享狀態資訊以保持同步。八卦協定是一種點對點通訊機制,節點定期交換自身及其所知其他節點的狀態資訊。每個節點每秒發起一輪八卦,與一到三個隨機選擇的節點交換狀態資訊。透過這種方式,所有節點能快速了解叢集中的所有其他節點。
每個八卦訊息都有一個版本與之關聯,因此在八卦交換過程中,較舊的資訊會被特定節點的最新狀態覆蓋。
- 世代編號(Generation Number):在 Cassandra 中,每個節點儲存一個世代編號,每次節點重新啟動時都會遞增。此世代編號包含在節點之間交換的每個八卦訊息中,用於區分節點的當前狀態和重新啟動前的狀態。接收八卦訊息的節點可以比較它已知的世代編號和八卦訊息中的世代編號。如果八卦訊息中的世代編號更高,則表示該節點已重新啟動。
- 種子節點(Seed Nodes):為了防止八卦通訊中的問題,Cassandra 在叢集中指定一個節點列表作為種子節點。這對於首次啟動的節點至關重要。預設情況下,節點會記住在後續重新啟動之間曾經進行八卦交流的其他節點。種子節點的指定除了為新加入叢集的節點引導八卦流程外,沒有其他用途。因此,種子節點不是單點故障,在叢集操作中除了引導節點外也沒有任何其他特殊用途。
節點故障偵測#
準確偵測故障是一個難以解決的問題,因為我們無法百分之百確定一個系統是真的當機了,還是只是因為負載過重、網路擁塞等原因而回應緩慢。像**心跳(Heartbeat)**這樣的機制輸出一個布林值告訴我們系統是否存活;沒有中間地帶。心跳使用固定的逾時時間,如果沒有收到伺服器的心跳,系統在逾時後會假設伺服器已崩潰。逾時值的設定至關重要:
- 如果逾時設定過短,系統能快速偵測故障,但會因緩慢的機器或有問題的網路而產生許多誤報。
- 如果逾時設定過長,誤報會減少,但系統偵測故障的速度會變慢,效率降低。
Cassandra 使用 Phi Accrual Failure Detector 所描述的適應性故障偵測機制。此演算法使用歷史心跳資訊使閾值具有適應性。通用的 Accrual Failure Detector 不是告訴伺服器是否存活,而是輸出對某個伺服器的懷疑程度(Suspicion Level);較高的懷疑程度意味著伺服器當機的可能性更高。使用 Phi Accrual Failure Detector 時,如果節點未回應,其懷疑程度會增加,之後可能被宣告為已死亡。隨著節點懷疑程度的增加,系統可以逐步決定停止向其發送新請求。Phi Accrual Failure Detector 使分散式系統更加高效,因為它在宣告系統完全死亡之前會考慮網路環境的波動和其他間歇性伺服器問題。
Cassandra 寫入操作的解剖#
Cassandra 將資料同時儲存在記憶體和磁碟上,以提供高效能和持久性。每次寫入都包含一個時間戳記。寫入路徑涉及許多元件,以下是 Cassandra 寫入路徑的摘要:
- 每次寫入都會附加到儲存在磁碟上的提交日誌(Commit Log)。
- 然後寫入記憶體中的 MemTable。
- 定期將 MemTable 沖刷(Flush)到磁碟上的 SSTable。
- 定期執行**壓縮(Compaction)**來合併 SSTable。
提交日誌#

將資料儲存到提交日誌和 MemTable
當節點收到寫入請求時,它會立即將資料寫入提交日誌。提交日誌是一個預寫日誌(Write-Ahead Log),儲存在磁碟上,用作崩潰恢復機制以支援 Cassandra 的持久性目標。寫入在節點上不會被視為成功,除非它已被寫入提交日誌;這確保了即使寫入操作未能寫入記憶體中的儲存區(MemTable),仍然可以恢復資料。如果節點關閉或意外崩潰,提交日誌可以確保資料不會遺失,因為節點重新啟動時會重播(Replay)提交日誌。
MemTable#
資料寫入提交日誌後,會被寫入一個駐留在記憶體中的資料結構,稱為 MemTable。
- 每個節點在記憶體中為每個 Cassandra 表維護一個 MemTable。
- 每個 MemTable 包含特定 Cassandra 表的資料,並在記憶體中模擬該表。
- 每個 MemTable 累積寫入並為尚未沖刷到磁碟的資料提供讀取服務。
- 提交日誌以順序方式儲存所有寫入,每個新寫入附加到末尾;而 MemTable 則按分區鍵和叢集欄位的排序順序儲存資料。
- 資料寫入提交日誌和 MemTable 後,節點向協調者發送確認,表示資料已成功寫入。
SSTable#
當 MemTable 中儲存的物件數量達到閾值時,MemTable 的內容會被沖刷到磁碟上一個稱為 SSTable 的檔案中。此時會建立一個新的 MemTable 來儲存後續資料。此沖刷操作是非阻塞的;一個表可能同時存在多個 MemTable,一個是當前的,其餘的等待被沖刷。每個 SSTable 包含特定表的資料。
當 MemTable 被沖刷到 SSTable 時,提交日誌中的對應條目會被移除。
SSTable 的名稱是 “Sorted String Table” 的縮寫,最早出現在 Google 的 Bigtable 中。Cassandra 借用了這個術語,儘管它並不以字串形式在磁碟上儲存資料。
一旦 MemTable 作為 SSTable 沖刷到磁碟,它就是不可變的(Immutable),應用程式無法修改。既然不允許更新 SSTable,那麼如何刪除或更新一個欄位呢?在 Cassandra 中,每次刪除或更新都被視為一次新的寫入操作(詳見墓碑章節)。
表的當前資料狀態由記憶體中的 MemTable 和磁碟上的 SSTable 組成。因此,在讀取時,Cassandra 會同時讀取 SSTable 和 MemTable 來找到資料值,因為 MemTable 可能包含尚未沖刷到磁碟的值。MemTable 的運作方式類似於一個回寫快取(Write-Back Cache),Cassandra 可以透過鍵來查詢。
**世代編號(Generation Number)**是一個索引號碼,每次為一個表建立新的 SSTable 時就會遞增,用於唯一識別 SSTable。

Cassandra 寫入路徑的解剖
sequenceDiagram
participant Cl as 客戶端(Client)
participant Co as 協調者(Coordinator)
participant N as 副本節點(Replica Node)
participant CL as 提交日誌(CommitLog)
participant MT as 記憶體表(MemTable)
participant SS as 排序字串表(SSTable)
Cl->>Co: 寫入請求
Co->>N: 轉發寫入至副本
Note over N, CL: 步驟 1:先寫提交日誌確保持久性
N->>CL: 寫入提交日誌(磁碟)
CL-->>N: 寫入完成
Note over N, MT: 步驟 2:寫入記憶體表
N->>MT: 寫入 MemTable(記憶體)
MT-->>N: 寫入完成
N-->>Co: 確認寫入成功
Co-->>Cl: 回傳寫入成功
Note over MT, SS: 步驟 3:MemTable 達到閾值時沖刷
MT->>SS: 沖刷(Flush)為 SSTable(磁碟)
Note over SS: SSTable 不可變(Immutable)
Note over SS: 步驟 4:壓縮(Compaction)
SS->>SS: 合併多個 SSTable 為一個新的 SSTableCassandra 讀取操作的解剖#
以下深入了解 Cassandra 讀取路徑中涉及的元件。
快取#
為了提升讀取效能,Cassandra 提供三種可選的快取形式:
- 列快取(Row Cache):列快取用於快取經常讀取(熱門)的列。它儲存完整的資料列,如果讀取操作請求該列,可以直接回傳給客戶端。這能顯著加速經常存取的列的讀取速度,但代價是更多的記憶體使用。
- 鍵快取(Key Cache):鍵快取儲存最近讀取的分區鍵到其 SSTable 偏移量的映射。這有助於更快地存取儲存在磁碟上的 SSTable,提升讀取效能,但可能會減慢寫入速度,因為每次寫入都必須更新鍵快取。
- 區塊快取(Chunk Cache):區塊快取用於儲存從經常存取的 SSTable 檔案中讀取的未壓縮資料區塊。
從 MemTable 讀取#

Cassandra 讀取路徑的解剖
資料按分區鍵和叢集欄位排序。當讀取請求進來時,節點對分區鍵執行二元搜尋(Binary Search)以找到所需的分區,然後回傳該列。
以下是 Cassandra 讀取路徑的摘要:
- 首先檢查該列是否存在於列快取中。如果存在,則回傳資料,請求結束。
- 如果列快取中沒有,則檢查 MemTable 中是否有資料。
- 如果 MemTable 中也沒有,則需要從 SSTable 或其他資料結構(如分區索引等)中查找。
從 SSTable 讀取#

從 SSTable 讀取
每個 SSTable 由兩個檔案組成:
- 資料檔案(Data File):實際資料儲存在資料檔案中。它包含分區以及與這些分區相關聯的列。分區按排序順序排列。
- 分區索引檔案(Partition Index File):儲存在磁碟上,分區索引檔案儲存排序後的分區鍵到其 SSTable 偏移量的映射。它能精確定位 SSTable 中的分區,而無需掃描資料。
布隆過濾器#
每個 SSTable 都有一個關聯的布隆過濾器(Bloom Filter),用於判斷某個特定鍵是否存在於其中。布隆過濾器用於提升讀取操作的效能。布隆過濾器是非常快速的非確定性演算法,用於測試某個元素是否為集合的成員。它們是非確定性的,因為可能出現誤判(False Positive),但不可能出現漏判(False Negative)。
布隆過濾器的工作原理是將資料集中的值映射到一個位元陣列(Bit Array)中,並使用雜湊函數將較大的資料集壓縮為摘要字串。過濾器儲存在記憶體中,透過減少鍵查找時的磁碟存取需求來提升效能。
Cassandra 為每個 SSTable 維護一個布隆過濾器。執行查詢時,先檢查布隆過濾器再存取磁碟。因為不可能出現漏判,如果過濾器指示元素不存在於集合中,則它肯定不存在;但如果過濾器認為元素存在於集合中,則需要存取磁碟來確認。
分區索引摘要檔案#

從分區索引摘要檔案讀取
**分區索引摘要檔案(Partition Index Summary File)**儲存在記憶體中,是分區索引檔案的摘要。這是為了提升效能而設計的。
如果要讀取 key=12 的資料,步驟如下:
- 在分區索引摘要檔案中,找到 key=12 所在的鍵範圍。這會提供分區索引檔案中的偏移量(=32)。
- 跳轉到分區索引檔案的偏移量 32,搜尋 key=12 的偏移量。這會提供 SSTable 檔案中的偏移量(=3914)。
- 跳轉到 SSTable 的偏移量 3914,讀取 key=12 的資料。

透過鍵快取從分區索引摘要檔案讀取
透過鍵快取讀取 SSTable#
由於鍵快取儲存了最近讀取的分區鍵到其 SSTable 偏移量的映射,它是在 SSTable 中找到所需列的最快方式。

透過鍵快取讀取 SSTable
Cassandra 讀取操作工作流程#

Cassandra 讀取操作的工作流程
如果資料不存在於 MemTable 中,必須在 SSTable 或其他資料結構(如分區索引等)中查找。以下是 Cassandra 讀取操作的完整摘要:
- 首先,Cassandra 檢查該列是否存在於**列快取(Row Cache)**中。如果存在,則回傳資料,請求結束。
- 如果該列不存在於列快取中,則檢查布隆過濾器。如果布隆過濾器指示資料存在於某個 SSTable 中,Cassandra 會在該 SSTable 中查找所需的分區。
- 檢查鍵快取是否有分區鍵。快取命中會提供 SSTable 中分區的偏移量,然後使用該偏移量檢索分區,請求完成。
- Cassandra 繼續在分區索引摘要和分區索引中尋找分區。這些結構也提供 SSTable 中分區的偏移量,然後用於檢索分區並回傳。如果啟用了快取,則使用最新讀取的資料更新快取。
flowchart TD
Start([客戶端發送讀取請求]) --> Coord[協調者(Coordinator)<br/>選擇副本節點]
Coord --> SendReq[向最快副本請求完整資料<br/>向其他副本請求摘要(Digest)]
SendReq --> DigestCheck{摘要是否匹配?}
DigestCheck -->|匹配| ReturnData[回傳資料給客戶端]
DigestCheck -->|不匹配| ReadRepair[讀取修復(Read Repair)]
ReadRepair --> FullRead[向所有副本請求完整資料]
FullRead --> CompareTS[比較時間戳記<br/>找出最新版本]
CompareTS --> UpdateStale[將最新資料推送到<br/>過時的副本節點]
UpdateStale --> ReturnLatest[回傳最新資料給客戶端]
subgraph 單一節點內部讀取路徑
direction TB
RC{列快取<br/>Row Cache}
RC -->|命中| Done[回傳資料]
RC -->|未命中| BF{布隆過濾器<br/>Bloom Filter}
BF -->|不存在| Skip[跳過此 SSTable]
BF -->|可能存在| KC{鍵快取<br/>Key Cache}
KC -->|命中| FetchSS1[透過偏移量讀取 SSTable]
KC -->|未命中| PS[分區索引摘要<br/>Partition Summary]
PS --> PI[分區索引<br/>Partition Index]
PI --> FetchSS2[透過偏移量讀取 SSTable]
FetchSS1 --> Done
FetchSS2 --> Done
end壓縮#
如前所述,SSTable 是不可變的,這幫助 Cassandra 實現如此高的寫入速度。MemTable 到 SSTable 的沖刷是持續進行的過程,這意味著磁碟上可能存在大量 SSTable。在讀取時,掃描所有這些 SSTable 是繁瑣的。因此,為了提升讀取效能,我們需要壓縮。
**壓縮(Compaction)**在 Cassandra 中是指將多個相關的 SSTable 合併為一個新的 SSTable 的操作。在壓縮過程中,SSTable 中的資料會被合併:鍵被合併、欄位被組合、過時的值被丟棄,並建立新的索引。

將兩個 SSTable 壓縮為一個
壓縮會:
- 減少需要查閱的 SSTable 數量,從而提升讀取效能。
- 回收 SSTable 中過時資料佔用的空間。
壓縮策略#
| 策略 | 適用場景 | 說明 |
|---|---|---|
| SizeTiered | 插入密集型 / 一般工作負載 | 預設策略,當存在多個大小相似的 SSTable 時觸發 |
| Leveled | 讀取密集型工作負載 | 將 SSTable 分組到不同層級,每個層級有固定大小限制,比前一層大十倍 |
| Time Window | 時間序列資料 | 在配置的時間視窗內壓縮 SSTable,適合固定時間間隔後不可變的資料 |
循序寫入#
**循序寫入(Sequential Writes)**是 Cassandra 寫入效能如此出色的主要原因。寫入值到 Cassandra 不需要任何讀取或搜尋操作,因為所有寫入都是「附加」操作。這使得磁碟速度成為效能的關鍵限制因素。壓縮旨在分攤資料重組的成本,但它使用循序 I/O 來完成,這使其高效。如果 Cassandra 直接將值插入到最終位置,寫入客戶端就需要預先付出搜尋的代價。
墓碑#
什麼是墓碑?#
一個有趣的情況是:當我們為某個當機或無法到達的節點刪除資料時,該節點可能錯過刪除操作。當該節點稍後重新上線並發生修復時,該節點可能會透過與其他節點共享資料來「復活」之前已刪除的資料。
為了防止已刪除的資料被重新引入,Cassandra 使用了一個稱為**墓碑(Tombstone)**的概念。墓碑類似於關聯式資料庫中「軟刪除(Soft Delete)」的概念。當我們刪除資料時,Cassandra 不會立即刪除它,而是在資料上關聯一個帶有過期時間的墓碑。換句話說,墓碑是一個標記,用於指示已被刪除的資料。當我們執行刪除操作時,資料不會立即刪除,而是被視為在該值上放置墓碑的更新操作。
每個墓碑都有一個相關聯的過期時間,表示節點在永久移除資料之前等待的時間。預設情況下,每個墓碑的過期時間為十天。此延遲的目的是給不可用的節點時間來恢復。如果節點的停機時間超過此值,則應將其視為故障並替換。墓碑在壓縮過程中被移除;在壓縮期間,帶有已過期墓碑的列不會被進一步傳播。
墓碑的常見問題#
墓碑使 Cassandra 的寫入操作高效,因為資料在刪除時不會立即被移除,而是在稍後的壓縮過程中被移除。話雖如此,墓碑會導致以下問題:
- 由於墓碑本身也是一筆記錄,它會佔用儲存空間。因此應注意,刪除操作實際上會導致資料大小增加而非縮小。此外,如果有大量墓碑,應用程式的可用儲存空間可能會大幅減少。
- 當一個表累積了許多墓碑時,對該表的讀取查詢可能變慢,並可能導致嚴重的效能問題(如逾時)。這是因為在實際壓縮發生並移除墓碑之前,我們必須讀取更多的資料。
總結#
Cassandra 是一個分散式、去中心化、可擴展且高可用的 NoSQL 資料庫。以下是其核心特性:
- Cassandra 的設計基於軟硬體故障隨時可能發生的前提。
- Cassandra 是一個點對點分散式系統,即沒有領導者或跟隨者節點。所有節點平等,不存在單點故障。
- 資料在 Cassandra 中自動分佈到各節點上。
- 資料被複製到多個節點上以實現容錯和冗餘。
- Cassandra 使用**一致性雜湊(Consistent Hashing)**演算法在叢集中的節點之間分佈資料。Cassandra 叢集具有環狀架構,其節點在邏輯上如同環一樣分佈。
- Cassandra 使用 Google Bigtable 的資料模型,即 SSTable 和 MemTable。
- Cassandra 使用 Amazon Dynamo 的分散式特性,即一致性雜湊、分區和複製。
- Cassandra 提供讀寫操作的可調式一致性(Tunable Consistency),以調整可用性和一致性之間的權衡。
- Cassandra 使用**八卦協定(Gossip Protocol)**進行節點間通訊。
系統設計模式#
以下是 Cassandra 中使用的系統設計模式摘要:
- 一致性雜湊(Consistent Hashing):Cassandra 使用一致性雜湊在節點之間分佈資料。
- 法定人數(Quorum):為確保資料一致性,每次 Cassandra 寫入操作可配置為僅在資料已寫入至少法定多數的副本節點後才視為成功。
- 預寫日誌(Write-Ahead Log):為確保持久性,每當節點收到寫入請求時,它會立即將資料寫入提交日誌(一種預寫日誌)。
- 分段日誌(Segmented Log):Cassandra 使用分段日誌策略將其提交日誌分割成多個較小的檔案,而非單一的大檔案,以便於操作。隨著提交日誌增長並達到其大小閾值,會建立一個新的提交日誌。因此,隨著時間推移,可能存在多個提交日誌,每個都稱為一個段。提交日誌段減少了寫入磁碟所需的搜尋次數。當 Cassandra 已將對應資料沖刷到 SSTable 後,提交日誌段會被截斷。提交日誌段可以在其所有資料被沖刷到 SSTable 後進行歸檔、刪除或回收。
- 八卦協定(Gossip Protocol):Cassandra 使用八卦協定,讓每個節點追蹤叢集中其他節點的狀態資訊。
- 世代編號(Generation Number):在 Cassandra 中,每個節點保存一個世代編號,每當節點重新啟動時都會遞增。此世代編號包含在節點之間交換的八卦訊息中,用於區分節點的當前狀態和重新啟動前的狀態。
- Phi Accrual Failure Detector:Cassandra 使用 Phi Accrual Failure Detector 演算法所描述的適應性故障偵測機制。此演算法不提供二元輸出來判斷系統是否存活,而是使用歷史心跳資訊來輸出節點的懷疑程度。較高的懷疑程度意味著節點當機的可能性更高。
- 布隆過濾器(Bloom Filter):在 Cassandra 中,每個 SSTable 都有一個關聯的布隆過濾器,用於判斷特定鍵是否存在其中。
- 提示移交(Hinted Handoff):Cassandra 節點使用提示移交來記住故障節點的寫入操作。
- 讀取修復(Read Repair):Cassandra 使用讀取修復將最新版本的資料推送到擁有較舊版本的節點。
Cassandra 的特性#
以下是 Cassandra 高效能和受歡迎的原因:
| 特性 | 說明 |
|---|---|
| 分散式(Distributed) | 可在大量機器上運行 |
| 去中心化(Decentralized) | 沒有領導者/跟隨者範式,所有節點相同 |
| 可擴展(Scalable) | 透過新增節點即可水平擴展,在通用硬體上實現線性可擴展性 |
| 高可用(Highly Available) | 即使節點或資料中心當機,資料仍然可用 |
| 容錯且可靠(Fault Tolerant) | 資料被複製到多個節點,容錯性高 |
| 可調式一致性(Tunable Consistency) | 可透過配置複製因子和一致性層級來調整可用性與一致性的權衡 |
| 持久性(Durable) | 永久儲存資料 |
| 最終一致性(Eventually Consistent) | 以高可用性為優先,犧牲強一致性 |
| 地理分佈(Geographic Distribution) | 支援多個雲端和資料中心之間的高效資料複製 |