Contents

Cassandra分区密钥,复合键和聚类密钥

1. 概述

Cassandra NoSQL 数据库中的数据分布和数据建模与传统关系数据库中的不同。

在本文中,我们将了解分区键、复合键和集群键如何构成主键。我们还将看到它们有何不同。因此,我们将涉及 Cassandra 中的数据分发架构和数据建模主题。

2. Apache Cassandra 架构

Apache Cassandra 是一个开源 NoSQL 分布式数据库,旨在实现高可用性和线性可扩展性,而不会影响性能。

这是高级 Cassandra 架构

/uploads/cassandra_keys/1.jpeg

在 Cassandra 中,数据分布在一个集群中。此外,一个集群可能由一圈节点组成,这些节点 排列在安装在跨地理区域的数据中心的机架中。

在更细粒度的级别上,称为vnode 的虚拟节点将数据所有权分配给物理机。Vnodes 通过使用一种称为一致性散列 的技术来分发数据,从而允许每个节点拥有多个小分区范围。

分区器是一个对分区键进行散列以生成令牌的函数。此标记值表示一行,用于标识它在节点中所属的分区范围。

然而,Cassandra 客户端将集群视为一个统一的整体数据库,并使用 Cassandra 驱动程序库与其通信。

3. Cassandra 数据建模

通常,数据建模是分析应用程序需求、识别实体及其关系、组织数据等的过程。在关系数据建模中,查询通常是整个数据建模过程中的事后考虑。

然而,在 Cassandra 中,数据访问查询驱动数据建模 。反过来,查询由应用程序工作流驱动。

此外,Cassandra 数据模型中没有表连接,这意味着查询中的所有所需数据都必须来自单个表。因此,表中的数据采用非规范化格式。

接下来,在逻辑数据建模步骤中,我们通过定义键空间、表甚至表列来指定实际的数据库模式。然后,在物理数据建模步骤中,我们使用 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)
);

以下是上表中的一些示例数据:

/uploads/cassandra_keys/3.png

正如我们之前所了解的,Cassandra 使用一致的散列技术来生成分区键 ( app_name ) 的散列值并将行数据分配给节点内的分区范围。

让我们看看可能的数据存储:

/uploads/cassandra_keys/5.png

上图是一个可能的场景,其中app1app2app3的哈希值导致每一行存储在三个不同的节点中——分别是Node1Node2Node3

所有app1日志都转到Node1app2日志转到Node2app3日志转到Node3

where子句中没有分区键的数据获取查询会导致低效的全集群扫描。

另一方面,通过where子句中的分区键,Cassandra 使用一致的散列技术来识别集群中节点内的确切节点和确切的分区范围。因此,获取数据查询快速高效:

select * application_logs where app_name = 'app1';

4.2. 复合分区键

如果我们需要组合多个列值来形成单个分区键,我们使用复合分区键。

同样,复合分区键的目标是用于数据放置,除了唯一地标识数据。结果,数据的存储和检索变得高效。

下面是一个表定义示例,它结合了app_nameenv列以形成复合分区键:

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_nameenv, primary key definition周围的内括号。**这个内括号指定app_nameenv是分区键的一部分,而不是集群键。

如果我们去掉内括号并且只有一个括号,那么app_name成为分区键,而env成为集群键组件

这是上表的示例数据:

/uploads/cassandra_keys/7.png

让我们看一下上述样本数据的可能数据分布。请注意:Cassandra 为app_nameenv列组合生成哈希值:

/uploads/cassandra_keys/9.png

正如我们在上面看到的,app1:prod、 app1:dev、 app1:qa 的哈希值可能导致这三行存储在三个单独的节点中——分别是Node1Node2Node3

prod环境中的所有app1日志都转到Node1,而dev环境中的app1日志转到Node2,而qa环境中的app1日志转到Node3

最重要的是,为了有效地检索数据,fetch 查询中的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)
);

让我们看一些示例数据:

/uploads/cassandra_keys/11.png

正如我们在上面的表定义中看到的,我们已经包含了hostnamelog_datetime作为集群键列。假设来自 app1prod环境的所有日志都存储在Node1中,Cassandra 存储引擎按分区内的hostnamelog_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子句应该包含与主键子句中定义的顺序相同的列。