1. 概述html
Apache Cassandra將數據存儲在表中,每一個表都由行和列組成。CQL(Cassandra查詢語言)用於查詢存儲在表中的數據。Apache Cassandra數據模型基於查詢並針對查詢進行了優化。Cassandra不支持用於關係數據庫的關係數據建模。Cassandra數據建模專一於查詢。數據庫
Cassandra中的數據建模使用查詢驅動(query-driven)的方法,其中特定查詢是組織數據的關鍵。查詢(Query)是從表中選擇數據的結果,模式(Schema)是對錶中數據的排列方式的定義。Cassandra的數據庫設計基於對快速讀寫的需求,所以架構設計越好,數據寫入和檢索的速度就越快。apache
相反,關係型數據庫根據設計的表和關係對數據進行規範化,而後編寫將要進行的查詢。關係數據庫中的數據建模是表驅動(table-driven)的,表之間的任何關係都表示爲查詢中的錶鏈接。架構
1.1. 什麼是數據建模數據庫設計
數據建模是識別實體及其關係的過程。在關係數據庫中,數據放在具備外鍵的規範化表中,外鍵用於引用其餘表中的相關數據。應用程序將進行的查詢由表的結構驅動,相關數據做爲錶鏈接進行查詢。分佈式
在Cassandra中,數據建模是查詢驅動(query-driven)的。 數據訪問模式和應用程序查詢肯定數據的結構和組織,而後將其用於設計數據庫表。ide
數據圍繞特定查詢建模。查詢的最佳設計是訪問單個表,這意味着查詢中涉及的全部實體必須位於同一表中,以使數據訪問(讀取)變得很是快。 數據被建模爲最適合一個查詢或一組查詢。一個表可能具備一個或多個最適合查詢的實體。因爲實體之間一般確實具備關係,而且查詢可能涉及實體之間具備關係的實體,所以單個實體能夠包含在多個表中。post
1.2. 查詢驅動的建模性能
在關係數據庫模型中,查詢使用錶鏈接從多個表獲取數據,而在Cassandra中不支持鏈接,所以全部必需字段(列)必須組合在一個表中。因爲每一個查詢都由一個表支持,所以在稱爲非規範化的過程當中,數據會在多個表之間冗餘。 數據冗餘和高寫入吞吐量用於實現高讀取性能。 優化
1.3. 目標
主鍵(primary key)和分區鍵(partition key)的選擇對於在整個集羣中均勻分佈數據很重要。使查詢讀取的分區數量保持最少也很重要,由於不一樣的分區可能位於不一樣的節點上,而且協調器將須要向每一個節點發送請求,從而增長了請求的開銷和延遲。即便查詢中涉及的不一樣分區位於同一節點上,較少的分區也能夠提升查詢效率。
1.4. 分區
分區鍵(partition key)是從主鍵(primary key)的第一個字段生成的。使用分區鍵分區到哈希表的數據能夠提供更快的查詢。用於查詢的分區越少,查詢的響應時間就越快。
下面是一個分區的例子,假設表t有一個主機id
CREATE TABLE t ( id int, k int, v text, PRIMARY KEY (id) );
分區鍵是從主鍵id生成的,用於在羣集中的各個節點之間進行數據分配。
下面這個例子,是一個複合主鍵
CREATE TABLE t ( id int, c text, k int, v text, PRIMARY KEY (id,c) );
對於具備複合主鍵的表t,第一個字段id用於生成分區鍵,第二個字段c是用於在分區內排序的聚類鍵。使用聚類鍵對數據進行排序能夠提升檢索相鄰數據的效率。
一般,對主鍵的第一個字段進行哈希處理以生成分區鍵,而其他字段則是用於對分區內的數據進行排序的聚類關鍵字。對數據進行分區能夠提升讀寫效率。不是主鍵字段的其餘字段能夠單獨創建索引,以進一步提升查詢性能。
接下來這個例子,id1和id2用戶生成分區鍵,c1和c2用於在分區內排序的聚類關鍵字。
CREATE TABLE t ( id1 int, id2 int, c1 text, c2 text k int, v text, PRIMARY KEY ((id1,id2),c1,c2) );
1.5. 與關係數據模型比較
關係數據庫使用外鍵將數據存儲在與其餘表有關係的表中。關係數據庫的數據建模方法是以表爲中心的。查詢必須使用錶鏈接從多個表中獲取數據,這些表之間存在關係。Apache Cassandra沒有外鍵或關係完整性的概念。Cassandra的數據模型是基於設計高效的查詢,不涉及多個表的查詢。關係數據庫對數據進行規範化以免重複。相反,Cassandra經過在以查詢爲中心的數據模型的多個表中冗餘數據來對數據進行非規範化。若是Cassandra數據模型不能徹底整合用於特定查詢的不一樣實體之間關係的複雜性,則可使用應用程序代碼中的客戶端鏈接(client-side joins)。
1.6. 數據建模示例
假設有一組雜誌數據,屬性有雜誌id、雜誌名稱、出版頻率、出版日期和出版商。
查詢一:列出全部雜誌名稱,包括其發佈頻率。
因爲不須要查詢全部屬性,所以數據模型將僅由ID(用於分區鍵),雜誌名稱和發佈頻率組成,以下圖所示:
查詢二:按出版商列出全部雜誌名稱
輸出列增長出版商,同時用出版商做分區鍵,以下圖所示:
1.7. 定義Schema
對於查詢一,定義以下:
CREATE TABLE magazine_name ( id int PRIMARY KEY, name text, publication_requency text )
對於查詢二,定義以下:
CREATE TABLE magazine_publisher ( publisher text, id int, name text, publication_requency text, PRIMARY KEY (publisher, id) ) WITH CLUSTERING ORDER BY (id DESC)
2. 概念數據建模
首先,建立一個簡單的域模型,它在關係型世界中很容易理解,而後看看如何在Cassandra中將它從關係型映射到分佈式哈希表模型。
以酒店預訂爲例,概念性領域包括酒店、入住酒店的客人、每一個酒店的房間集合、這些房間的價格和空房狀況以及爲客人預訂的預訂記錄。酒店一般還會維護「景點」的集合,這些景點包括公園,博物館,購物畫廊,古蹟或客人在住宿期間可能要參觀的酒店附近的其餘地方。旅館和興趣點都須要維護地理位置數據,以即可以在地圖上找到它們進行混搭,並計算距離。ER圖以下:
一目瞭然,一個酒店有多個房間,一個房間裏面有多個休閒設施,房間的空閒狀況也是分時段的,酒店附近有多個景點,一位顧客能夠訂多個房間,每個預訂記錄對應多個房間。
3. 關係型數據庫設計
當咱們構建一個新的數據驅動應用程序時,將使用關係型數據庫。首先,將領域對象轉化爲一組規範化的表,並使用外鍵引用其餘表中的相關數據。
3.1. RDBMS和Cassandra之間的設計差別
• 沒有鏈接(join)
在Cassandra中,沒法執行鏈接(join)操做。若是你已經設計了一個數據模型而且須要一個鏈接其它表的數據,那麼你不得不在客戶端作這種鏈接,或者建立一個非規範化的第二個表來表示鏈接結果,後一種方法是Cassandra數據建模的首選方法。
• 沒有外鍵引用
在關係數據庫中,能夠在表中指定外鍵來引用另外一個表的主鍵。但Cassandra並無強制要求必須定義外鍵來引用。在表中存儲與其餘實體相關的ID仍然是常見的設計需求,可是級聯刪除等操做不可用。
• 非規範化
在關係數據庫設計中,常常會強調範式重要性,數據庫設計三範式。可是在Cassandra中,遵循範式不是一個好的選擇,由於一般在不遵循範式時執行得最好。
關係數據庫故意去規範化的第二個緣由是須要保留的業務文檔結構。也就是說,有一個封閉的表,它引用了不少外部表,這些表的數據可能會隨着時間的變化而變化,可是你須要將封閉的文檔保存爲歷史記錄中的一個快照。這裏最多見的例子是發票。假設已經有了customer和product表,可能你認爲能夠只製做一個針對這些表的發票,但在實踐中永遠不該該這樣作。顧客或價格信息可能會改變,而後你將失去失去發票單據上發票日期的完整性,這可能違反審計、報告、或法律,並致使其餘問題。能夠看到,在這種狀況下,冗餘是必要的。
在Cassandra中,非規範化是徹底正常的。
• 查詢優先
簡單的來講,關係建模意味着概念領域模型開始,用表來表示領域對象,用表字段來表示領域對象的屬性,而後設置主鍵和外鍵來表示領域對象以前的關係。若是有多對多的關係,還要再建一張中間表。關係世界中的查詢是次要的。只要對錶進行適當的建模,就能夠始終得到所需的數據。即便必須使用幾個複雜的子查詢或鏈接語句,這一般也是正確的。
相比之下,在Cassandra中,不是從數據模型開始的,而是從查詢模型開始的。Cassandra不是先對數據建模,而後編寫查詢,而是先對查詢建模,讓數據圍繞查詢進行組織。考慮應用程序將使用的最多見的查詢路徑,而後建立支持它們所需的表。
• 最佳存儲設計
在關係數據庫中,對於用戶來講,表是如何存儲在磁盤上的一般是透明的,基本不用關心。然而,這是Cassandra中的重要考慮因素。因爲Cassandra表都存儲在磁盤上的獨立文件中,所以將相關列一塊兒定義在同一個表中很是重要。
在Cassandra中建立數據模型時,一個關鍵目標是最小化必須搜索的分區數量,以知足給定的查詢。因爲分區是不跨節點劃分的存儲單元,所以搜索單個分區的查詢一般會產生最佳性能。
• 排序是一個設計決策
在RDBMS中,能夠經過在查詢中使用ORDER BY輕鬆地更改記錄返回的順序。默認的排序順序是不可配置的;默認狀況下,記錄是按照寫入的順序返回的。若是要更改順序,只需修改查詢便可,而且能夠根據任何列進行排序。
可是,在Cassandra中,排序的處理方式有所不一樣。這是一個設計決策。查詢中可用的排序順序是固定的,而且徹底由在CREATE TABLE命令中提供的集羣列的選擇肯定。CQL SELECT語句確實支持ORDER BY語義,但僅按聚簇列指定的順序。
4. 定義應用程序的查詢
既然是查詢驅動的,那麼就先看看針對酒店預訂這個例子,業務都須要查詢,畢竟技術是爲業務服務的,拋開業務彈設計是不可取的。
(畫外音:此刻,忽然想到那句「技術支撐商業、技術拓展商業、技術創做商業」)
言歸正傳,在酒店預訂的例子中,能夠大體梳理出如下業務查詢:
Q1: 查找某個景點附近的酒店
Q2: 查找某個酒店的信息
Q3: 查找某個酒店附近的景點
Q4: 在給定的日期範圍內找到一個可用的房間
Q5: 查找房間的價格和設施
Q6: 經過確認碼查找預訂
Q7: 根據酒店、日期和顧客姓名查找預訂
Q8: 按顧客姓名查找全部預訂
Q9: 查看顧客詳細信息
5. 邏輯數據建模
爲了更生動形象地表示數據模型,這裏採用下面這種圖表方式:
5.1. Hotel邏輯數據模型
按照上面的圖表方式,酒店邏輯數據模型表示以下:
5.2. Reservation邏輯數據模型
一樣的方式,預訂邏輯數據模型表示以下:
6. 物理數據建模
(畫外音:一切設計都是爲了查詢,這話聽着很耳熟,哈哈,Elasticsearch說過,一切設計都是爲了提升檢索的性能)
爲了方便理解,採用以下格式來表示。不用多說,看圖說話:
酒店數據模型:
預訂數據模型:
7. 評估和完善數據模型
7.1. 計算分區大小
首先要考慮的是,表的分區是否太大,或者換句話說,太寬。分區大小是經過存儲在分區中的單元格(值)的數量來度量的。Cassandra的硬限制是每一個分區有20億個單元格(PS:類比Excel中的單元格),可是在達到這個限制以前,可能會遇到性能問題。
分區大小計算公式:N_v = N_r (N_c - N_{pk} - N_s) + N_s
其中:
N_r表示行數
N_c表示列數
N_pk表示主鍵列數
N_s表示靜態列數
N_v表示單元格數量
那麼,單元格數量 = 行數 × (總列數 - 主鍵列數 - 靜態列數) + 靜態列數
以available_rooms_by_hotel_date表爲例,根據公式,該表的單元格總數 = 行數 × (4 - 3 -0) + 0
7.2. 計算磁盤大小
每種數據類型所佔磁盤空間大小不一,粗略地能夠用全部列所佔磁盤大小乘以行數來計算
7.3. 拆分大分區
有一種稱爲bucketing的技術常被用來將數據分割成中等大小的分區。例如,能夠經過向分區鍵添加一個month列(可能表示爲一個整數)來分解available_rooms_by_hotel_date表。與原設計的對好比下圖所示。雖然month列部分重複了date,但它提供了一種很好的方法,能夠在分區中對相關數據進行分組,並且分區不會變得太大。
8. 定義數據庫Schema
schema能夠理解爲數據庫,一個schema就是指一個數據庫
下面是爲hotel keyspace定義的schema:
CREATE KEYSPACE hotel WITH replication = {‘class’: ‘SimpleStrategy’, ‘replication_factor’ : 3}; CREATE TYPE hotel.address ( street text, city text, state_or_province text, postal_code text, country text ); CREATE TABLE hotel.hotels_by_poi ( poi_name text, hotel_id text, name text, phone text, address frozen<address>, PRIMARY KEY ((poi_name), hotel_id) ) WITH comment = ‘Q1. Find hotels near given poi’ AND CLUSTERING ORDER BY (hotel_id ASC) ; CREATE TABLE hotel.hotels ( id text PRIMARY KEY, name text, phone text, address frozen<address>, pois set ) WITH comment = ‘Q2. Find information about a hotel’; CREATE TABLE hotel.pois_by_hotel ( poi_name text, hotel_id text, description text, PRIMARY KEY ((hotel_id), poi_name) ) WITH comment = Q3. Find pois near a hotel’; CREATE TABLE hotel.available_rooms_by_hotel_date ( hotel_id text, date date, room_number smallint, is_available boolean, PRIMARY KEY ((hotel_id), date, room_number) ) WITH comment = ‘Q4. Find available rooms by hotel date’; CREATE TABLE hotel.amenities_by_room ( hotel_id text, room_number smallint, amenity_name text, description text, PRIMARY KEY ((hotel_id, room_number), amenity_name) ) WITH comment = ‘Q5. Find amenities for a room’;
reservation keyspace 的 schema 以下:
CREATE KEYSPACE reservation WITH replication = {‘class’: ‘SimpleStrategy’, ‘replication_factor’ : 3}; CREATE TYPE reservation.address ( street text, city text, state_or_province text, postal_code text, country text ); CREATE TABLE reservation.reservations_by_confirmation ( confirm_number text, hotel_id text, start_date date, end_date date, room_number smallint, guest_id uuid, PRIMARY KEY (confirm_number) ) WITH comment = ‘Q6. Find reservations by confirmation number’; CREATE TABLE reservation.reservations_by_hotel_date ( hotel_id text, start_date date, end_date date, room_number smallint, confirm_number text, guest_id uuid, PRIMARY KEY ((hotel_id, start_date), room_number) ) WITH comment = ‘Q7. Find reservations by hotel and date’; CREATE TABLE reservation.reservations_by_guest ( guest_last_name text, hotel_id text, start_date date, end_date date, room_number smallint, confirm_number text, guest_id uuid, PRIMARY KEY ((guest_last_name), hotel_id) ) WITH comment = ‘Q8. Find reservations by guest name’; CREATE TABLE reservation.guests ( guest_id uuid PRIMARY KEY, first_name text, last_name text, title text, emails set, phone_numbers list, addresses map<text, frozen<address>, confirm_number text ) WITH comment = ‘Q9. Find guest by ID’;
9. 文檔
https://cassandra.apache.org/doc/latest/data_modeling/index.html
https://cassandra.apache.org/doc/latest/data_modeling/intro.html
https://cassandra.apache.org/doc/latest/data_modeling/data_modeling_rdbms.html