1. 概述
Cassandra NoSQL 数据库中的数据分布和数据建模与传统关系数据库不同。
在本文中,我们将学习分区键(partition key)、复合键(composite key)和集群键(clustering key)如何构成主键。我们还将看到它们之间的区别。因此,我们将涉及 Cassandra 中的数据分布架构和数据建模主题。
2. Apache Cassandra 架构
Apache Cassandra 是一个开源的 NoSQL 分布式数据库,专为高可用性和线性可扩展性而构建,同时不损害性能。
这是高级别的 Cassandra 架构图:
在 Cassandra 中,数据分布在集群中。此外,集群可能由安装在跨地理区域的数据中心中的机架内的节点环组成。
在更细粒度的层面上,称为 vnodes 的虚拟节点将数据所有权分配给物理机器。Vnodes 通过使用称为一致性哈希的技术,允许每个节点拥有多个小的分区范围来分布数据。
分区器(partitioner)是一个对分区键进行哈希以生成令牌(token)的函数。此令牌值表示一行,并用于标识其在节点中所属的分区范围。
然而,Cassandra 客户端将集群视为统一的整体数据库,并使用 Cassandra 驱动程序库与其通信。
3. Cassandra 数据建模
通常,数据建模是分析应用程序需求、识别实体及其关系、组织数据等的过程。在关系数据建模中,查询通常在整个数据建模过程中是事后考虑的。
然而,在 Cassandra 中,数据访问查询驱动数据建模。查询又由应用程序工作流驱动。
此外,Cassandra 数据模型中没有表连接(table-joins),这意味着查询中的所有所需数据必须来自单个表。因此,表中的数据采用非规范化格式。
接下来,在逻辑数据建模步骤中,我们通过定义键空间(keyspaces)、表甚至表列来指定实际的数据库模式。然后,在物理数据建模步骤中,我们使用 Cassandra 查询语言(CQL)来创建物理键空间——即集群中包含所有数据类型的表。
4. 主键
理解主键在 Cassandra 中的工作方式是一个重要的概念。
Cassandra 中的主键由一个或多个分区键和零个或多个集群键组件组成。这些组件的顺序总是将分区键放在前面,然后是集群键。
除了使数据唯一外,主键的分区键组件在数据放置方面也起着重要的额外作用。因此,它提高了在集群中跨多个节点分布的数据的读取和写入性能。
现在,让我们看看主键的每个组件。
4.1. 分区键
分区键的主要目标是在集群中均匀分布数据并高效查询数据。
分区键用于数据放置,除了唯一标识数据外,它总是主键定义中的第一个值。
让我们尝试使用一个例子来理解——一个包含应用程序日志的简单表,具有一个主键:
CREATE TABLE application_logs (
id INT,
app_name VARCHAR,
hostname VARCHAR,
log_datetime TIMESTAMP,
env VARCHAR,
log_level VARCHAR,
log_message TEXT,
PRIMARY KEY (app_name)
);
以下是上述表中的一些示例数据:
正如我们之前学到的,Cassandra 使用一致性哈希技术来生成分区键(app_name)的哈希值,并将行数据分配给节点内的分区范围。
让我们看看可能的数据存储:
上图是一个可能的场景,其中 app1、app2 和 app3 的哈希值导致每一行分别存储在三个不同的节点中——Node1、Node2 和 Node3。
所有 app1 的日志都进入 Node1,app2 的日志进入 Node2,app3 的日志进入 Node3。
在 where 子句中没有分区键的数据获取查询会导致低效的全集群扫描。
另一方面,在 where 子句中使用分区键,Cassandra 使用一致性哈希技术来识别集群中的确切节点和节点内的确切分区范围。因此,获取数据的查询快速且高效:
select * application_logs where app_name = 'app1';
4.2. 复合分区键
如果我们需要组合多个列值来形成单个分区键,我们使用复合分区键。
同样,复合分区键的目标也是用于数据放置,除了唯一标识数据外。因此,数据的存储和检索变得高效。
以下是结合 app_name 和 env 列形成复合分区键的表定义示例:
CREATE TABLE application_logs (
id INT,
app_name VARCHAR,
hostname VARCHAR,
log_datetime TIMESTAMP,
env VARCHAR,
log_level VARCHAR,
log_message TEXT,
PRIMARY KEY ((app_name, env))
);
上述定义中需要注意的重要事项是 app_name 和 env 主键定义周围的内部括号。这个内部括号指定 app_name 和 env 是分区键的一部分,而不是集群键。
如果我们删除内部括号而只有一个括号,那么 app_name 成为分区键,env 成为集群键组件。
以下是上述表的示例数据:
让我们看看上述示例数据的可能数据分布。请注意:Cassandra 为 app_name 和 env 列组合生成哈希值:
如上所示,可能的场景是 app1:prod, app1:dev, app1:qa 的哈希值导致这三行分别存储在三个不同的节点中——Node1、Node2 和 Node3。
来自 prod 环境的所有 app1 日志都进入 Node1,而来自 dev 环境的 app1 日志进入 Node2,来自 qa 环境的 app1 日志进入 Node3。
最重要的是,为了高效检索数据,获取查询中的 where 子句必须包含所有复合分区键,顺序与主键定义中指定的相同:
select * application_logs where app_name = 'app1' and env = 'prod';
4.3. 集群键
正如我们上面提到的,分区是识别数据放置到的节点内分区范围的过程。相比之下,集群是存储引擎对分区内的数据进行排序的过程,基于定义为集群键的列。
此外,集群键列的识别需要预先完成——因为我们对集群键列的选择取决于我们想要在应用程序中如何使用数据。
分区内的所有数据都存储在连续存储中,按集群键列排序。因此,检索所需的排序数据非常高效。
让我们看看一个具有集群键和复合分区键的表定义示例:
CREATE TABLE application_logs (
id INT,
app_name VARCHAR,
hostname VARCHAR,
log_datetime TIMESTAMP,
env VARCHAR,
log_level VARCHAR,
log_message TEXT,
PRIMARY KEY ((app_name, env), hostname, log_datetime)
);
让我们看看一些示例数据:
如我们在上面的表定义中所见,我们包含了 hostname 和 log_datetime 作为集群键列。假设来自 app1 和 prod 环境的所有日志都存储在 Node1 中,Cassandra 存储引擎按 hostname 和分区内的 log_datetime 对这些日志进行词法排序。
默认情况下,Cassandra 存储引擎按集群键列的升序对数据进行排序,但我们可以通过在表定义中使用 WITH CLUSTERING ORDER BY 子句来控制集群列的排序顺序:
CREATE TABLE application_logs (
id INT,
app_name VARCHAR,
hostname VARCHAR,
log_datetime TIMESTAMP,
env VARCHAR,
log_level VARCHAR,
log_message TEXT,
PRIMARY KEY ((app_name,env), hostname, log_datetime)
)
WITH CLUSTERING ORDER BY (hostname ASC, log_datetime DESC);
根据上述定义,在分区内,Cassandra 存储引擎将按 hostname 的词法升序存储所有日志,但在每个 hostname 组内按 log_datetime 的降序存储。
现在,让我们看看一个在 where 子句中使用集群列的数据获取查询示例:
select * application_logs
where
app_name = 'app1' and env = 'prod'
and hostname = 'host1' and log_datetime > '2021-08-13T00:00:00';
这里需要注意的重要事项是,where 子句应包含与主键子句中定义的相同顺序的列。
5. 总结
在本文中,我们了解到 Cassandra 使用分区键或复合分区键来确定数据在集群中的位置。集群键提供分区内存储数据的排序顺序。所有这些键也唯一标识数据。
我们还涉及了 Cassandra 架构和数据建模主题。
有关 Cassandra 的更多信息,请访问 DataStax 和 Apache Cassandra 文档。