Contents

Cassandra 数据建模

1. 概述

Cassandra 是一个 NoSQL 数据库,可提供高可用性和水平可扩展性,而不会影响性能。

为了从 Cassandra 中获得最佳性能,我们需要围绕特定于手头业务问题的查询模式仔细设计模式。

在本文中,我们将回顾一些关于如何在 Cassandra 中进行数据建模的关键概念。 在继续之前,您可以阅读我们的Cassandra 与 Java 文章以了解基础知识以及如何使用 Java 连接到 Cassandra。

2. 分区键

Cassandra 是一个分布式数据库,其中数据被分区并存储在集群内的多个节点上。

分区键由一个或多个数据字段组成,分区器使用分区键通过散列生成令牌,从而在集群中均匀分布数据

3. 聚类键

集群键由一个或多个字段组成,有助于将具有相同分区键的行集群或分组在一起,并按排序顺序存储它们。

假设我们在 Cassandra 中存储时间序列数据,并且我们希望按时间顺序检索数据。包含时间序列数据字段的聚类键对于有效检索此用例的数据非常有帮助。

注意:分区键和集群键的组合构成了主键,并唯一标识了 Cassandra 集群中的任何记录。

4. 查询模式指南

在开始在 Cassandra 中进行数据建模之前,我们应该确定查询模式并确保它们遵守以下准则:

  1. 每个查询都应该从单个分区中获取数据
  2. 我们应该跟踪一个分区中存储了多少数据,因为 Cassandra 对可以存储在单个分区中的列数有限制
  3. 可以对数据进行非规范化和复制以支持对同一数据的不同类型的查询模式

基于上述指导方针,让我们看看一些现实世界的用例,以及我们如何为它们建模 Cassandra 数据模型。

5. 真实世界数据建模示例

5.1. Facebook帖子

假设我们在 Cassandra 中存储不同用户的 Facebook 帖子。常见的查询模式之一是获取给定用户发表的前“ N ”个帖子。

因此,我们需要按照上述准则将特定用户的所有数据存储在单个分区上。

此外,使用帖子时间戳作为聚类键将有助于更有效地检索前“ N ”个帖子。

让我们为此用例定义 Cassandra 表模式:

CREATE TABLE posts_facebook (
  user_id uuid,
  post_id timeuuid, 
  content text,
  PRIMARY KEY (user_id, post_id) )
WITH CLUSTERING ORDER BY (post_id DESC);

现在,让我们编写一个查询来查找用户Anna的前 20 个帖子:

SELECT content FROM posts_facebook WHERE user_id = "Anna_id" LIMIT 20

5.2. 全国各地的健身房

假设我们正在存储许多国家不同城市和州的不同合作健身房的详细信息,并且我们想获取给定城市的健身房。

另外,假设我们需要返回按开放日期排序的健身房的结果。

基于上述指导方针,我们应该将位于特定州和国家的给定城市的健身房存储在单个分区上,并使用开放日期和健身房名称作为聚类键。

让我们为此示例定义 Cassandra 表模式:

CREATE TABLE gyms_by_city (
 country_code text,
 state text,
 city text,
 gym_name text,
 opening_date timestamp,
 PRIMARY KEY (
   (country_code, state_province, city), 
   (opening_date, gym_name)) 
 WITH CLUSTERING ORDER BY (opening_date ASC, gym_name ASC);

现在,让我们看一个查询,该查询获取美国亚利桑那州凤凰城的前十个健身房的开业日期:

SELECT * FROM gyms_by_city
  WHERE country_code = "us" AND state = "Arizona" AND city = "Phoenix"
  LIMIT 10

接下来,让我们看一个查询,该查询获取美国亚利桑那州凤凰城最近开放的十个健身房:

SELECT * FROM gyms_by_city
  WHERE country_code = "us" and state = "Arizona" and city = "Phoenix"
  ORDER BY opening_date DESC 
  LIMIT 10

注意:由于最后一个查询的排序顺序与创建表时定义的排序顺序相反,因此查询会运行得更慢,因为 Cassandra 将首先获取数据,然后在内存中对其进行排序。

5.3. 电子商务客户和产品

假设我们正在经营一家电子商务商店,并且我们将CustomerProduct信息存储在 Cassandra 中。让我们看一下围绕这个用例的一些常见查询模式:

  1. 获取Customer信息
  2. 获取Product信息
  3. 获取所有喜欢给定ProductCustomer
  4. 获取给定Customer喜欢的所有Product

我们将首先使用单独的表来存储CustomerProduct信息。但是,我们需要引入大量的非规范化来支持上面显示的第 3 和第 4 个查询。

我们将创建另外两个表来实现这一点——“ Customer_by_Product ”和“ Product_by_Customer ”。

让我们看看这个例子的 Cassandra 表模式:

CREATE TABLE Customer (
  cust_id text,
  first_name text, 
  last_name text,
  registered_on timestamp, 
  PRIMARY KEY (cust_id));
CREATE TABLE Product (
  prdt_id text,
  title text,
  PRIMARY KEY (prdt_id));
CREATE TABLE Customer_By_Liked_Product (
  liked_prdt_id text,
  liked_on timestamp,
  title text,
  cust_id text,
  first_name text, 
  last_name text, 
  PRIMARY KEY (prdt_id, liked_on));
CREATE TABLE Product_Liked_By_Customer (
  cust_id text, 
  first_name text,
  last_name text,
  liked_prdt_id text, 
  liked_on timestamp,
  title text,
  PRIMARY KEY (cust_id, liked_on));

注意:为了支持查询、给定客户最近喜欢的产品和最近喜欢给定产品的客户,我们使用“ liked_on ”列作为聚类键。

让我们看一下查询,找到最近喜欢该产品“Pepsi”的十个客户:

SELECT * FROM Customer_By_Liked_Product WHERE title = "Pepsi" LIMIT 10

让我们看看一个名为“ Anna ”的客户查找最近喜欢的产品(最多十个)的查询:

SELECT * FROM Product_Liked_By_Customer 
  WHERE first_name = "Anna" LIMIT 10

6. 低效的查询模式

由于 Cassandra 存储数据的方式,一些查询模式根本没有效率,包括:

  • 从多个分区获取数据——这将需要协调器从多个节点获取数据,将其临时存储在堆中,然后在将结果返回给用户之前聚合数据
  • 基于联接的查询——由于其分布式特性,Cassandra 不像关系数据库那样支持查询中的表联接,因此,使用联接的查询会更慢,还可能导致不一致和可用性问题