1. 程式人生 > >Apache Cassandra 資料儲存模型

Apache Cassandra 資料儲存模型

我們在《Apache Cassandra 簡介》文章中介紹了 Cassandra 的資料模型類似於 Google 的 Bigtable,對應的開源實現為 Apache HBase,而且我們在 《HBase基本知識介紹及典型案例分析》 文章中簡單介紹了 Apache HBase 的資料模型。按照這個思路,Apache Cassandra 的資料模型應該和 Apache HBase 的資料模型很類似,那麼這兩者的資料儲存模型是不是一樣的呢?本文將為大家解答這些問題。我們從 KeySpace -> Table -> Partition -> Row -> Cell 順序介紹。本文基於 Apache Cassandra 3.11.4 原始碼進行介紹的,不同版本可能有些不一樣。

Table & KeySpace

Cassandra 中的 KeySpace 概念和 RDBMS 裡面的 DataBase 概念很類似,一個 KeySpace 包含多張表,一般將有關聯的資料表放到同一個 KeySpace 下面。KeySpace 建立的時候可以指定副本策略,副本因子以及是否啟用 CommitLog 機制(類似 HBase 中的 WAL)。

Cassandra 中表的概念和 RDBMS 很類似。不同的是在 Cassandra 中屬於同一張表的資料在物理上是分佈在不同節點上儲存的,同一張表由多個 Partition 組成。

Partitions

Cassandra 一般是由多臺節點組成的,每臺節點負責一定範圍的,如果使用 Murmur3hash 的時候,每個節點負責的 Token 類似於下面那樣:

所以 Token 範圍為 -9223372036854775808 ~ -4611686018427387904 的資料儲存在 A 節點;同理,Token 範圍為 -4611686018427387903 ~ -1 之間的資料儲存在 B節點,其他類似;每個 Token 範圍由多個 Partition 構成,每個 Partition 由一行或多行資料組成,Partition 類似下面的:

其中,username 為 Partition key;type 為 Clustering key。那麼在這種情況下,username = iteblog 的兩條資料構成一個 Partition;另外兩條構成分別構成兩個 Partitions。在底層儲存每個 Partition 格式如下:

從上圖可以看出,一個 Partition 是由 PartitionHeader、零個或多個 Row (格式在後面介紹)以及 EndPartition 三部分組成的。EndPartition 這個用於標記 Partition 結束,只佔用一個位元組,並使用 0x00000001 標記。PartitionHeader 的格式如下:

  • Partition Key 就是我們建表的時候指定的,由於 Partition Key 長度使用兩位元組表示,所以 Cassandra 中 Partition Key 長度必須小於等於 65535 位元組。
  • Local Delete Time 是刪除發生時的伺服器時間(以秒為單位),與 gc_grace_seconds 進行比較以確定何時可以清除它。當與 TTL 一起使用時,localDeletionTime 是資料到期的時間。共佔四個位元組;
  • Marked For Delete At 記錄刪除的時間戳,時間戳小於此值的資料被視為已刪除,共佔用八位元組。
  • Static Row:如果我們建表的時候有 Static 欄位,那麼標記為 Static 的列會在這裡儲存。從這裡也可以看出,partition key 相同的資料 Static 列只會儲存一份資料。

在底層儲存中,多個 Partition 組成一個 SSTable(Sorted-String Table)檔案。那麼同一個 SSTable 檔案中的資料資料是如何組織的呢?答案是按照 Partition Key 計算得到的 Token 升序排序的。

Row

上面看出,Partition 裡面包含了零個或多個 Row,這些 Row 對應的 Partition Key 是一樣的。非 Static 的 Row 在磁碟儲存的格式如下:

上面除了 flags 、Row Body Size 、 Previous Row Body Size 以及 Primary Key Liveness Timestamp 這四個欄位一定會存在,其他欄位需要滿足條件才會儲存。下面對上面欄位進行介紹:

  • flags:Row 的標記資訊,主要用於標記當前 Row 是否存在時間戳、TTL、被刪除、是否包含所有的列等資訊。flag 欄位佔用一個位元組,
  • hasExtendedFlags:當前 Row 是否含有 Static 列,存在才會有資料;
  • Clustering info:每個 Row 包含零個或多個 Clustering 相關的資訊。Clustering 資訊就是我們建立表的時候指定的 Clustering key 資訊。每個 Clustering Info 在持久化的時候會先儲存頭部資訊,標記當前 Clustering key 是否為空、是否為 null 以及是否有值等資訊;然後根據資料型別將值存下來,如果當前 Clustering key 的值佔用位元組非固定,還需要儲存當前 Clustering key 值的位元組數。
  • Row Body Size:當前 Row Body 的大小,Row Body 包含 primary key 的 liveness 資訊、Row 是否刪除等資訊以及 Cell 的資訊。
  • Previous Row Body Size:前一個 Row Body 的大小,這個主要用於加速反向查詢的,不過當前並沒有使用;
  • Primary Key Liveness Timestamp:primary key 的 Liveness 用於確定行是否還活著或已經死了(沒有 live cells 並且 liveness 為空)。這個欄位主要用於儲存當前 Row 的 Liveness 時間戳。注意,持久化到磁碟的時間戳是相對於當前 Memtable 最小時間戳的值。
  • Primary Key Liveness TTL:這個欄位主要用於儲存當前 Row 的 Liveness TTL 資訊。也是相對於當前 Memtable 最小 TTL 的值
  • Primary Key Liveness LocalExpirationTime:當前 Liveness 的 ExpirationTime,也是相對時間;
  • Row Marked For Delete At:當前 Row 的刪除時間,也是相對時間,精確到毫秒;
  • Row Local Deletion Time:當前被標記為 tombstone 時伺服器的時間,也是相對時間,精確到秒;
  • Columns Bitmap:從 Cassandra 3.x 開始,列的資訊已經不儲存到資料檔案裡面了,列的資訊是儲存在對應 SSTable 的 md-X-big-Statistics.db 檔案中。這個欄位是用於標記當前行哪些列存在,哪些列不存在。如果列存在則標記為0;如果列不存在則標記為1;如果列全部存在,直接標記為0。當表的欄位數小於64個的時候,直接使用一個 long 型別的資料來儲存這個 bitmap。如果大於等於64個,處理方案稍微複雜一些:
    先儲存一個標記位,標記當前表擁有的欄位個數大於等於64;

如果存在的列沒有佔總列數的一半,則按照全部列的順序儲存存在的列在排序後列的索引位置;
如果存在的列佔總列數超過一半,則按照全部列的順序儲存不存在的列在排序後列的索引位置。
可見,Cassandra 通過將列的資訊(包括列的名稱、型別、表名、keySpace等資訊)儲存到對應 SSTable 的 md-X-big-Statistics.db 檔案中,相應的行只儲存列是否存在的標記資訊,這個可以節省儲存空間的佔用。注意,HBase 儲存資料的時候每個 Cell 都需要儲存列名稱和列族名稱的。

非 Static Row 的底層儲存格式已經在前面描述過,對於 Static Row 除了沒有上圖的 Clustering info 資訊,其餘都一樣,所以這裡就不介紹了。

上圖中最後有 N 個 Cell,那多個 Cell 之間的順序是如何保證的呢?答案是按照列的名稱字典順序升序排序的。比如我們表的定義如下:

CREATE TABLE iteblog (
  user_id text,
  type text,
  action text,
  username text,
  age text,
  email text,
  PRIMARY KEY(user_id)
);

那麼 Cell 的順序排列如下:

action -> age -> email -> type -> username

這個排序是通過 BTree 實現的,Row 的實現類為 BTreeRow。

Cell

Cell 就是每列資料的底層實現,Cell 裡面包含了列的定義資訊,比如是否被刪除、是否過期、是否設定了時間戳等。在 Cassandra 裡面,Column 有 Simple 和 Complex(CASSANDRA-8099引入的) 之分。non-frozen collection 或 UDT(使用者自定義型別)的列是 ComplexColumn(Complex Cell)。

Simple Cell(Simple Column)的底層格式

我們正常使用的列就是屬於這種型別的,它的底層儲存格式如下:

  • flags:這個 Cell 的 flag 標記,主要用於標記當前 Cell 是否有值、是否被刪除、是否過期、是否使用 Row 時間戳、是否使用 Row TTL 等資訊。flag 欄位佔用一個位元組,每位的含義代表如下:
  • timestamp:當前 Cell 的時間戳,Cassandra 中我們可以對每列設定時間戳;
  • deletion time:當前 Cell 的刪除時間;
  • ttl:當前 Cell 的 TTL,Cassandra 中我們可以對每列設定 TTL,代表這個 Cell 保留多長時間;
  • value:當前 Cell 的值;

Complex Cell(Complex Column)的底層格式

如果列屬於 non-frozen collection 或 UDT(使用者自定義型別),那麼這個屬於 Complex Cell,它的底層儲存格式如下:

可以看出,Complex Cell 和 Simple Cell 大部分很類似,下面只介紹不一樣的地方:

  • Complex Cell Marked For Delete At & Complex Cell Local Deletion Time:這兩個屬性和前面的類似,只不過針對 Complex Cell 而言的。
  • Complex Cell Counts:Complex Cell 的個數;
  • path:當前 Cell 的路徑。

在 Cassandra 中, Complex Cell 的實現類是 ComplexColumnData。

作者:明惠

原文連結

本文為雲棲社群原創內容,未經