ScyllaDB实战——游览 ScyllaDB

366 阅读21分钟

本章内容:

  • 使用 Docker 本地运行 ScyllaDB
  • 使用 nodetool 查看集群的操作详情
  • 创建表并进行数据读取和写入
  • 实验故障模拟并调整一致性级别

用户使用数据库来存储数据。无论是博客帖子、文本消息还是图片元数据,每个数据库的使用场景都从“我想存储数据以供日后使用”开始。虽然关于一致性、容错性和比较优势的讨论非常有用,但这类信息有时会偏离最初的目标。之前我已经详细讲解了 ScyllaDB 的理论知识,但在本章,我们将深入实践。你将启动你的第一个 ScyllaDB 集群,开始动手执行查询并了解 ScyllaDB 的容错保证。

2.1 启动第一个集群

ScyllaDB 是一个为 Linux 环境设计的应用程序。对于家庭用户来说,ScyllaDB 并不直接支持在 Windows 或 macOS 上运行。然而,别担心,ScyllaDB 提供了 Docker 镜像来解决这个问题!Docker 是一个可以通过虚拟机接口运行打包应用程序(称为容器)的工具。你可以在附录 A 中找到更多关于配置 Docker 的指导,帮助你安装并开始使用 Docker。

注意:如果你想深入了解 Docker,Jeff Nickoloff 和 Stephen Kuenzli 合著的《Docker in Action》是一本很好的资源(www.manning.com/books/docke…

为了方便学习和实验 ScyllaDB,你将使用 Docker 在本地机器上启动一个三节点集群(如图 2.1 所示)。你将启动三个容器,分别命名为 scylla-1scylla-2scylla-3。通过运行这三个节点,ScyllaDB 可以展示其分布式优势。接下来,在本章的后续部分,你将让其中一个节点下线,并看到通过调整一致性级别,集群如何继续运行并提供服务。

image.png

你需要能够容忍单个节点的故障,因此至少需要两个节点。在上一章中,你学习了 ScyllaDB 提供了更改查询一致性级别的能力。通过选择 quorum(一致性)级别,操作员可以确保查询成功执行需要大多数节点的响应。在一个两节点的集群中,什么是“大多数”?你需要超过一半的节点,因此操作将要求两个节点响应——即整个集群。这显然不是理想的!通过增加第三个节点,你可以使用 quorum 一致性,并且在一个节点宕机时依然不会失去可用性。尽管如此,在你拥有三个节点之前,你需要先启动第一个节点,所以让我们开始启动第一个节点吧。

2.1.1 启动第一个节点

在创建第一个节点之前,你需要进行一些基础设施的配置工作。你需要为容器配置一个网络,这可以通过 Docker 内置的命令来实现。通过创建你自己的专用网络,集群中的节点可以通过它们的 DNS 名称相互引用,避免直接使用 IP 地址。

运行以下命令创建一个名为 scylla-network 的网络,允许集群中的节点通过计算机上的专用网络进行通信。此命令将创建该网络并输出其 ID(一个十六进制文本块):

$ docker network create scylla-network

现在,网络配置完成,你就可以开始构建集群了。一个 ScyllaDB 集群由一个或多个 ScyllaDB 节点组成。为了发挥其优势,数据库通常需要至少三个节点。因此,你将构建一个三节点集群。Scylla 并没有一个“给我一个集群”的可执行文件(因为它可能需要连接多个在不同机器上运行的节点),所以你必须逐个节点地构建集群。

注意:在本书中的示例,你将运行 ScyllaDB 5.4,这是本文编写时的当前版本。Docker 允许你选择特定版本,因此你可以在此验证相同的行为。尽管 Scylla 在未来可能会添加补丁,但核心功能在该版本中应该保持不变。

首先,运行以下 docker run 命令。这告诉 Docker 你想要运行一个容器。命名为 scylla-1,而不是 Docker 默认生成的巨型哈希值。你还需要指定主机名,因为其他节点需要连接到它以构建集群。--detach 标志表示你希望容器在后台运行,命令返回后容器继续运行。-p 标志指定端口转发,首先是你希望从本地机器分配的端口,后面跟着容器内应该转发到的端口。虽然你在接下来的几章中不会用到这个端口转发,但在构建连接到 Scylla 集群的应用时,它会非常有用。接下来,你指定要运行的容器镜像,这标识了应用程序——在这种情况下是 ScyllaDB:

$ docker run --name scylla-1 --hostname scylla-1 \
  --network scylla-network --detach -p 9241:9042 \
  -p 19241:9042 scylladb/scylla:5.4 --reactor-backend=epoll

当你运行此命令时,Docker 启动容器,该容器是由 ScyllaDB 打包的并运行 ScyllaDB 节点。

注意:我为容器添加了一个额外的参数:--reactor-backend=epoll。此参数告诉 Scylla 使用传统的异步 I/O 实现,避免潜在的资源争用问题,这可能会阻止开发集群的正常运行。

Docker 提供了通过 docker logs 命令访问日志,你可以将容器名称作为参数传入。要查看第一个 ScyllaDB 节点的日志,可以使用 --follow 标志查看其是否成功启动。运行此命令时,该容器的日志将打印到命令行:

$ docker logs scylla-1 --follow

此时,你需要在输出中查找以下两条信息,分别为 servingScylla ... initialization completed。这些消息表明 ScyllaDB 已初始化并准备好提供服务:

...
INFO  2023-10-05 12:36:37,166 [shard 0] init - serving    #1
INFO  2023-10-05 12:36:37,167 [shard 0] init - Scylla↪
↪ version Scylla version 5.4.1-0.20231231.
↪ 3d22f42cf9c3 initialization completed.     #2
  • #1 表示 ScyllaDB 已准备好提供服务
  • #2 表示 ScyllaDB 初始化已完成

注意:启动过程可能需要几分钟,请耐心等待!

如果出现问题,这些信息可能不会出现在日志中。启动过程中以及运行时,ScyllaDB 会记录日志,描述其操作以及遇到的错误。这个日志是分析节点状态的一个很好的工具。为了更快速地调试,你可以使用 ScyllaDB 配套的一个工具——nodetool,它可以检查节点和集群的状态。

2.1.2 你的新朋友:nodetool

nodetool 是一个随 ScyllaDB 附带的命令行工具,包含多种命令,可以用来分析集群并与之进行交互,帮助运维人员和操作员管理集群。它是运行 ScyllaDB 的不可或缺的一部分:你可以查看集群中的所有节点,查看每个表的性能,甚至通过终端从集群中移除节点。

nodetool 通过连接到集群来工作。它可以通过网络连接,也可以进行本地通信。最直接的方法是打开容器中的 shell,在容器内运行命令。要在节点所在的容器中运行命令,你可以使用 docker exec 打开容器中的 bash shell。传入 -it 标志——-i 表示交互模式,命令执行后不会立即退出;-t 则为容器分配伪终端。然后,exec 命令接受一个容器名称来运行命令,以及你想要执行的命令。通过传入 /bin/bash 和前面的参数,你就可以在指定的容器中获得一个 bash shell:

$ docker exec -it scylla-1 /bin/bash

作为一个运维人员,尤其是刚开始启动集群的你,可能会有一个问题:“这个集群的状态怎么样?”巧合的是,nodetool 中你最常使用的命令之一就是用来回答这个问题的。要获取集群状态的概览,可以运行 nodetool status,它列出了每个节点、节点的状态、它所拥有的数据量以及它在集群中的位置。如果你加上 --resolve-ip 参数,还可以解析节点的 IP 地址,显示更友好的 DNS 名称,而不是 IP 地址。

注意:为了表示命令是在容器内运行,我选择在 shell 会话的 $ 前缀加上容器名称:

(scylla-1) $ nodetool status --resolve-ip

运行 nodetool status 后,你会看到类似下面的输出,它正确地识别出集群中有一个节点,但当前的信息可能不太容易理解。它提到了 UN,这难道是说节点属于联合国吗?让我们学习如何分析输出:

Datacenter: datacenter1
=======================
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
--  Address   Load   Tokens Owns Host ID                              Rack
UN  scylla-1  572 KB 256    ?    75ddfa00-9624-4137-b926-429dff20e516 rack1

第一行列出了节点所在的 datacenter(数据中心),这是一个节点分组。如果你是在单台本地机器上运行,或者在云中运行,这可以是一个逻辑分组;但如果你是在本地数据中心运行,这也可以是一个实际的物理分组。一个集群可以由多个数据中心组成,每个数据中心有自己的复制配置。它提供了一种抽象,用于分隔不同的节点组,支持冗余、集群迁移,甚至多区域配置。

往后看,输出的最后一列列出了节点所在的 rack(机架),这是数据中心内的进一步分组。就像数据中心一样,rack 也可以是仅限逻辑的分组,或者它可能对应于现实数据中心中的实际机架,或云中的可用区。你目前使用的是默认配置,但在后续章节中你将学习更多关于 ScyllaDB 如何使用数据中心和机架的信息。

输出的第一列包含一个两字母缩写,表示 nodetool 所称的节点状态(见表 2.1)和生命周期状态(见表 2.2)。状态(Status)表示节点是否处于 Up 或 Down 状态。节点要么是健康的并正在处理流量,要么就不是。生命周期状态(State)则表示节点在集群中的生命周期状态。节点初始状态是 Joining,这个过程开始时较快,但随着集群数据的增多,它将变得更长。完成加入后,节点变为 Normal,标志着它已成为集群的完整成员。如果你想移除一个节点并转移它的数据,它将进入 Leaving 状态。如果你明确地将节点移动到集群中的其他位置,它将进入 Moving 状态。

表 2.1 节点状态的可能值
状态字母含义描述
UUp节点正在处理流量
DDown节点不健康,无法处理流量
表 2.2 节点生命周期状态的可能值
状态字母含义描述
NNormal节点处于正常状态,已成功加入集群
JJoining节点正在加入集群
LLeaving节点正在离开集群
MMoving节点正在移动到其他数据中心或机架

小贴士:如果你忘记了哪些字母代表什么意思,可以在命令行中运行 nodetool status。它会在列出节点之前提供一个有用的提示信息。

当节点已经准备好、健康且能处理流量时,nodetool status 会将其列为 UN。如果看到其他值,说明集群处于不寻常的状态。也许这是有意的——你可能正在添加额外的节点到集群中,导致某个节点处于 Joining 状态。也有可能是意外的——某个服务器可能已经离线。通过检查状态,你可以快速获得集群的高层次状态信息。

在状态信息之后,nodetool 会列出节点的地址,如下所示:

Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
--  Address   Load   Tokens Owns Host ID                              Rack
UN  scylla-1  572 KB 256    ?    75ddfa00-9624-4137-b926-429dff20e516 rack1

通过传入 --resolve-ip 标志(或 -r),nodetool status 会解析 IP 地址并打印出 DNS 名称。如果没有这个标志,你会看到 IP 地址。在这一行中的下一个信息是关于该节点上存储数据的情况。

  • Load 告诉你该节点上存储的数据大小。
  • Tokens 字段包含该节点数据分配的信息。你将在讨论 vnodes(虚拟节点)并深入哈希环时了解更多关于 tokens 的内容。
  • Owns 告诉你该节点在集群中拥有的数据百分比。然而,根据表的配置不同,nodetool 通常无法准确计算这个信息,通常会显示一个 ?
  • 最后,Host ID 是该节点在集群中的标识符。

将这些信息结合起来,你可以获得集群状态的概览。

目前,当你运行 nodetool status 时,你会看到一个非常简短的节点列表,因为单个节点并不能构成一个集群。接下来,让我们再添加几个节点。

2.1.3 构建集群

添加节点与启动第一个节点类似,但需要额外的配置,以便第二个节点能够与第一个节点进行通信并形成集群。当 ScyllaDB 启动时,它假设自己是一个全新集群中的第一个节点,或者是一个已存在集群中的附加节点。在加入现有集群时,操作员必须为加入节点提供种子节点(seed nodes)的配置,以便它能够找到集群中的其他节点。种子节点是集群中已经运行的另一个节点,它是最新的、可以处理流量,并且能够帮助新加入的节点加入集群。

这就像是开始一份新工作:你在第一天会见到经理,经理会把你介绍给团队的其他成员,并帮助你了解团队的运作方式(见图 2.2)。当一个节点加入 Scylla 集群时,它会联系一个种子节点。种子节点和加入节点通过 Scylla 的 gossip 协议交换关于集群的信息,节点之间通过这个协议相互沟通集群成员情况。其他节点会根据加入节点已经“签约”拥有的数据,将数据流式传输给它。

image.png

通过提供 seeds 参数,第二个节点可以知道第一个节点的存在,了解集群中其他节点的信息,并开始接收数据。你可以通过更新的 docker run 命令启动第二个节点,为其设置新节点的值以及 seeds 参数,这样它就知道如何找到集群中的第一个节点。此外,注意使用了新的端口。由于你已经为主机上的第一个容器使用了 9241 和 19241 端口,因此这里需要转发不同的端口:

$ docker run --name scylla-2  --hostname scylla-2 --network scylla-network \
 -d -p 9242:9042 -p 19242:9042 scylladb/scylla:5.4 --seeds=scylla-1 \
 --reactor-backend=epoll

注意:
Scylla 只允许一次添加一个节点。如果你想扩展集群五个节点,你不能同时添加所有五个节点。你必须逐个添加节点,等第一个节点加入完成后,再添加第二个节点,以此类推,直到达到期望的集群大小。如果是因为发生了某种事件需要扩展,你需要耐心等待,每个节点加入都需要时间。因为一个节点在完成加入之前,需要从其他节点流式传输数据,所以这个过程的扩展会受到集群中数据总量的影响。然而,Scylla 即将发布的版本中会有一些新特性使得扩展变得更快,你将在第 8 章中了解更多。

当你将 scylla-1(第一个节点的主机名)作为种子节点参数时,新的节点会在启动时联系 scylla-1,并尝试加入集群。加入过程可能会很长(随着集群数据量的增加,加入过程会变得更长);如果你再次运行 nodetool status --resolve-ip,你可以看到正在加入的节点。如果你在命令前加上 watch,该命令将每隔几秒更新一次。确保在 scylla-1 上运行这个命令,因为 scylla-2 还没有完全启动:

(scylla-1) $ watch nodetool status --resolve-ip

如果你在加入完成之前运行此命令,你会看到 scylla-2 显示为状态 UJ(表示已启动并正在加入中),这表示它正在加入集群:

Datacenter: datacenter1
=======================
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
--  Address  Load    Tokens Owns Host ID                              Rack
UN  scylla-1 ?       256    ?    75ddfa00-9624-4137-b926-429dff20e516 rack1
UJ  scylla-2 944 KB  256    ?    2b38db3b-1b00-41f8-a3d5-1e66afc31db8 rack1

如果 scylla-2 显示为 UN(表示已启动并正常),则表示它已完成加入,成为集群的完整成员。否则,该节点正在通过加入过程,从其他节点获取它现在负责的数据副本。

当节点成功加入集群时,它会打印与 scylla-1 相同的消息,表示初始化完成。此时,运行 nodetool status,你将看到两个节点的状态都为 UN

(scylla-1) $ nodetool status --resolve-ip
Datacenter: datacenter1
=======================
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving

--  Address   Load    Tokens Owns Host ID                              Rack
UN  scylla-1  1008 KB 256    ?    75ddfa00-9624-4137-b926-429dff20e516 rack1
UN  scylla-2  944 KB  256    ?    2b38db3b-1b00-41f8-a3d5-1e66afc31db8 rack1

现在你有了一个包含两个节点的集群,但最终你希望集群中有三个节点(在生产系统中,建议至少有三个节点,以便使用 quorum 一致性并容忍单个节点的故障),因此接下来我们将创建最后一个节点。和 scylla-2 类似,运行另一个 docker run 命令,将 scylla-2 替换为 scylla-3

$ docker run --name scylla-3  --hostname scylla-3 --network scylla-network \
 -d -p 9243:9042 -p 19243:9043 scylladb/scylla:5.4 --seeds=scylla-1 \
 --reactor-backend=epoll

这个命令会启动一个第三个 Scylla 节点,并告诉它使用 scylla-1 作为种子节点。一旦节点加入完成,你可以通过 nodetool status 查看完整的集群,所有节点都已正常运行:

(scylla-1) $ nodetool status --resolve-ip

你可以在这里看到所有三个节点;它们都标记为 UN,并且是你第一个 ScyllaDB 集群的健康成员:

Datacenter: datacenter1
=======================
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
--  Address   Load    Tokens Owns Host ID                              Rack
UN  scylla-2  944 KB  256    ?    2b38db3b-1b00-41f8-a3d5-1e66afc31db8 rack1
UN  scylla-1  572 KB  256    ?    75ddfa00-9624-4137-b926-429dff20e516 rack1
UN  scylla-3  1.11 MB 256    ?    2b769a73-1119-4981-ba82-5a5b92af61b9 rack1

重建集群

如果你的集群发生了故障,你可以按照本章的步骤进行修复,或者使用 Docker Compose 来重建它。在本书的示例代码中,包含了一个 docker compose.yaml 文件,你可以使用它来重新创建集群。该文件可在本书网站(<www.manning.com/books/scyll… GitHub 仓库(github.com/scylladb-in…)中找到。你可以在ch02 目录下运行以下命令来启动集群:

$ docker compose up --detach

通过使用三节点集群并结合 quorum 查询,即需要大多数节点响应的查询,集群可以容忍一个节点的丢失。数据库的目的是存储数据,而不是启动并讨论假设的故障情景。因此,让我们深入研究并将一些数据放入你崭新的集群中。

2.2 创建你的第一个表

当往 ScyllaDB 中添加数据时,数据必须有一个存储位置。在你能读取或写入数据之前,你需要告诉 ScyllaDB 应该如何存储数据以及数据的结构是什么。为了定义数据库如何存储数据,你需要构建一个 模式,即描述数据在数据库中如何组织的数据模型。

在上一章中,你在餐厅下单,指定了日期和时间,并列出了所点的食物及其评价——这是一个非常基础的假设性数据库模式。在这个示例中,你使用了一个名为 ABC Data 的公司,但现在你决定自己运行数据库。你希望这个系统具有可扩展性和容错性,而你的研究将你引导到 ScyllaDB。现在,让我们创建一个模式并试试吧!

2.2.1 Keyspaces 和表

ScyllaDB 将数据存储在 中,表是数据库中零个或多个行的集合。一行是表示数据库中完整条目的数据集合。当你存储数据时,它必须具有某种结构。你不会随意发送一个随机的数字和一串随机的单词;你会通过标签或名称为这些值赋予意义。在 Scylla 中,这些标签及其关联的值被称为 。列通过一个 主键 在数据库中唯一标识:主键是由一个或多个列组成的集合,用来唯一标识这一数据组。表中没有两行可以具有相同的主键;主键总是唯一地标识表中的一行。

表被组织在 Keyspace 中,Keyspace 不仅是一个分组,还包含了配置,用于跨集群复制数据。

在你的食物评论中,你定义了位置、时间、餐食和评论。每个定义的元素都是一列——一个通过名称来标识的数据信息。列具有类型:例如,你可以将用餐时间存储为时间戳,但评论可以是文本或甚至是以整数形式存储的评分。列类型是按表定义的,因此给定列的所有值必须具有相同的类型。一行的主键也决定了这行数据在集群中的存储位置。ScyllaDB 会根据主键将行分组为 分区。你可以决定这些分区;当你设计表时,主键决定了 Scylla 如何对这些行进行分组——这是确保良好性能的关键部分,你将在后续章节中学习到(Scylla 集群的性能最佳状态是数据均匀分布且分区大小相似)。每个分区会被分配给一个节点。图 2.3 展示了这些关系,展示了 Scylla 如何通过一系列“套娃”概念来存储数据。

image.png

注意 如果你曾经使用过关系型数据库,那么这些概念应该非常熟悉;关系型数据库也有表、行、列和主键。ScyllaDB 中所称的 Keyspaces,关系型数据库通常也称为 数据库(或模式),这个命名有时可能会让人感到困惑。然而,自动分区这一概念是 ScyllaDB(以及类似的分布式数据库)独有的;关系型数据库要么手动将数据分片到多个节点,要么复制整个数据集。分区并不是关系型数据库中原生的概念;在关系型数据库中,你通常需要手动实现分区。

通过将所有这些概念——Keyspaces主键分区——结合起来,ScyllaDB 就能存储和提供数据。让我们在实际操作中使用它们:现在终于可以开始使用数据库了。

2.2.2 创建模式

要创建模式,你可以使用一个叫做 cqlsh 的工具,它是 Cassandra Query Language(CQL)Shell 的缩写。这个应用提供了一个命令行界面,供你在集群上运行查询。由于 ScyllaDB 使用与 Cassandra 相同的协议,它可以复用 Cassandra 的 cqlsh 来连接到数据库,而 ScyllaDB 默认就是这样做的。

CQL 与 SQL:有什么区别?

要查询 ScyllaDB,你使用 CQL,这与“sequel”发音相似——这是关系型数据库中 SQL(结构化查询语言)的常见发音之一。这种相似性是故意的:CQL 不仅听起来像 SQL,实际上也长得像 SQL!为了避免重新发明轮子,Cassandra 的创建者选择了一个使用简化 SQL 语法的查询语言。作为 Cassandra 的重写,ScyllaDB 继承了这一做法。由于 Scylla 没有 JOIN,它不支持许多更复杂的 SQL 查询和操作。为了建模和查询这些一对多关系,你需要使用不同的技术,例如在一个叫做 去规范化 的方法中将这些关系的两端存储在一起,更多内容将在第 3 章中介绍。从语法上看,如果你有关系型数据库的背景,你会感到非常熟悉。

nodetool 类似,cqlsh 已经预安装在 Docker 镜像中并运行在你的容器内。你可以通过运行 docker exec 命令来打开 cqlsh 并连接到集群:

$ docker exec -it scylla-1 cqlsh

此时,你会看到一个 Shell 界面,可以在其中运行 CQL 语句。它会告诉你连接到的节点,以及集群中各个组件的版本信息:

Connected to  at 172.18.0.2:9042
[cqlsh 6.2.0 | Scylla 5.4.1-0.20231231.3d22f42cf9c3 | CQL spec 3.3.1
 | Native protocol v4]
Use HELP for help.
cqlsh>

你可以使用这个 Shell 来对 Scylla 集群执行命令。刚开始时,你首先需要创建一个 Keyspace 来存放你的表,然后再创建表并向其中添加数据。必须从创建 Keyspace 开始,否则你的表就没有地方存放。

创建 Keyspace

要创建一个 Keyspace,你需要运行 CREATE KEYSPACE 语句。接着,指定 Keyspace 的名称——我们使用 initial。在 Keyspace 名称后,使用 WITH 来定义 Keyspace 的选项。之后的选项则使用 AND 来添加(稍后你会看到一个例子)。在这里,你只需要设置一个选项——replication,它定义了 Keyspace 如何在集群中复制数据。这个命令使用的是 SimpleStrategy 类,它将每一行分配到由 replication_factor 配置的多个节点:在这个例子中是 3。最后,用分号 ; 结束命令:

cqlsh> CREATE KEYSPACE initial WITH replication = 
{'class': 'SimpleStrategy', 'replication_factor': 3};

提示:如果你按下 Tab 键,cqlsh 会根据可能的选项自动补全。

警告

如果你迫不及待想要在生产环境中使用 ScyllaDB,切勿在复制配置中使用 SimpleStrategy。其他复制策略(稍后你会了解)更为精细,能够为系统提供更高的安全性。

查看已创建的 Keyspace

要查看新创建的 Keyspace,你可以使用 DESCRIBE 命令,并传入你的 Keyspace 名称。记得用分号结束命令!

cqlsh> DESCRIBE initial;

运行 DESCRIBE 命令后,会打印出完整的 CQL 语句,用于重新创建该表。你会立刻发现它与 CREATE KEYSPACE 命令非常相似——它的开头相同,但结尾处有更多信息。Scylla 添加了 durable_writes = trueKeyspace 都有许多配置选项——如果你的语句中没有设置某个选项,ScyllaDB 会将其设置为默认值,并在你运行 DESCRIBE 时显式显示出来。将 durable_writes 默认为 true 是非常好的(它确保你的写入操作始终会写入磁盘);如果你突然发现这个设置没有生效,你会感到非常惊讶,因此,如果没有设置,Scylla 默认假定为 true

CREATE KEYSPACE initial
  WITH replication = {'class': 'SimpleStrategy',
    'replication_factor': '3'}                 #1
  AND durable_writes = true;   #2
#1 你的 `CREATE KEYSPACE` 语句
#2 Scylla 自动添加的选项

现在,你已经有了一个 Keyspace:它的名称是 initial,每个分区的复制因子是 3,且具有持久化写入功能。那么接下来你应该做什么?现在它是空的,需要填充一些数据。Keyspace 包含表——所以,让我们来创建一个表吧!

创建一个表

创建一个表需要运行 CREATE TABLE 语句。你需要指定要创建表的 Keyspace 和表的名称,格式为 keyspace_name.table_name,其中 keyspace_name 指的是现有的 Keyspace,而 table_name 则是你要创建的新表。

接下来,在表名后加上括号,在括号内定义你要存储的列。ScyllaDB 提供了多种数据类型,从简单的文本和数字到更复杂的类型,如时间戳和集合类型。那对于餐厅评论来说,需要存储什么内容呢?你需要知道你在哪里吃饭,可以将餐厅名称作为一个文本类型存储;你吃饭的时间是特定的,因此可以存储就餐的日期和时间,还需要存储你吃了什么。你还需要一个评论来说明食物的好坏。为了实现这些,你可以使用两种类型:texttimestamp

ID 呢?

如果你熟悉关系型数据库,可能会注意到这个示例中缺少自动递增的整数型 ID。PostgreSQL 和 MySQL 允许使用自动设置和递增的数字作为主键,这样你就可以跳过创建唯一标识符的逻辑。但 ScyllaDB 并不支持这种功能。创建这些 ID 的任务应该由哪个节点来完成?它如何在不牺牲速度的情况下保证唯一性,并且避免进行 quorum 调用来计算 ID?如果某个节点崩溃了,会发生什么?

这意味着你需要自己提供 ID(或说自己管理 ID)。我们稍后会讨论一些适用于 ScyllaDB 的 ID 生成策略。

创建表时的主键

最后,创建表时需要指定主键。主键用来唯一标识表中的一行数据。那么,什么能让一条餐厅评论记录唯一呢?仅仅凭借餐厅名称是不够的,因为你可能会多次光顾;而餐厅名称和日期也可能不够,因为有时你会吃到甜点或其他附加食物。为了确保记录的唯一性,你可以将主键设计为餐厅名称、点餐时间和食物名称的组合。

ScyllaDB 使用主键将行分组到分区,并基于分区键在集群中分布数据。那么,什么是分区键?它是主键的第一部分,在这里是餐厅名称。现在你知道你要做什么了,可以将这些内容组合成一个 CREATE TABLE 语句,如下所示。运行这个命令,你就会创建出第一个表!

示例 2.1:第一个表,存储餐厅评论
CREATE TABLE initial.food_reviews (   #1
    restaurant text,           #2
    ordered_at timestamp,
    food text,
    review text,
    PRIMARY KEY (restaurant, ordered_at, food)       #3
);
  • #1:在 initial Keyspace 中创建一个名为 food_reviews 的表。
  • #2:定义了不同的列及其数据类型。
  • #3:指定主键,用来唯一标识每一行数据。

使用 DESCRIBE 命令查看表

再次运行 DESCRIBE 命令查看 Keyspace,你会看到新创建的表以及一些添加的额外设置,并且显示了它们的默认值。不仅是 initial Keyspace,你还会看到 CREATE TABLE 语句:

cqlsh> DESCRIBE initial;

CREATE KEYSPACE initial      #1
    WITH replication =
        {'class': 'SimpleStrategy', 'replication_factor': '3'}
    AND durable_writes = true;

CREATE TABLE initial.food_reviews (    #2
    restaurant text,
    ordered_at timestamp,
    food text,
    review text,
    PRIMARY KEY (restaurant, ordered_at, food)
) WITH CLUSTERING ORDER BY (ordered_at ASC, food ASC)       #3
    AND bloom_filter_fp_chance = 0.01      #4
    AND caching = {'keys': 'ALL', 'rows_per_partition': 'ALL'}
    AND comment = ''
    AND compaction = {'class': 'SizeTieredCompactionStrategy'}
    AND compression = {
        'sstable_compression':
            'org.apache.cassandra.io.compress.LZ4Compressor'
    }
    AND crc_check_chance = 1.0
    AND dclocal_read_repair_chance = 0.0
    AND default_time_to_live = 0
    AND gc_grace_seconds = 864000
    AND max_index_interval = 2048
    AND memtable_flush_period_in_ms = 0
    AND min_index_interval = 128
    AND read_repair_chance = 0.0
    AND speculative_retry = '99.0PERCENTILE';
  • #1:之前的 CREATE KEYSPACE 命令。
  • #2:新的 CREATE TABLE 命令。
  • #3:由于你没有明确指定排序顺序,Scylla 推断出了一种排序顺序。
  • #4:Scylla 显式添加的一些默认选项。

DESCRIBE 命令会将所有的表选项显式列出,我们稍后会讨论其中的一些。另一个添加的内容是 WITH CLUSTERING ORDER BY (ordered_at ASC, food ASC)。主键中不是分区键的部分(即主键中的其他列)被称为 clustering keys,它们负责在分区内对行进行排序。

注意 在接下来的几章中,你将学习如何为 ScyllaDB 选择正确的分区键和聚集键,以设计符合需求的 Schema

DESCRIBE 命令也会显式地列出排序顺序。默认情况下,排序顺序是按照列在定义时的顺序进行升序排列。对于你的表来说,这意味着数据会先按 ordered_at(最早的就餐时间)排序,然后按 food(食物名称)字母顺序排序。根据你查询数据的需求,你可能会选择改变这种排序方式。例如,你可能希望最近的餐点排在前面。我们将在后续章节中进一步讨论各种排序概念和数据建模方法。

你现在已经创建了表,但它是空的。就像一座空房子,你需要给它填充一些东西。因为这是一个数据库表,所以你填充进去的不是椅子、沙发和床,而是数据。让我们开始运行一些查询吧!

2.3 运行你的第一个查询

你已经为数据库构建了 schema,现在是时候开始运行查询了。在本章的其余部分,你将从两家餐馆——Ernie’s Eats 和 Really Good Ramen——添加三条新的餐厅评论。你曾在这些餐厅就餐,享受了美好的餐点,现在,借助崭新的 Scylla 数据库,你可以将这些评论存储起来。

2.3.1 插入数据

终于,我们可以插入一些数据了!就像执行 CREATE KEYSPACECREATE TABLE 命令一样,你可以在 cqlsh 中执行写操作。CQL 中的 INSERT 命令和 SQL 中的 INSERT 命令非常相似,它们使用相同的语法。你指定表名并列出你要插入的列,接着列出这些列对应的值。

你在 Ernie’s Eats 吃到了非常好吃的烤奶酪三明治,那么我们就来保存这条评论。你告诉 ScyllaDB,你要向 initial.food_reviews 表插入数据,并指定要写入的列:restaurantordered_atfoodreview。然后,你给这些列指定相应的值。由于 Ernie’s Eats 中有一个所有格的单引号,因此你需要加一个额外的单引号来转义它,CQL 使用单引号表示字符串。你的 ordered_at 时间戳是以时间字符串的形式指定的,但你也可以选择使用 Unix 纪元以来的毫秒数,或者其他格式选项。

要保存你在 Ernie’s Eats 吃到的烤奶酪三明治的评论,可以在 cqlsh 中运行以下命令:

cqlsh> INSERT INTO initial.food_reviews(
    restaurant,
    ordered_at,
    food,
    review
) VALUES (
    'Ernie''s Eats',
    '2023-05-09 12:00:00',
    'Grilled cheese',
    'Outstanding'
);

后来你又回到了 Ernie’s Eats,这次尝试了炸鸡。你发现它很好吃,而且还有剩余,所以你把它们保存了起来。主键列中的值决定了行的唯一性。对于这个表来说,主键是 restaurantordered_atfood。尽管你又回到了 Ernie’s Eats,你这次有不同的 ordered_atfood 值,因此 Scylla 会将其视为一行新数据并插入到数据库中。现在插入你的评论:

cqlsh> INSERT INTO initial.food_reviews(
    restaurant,
    ordered_at,
    food,
    review
) VALUES (
    'Ernie''s Eats',
    '2023-05-13 11:30:00',
    'Fried chicken',
    'Tasty, saved some for later'
);

现在,你已经有了两行数据——每次到 Ernie’s Eats 的一次评论。以后,你可以更新这条评论,比如尝试剩下的食物之后。

你还很想吃拉面,于是尝试了一个名字听起来非常不错的地方——Really Good Ramen。你创建表格时,Scylla 使用主键的第一部分 restaurant 作为分区键,这个键决定了数据在集群中的分布。Ernie’s Eats 的评论存储在一起,而你从新餐厅添加的评论(Really Good Ramen)则会单独存储。这些行可能会存储在不同的节点上,也可能存储在同一节点上,但它们会存储在磁盘的不同位置。结果证明,这家拉面店确实非常好吃,所以我们也把这个评论添加到数据库中:

cqlsh> INSERT INTO initial.food_reviews(
    restaurant,
    ordered_at,
    food,
    review
) VALUES (
    'Really Good Ramen',
    '2023-05-11 18:00:00',
    'Ramen',
    'it really was good'
);

现在,你的数据库中有三条数据——两条来自 Ernie’s Eats,一条来自 Really Good Ramen。你不必仅凭我说的,你可以通过查询来查看它们。

2.3.2 读取数据

与插入操作类似,CQL 的读取语法也与 SQL 非常相似。你只需要指定一个 SELECT 语句,列出你想要读取的列(或者添加通配符 * 来表示所有列),指定你要读取的表,并通过 WHERE 子句来过滤数据。首先,运行一个不带 WHERE 子句的 SELECT 查询:

cqlsh> SELECT * FROM initial.food_reviews;

通过运行这个查询而不进行数据过滤,你可以查看数据库中的所有行。这些行包含你之前设置的值,并按你隐式提供的排序顺序进行排序:

cqlsh> SELECT * FROM initial.food_reviews;

@ Row 1
------------+--------------------------------------------
 restaurant | Really Good Ramen
 ordered_at | 2023-05-11 18:00:00.000000+0000
 food       | Ramen
 review     | it really was good

@ Row 2
------------+--------------------------------------------
 restaurant | Ernie's Eats
 ordered_at | 2023-05-09 12:00:00.000000+0000
 food       | Grilled cheese
 review     | Outstanding

@ Row 3
------------+--------------------------------------------
 restaurant | Ernie's Eats
 ordered_at | 2023-05-13 11:30:00.000000+0000
 food       | Fried chicken
 review     | Tasty, saved some for later

(3 rows)

格式化

为了格式化显示,我将我的 cqlsh 会话设置为使用 EXPAND ON,这会将结果行垂直显示:

cqlsh> EXPAND ON;

然而,你查询了整个数据库。在真实的生产系统中,这种操作可以说是“绝对不建议”的。每次读取数据时,协调节点(即处理请求的节点)需要与其他可能持有数据的节点进行通信。当你在一个分区内读取数据时,通常是高效的——请求的节点数很少。但当你跨越所有分区读取数据时,协调节点需要向每个分区的每个节点发送请求。这种方式无法很好地扩展,如果在生产系统中运行,可能会导致严重的性能问题。因此,在 ScyllaDB 中,你应该尽量设计查询,使得每次查询都只读取单一分区的数据。

在本书中,你将学到如何设计系统以始终遵循这一点,但如果你从中学到一个关键点,那就是:Scylla 在按分区键查询时性能最好,因此你应该将数据结构化,以便支持这种查询模式(见图 2.4)。

image.png

为了说明一个更好的查询模式,查询 Ernie’s Eats 的食物评价。通过分区键(即餐厅名)进行查询对数据库更加友好。你可以在之前的查询中添加一个 WHERE 子句,限制结果仅显示 Ernie’s Eats 的评价:

cqlsh> SELECT * FROM initial.food_reviews WHERE restaurant = 'Ernie''s Eats';

如预期所示,你只会看到来自 Ernie’s Eats 的评价:

@ Row 1
------------+--------------------------------------------
 restaurant | Ernie's Eats
 ordered_at | 2023-05-09 12:00:00.000000+0000
 food       | Grilled cheese
 review     | Outstanding

@ Row 2
------------+--------------------------------------------
 restaurant | Ernie's Eats
 ordered_at | 2023-05-13 11:30:00.000000+0000
 food       | Fried chicken
 review     | Tasty, saved some for later

(2 rows)

对于这样一个非常小的数据集,你可能不会看到太大的性能差异,因为你只得到两行数据而不是三行,但数据库在查询执行时会更加高效。Scylla 会识别你正在使用分区键进行查询,因此它会通过对给定值进行哈希处理来确定哪个节点拥有该分区。然后,它将查询发送到所有者和副本节点,以确保达到所需的一致性。通过分区键进行查询是直接且数据库友好的,但如果你对查询进行了筛选,但又没有使用分区键进行筛选,会发生什么情况呢?你可以通过按 ordered_at 查询你在 Really Good Ramen 吃饭的日期来测试这一点:

cqlsh> SELECT * FROM initial.food_reviews 
 WHERE ordered_at = '2023-05-11 18:00:00';

你只会看到来自 Really Good Ramen 的评价,这正是你预期的,但在结果之后,你也会看到一个令人不安的警告:

@ Row 1
------------+---------------------------------
 restaurant | Really Good Ramen
 ordered_at | 2023-05-11 18:00:00.000000+0000
 food       | Ramen
 review     | it really was good

(1 rows)

Warnings :
This query should use ALLOW FILTERING and will be rejected in future versions.

如果 Scylla 无法通过分区键直接将查询路由到适当的节点,它会扫描所有分区!这会对性能造成很大的负担,因此会有警告。由于你现在只有三行数据,你可能还能勉强接受这种方式(想象一下当你读到这句话时,配上令人不安的背景音乐)。

如果你想在分区内进一步筛选数据,可以通过在 WHERE 子句中添加 AND 来扩展筛选条件,并设置另一个值。比如,你想获取 Really Good Ramen 在某个日期的所有评价,可以运行以下查询,指定餐厅名和订餐日期:

cqlsh> SELECT * FROM initial.food_reviews
 WHERE restaurant = 'Really Good Ramen'
   AND ordered_at = '2023-05-11 18:00:00';

这时没有警告,Scylla 对你的查询非常满意。Scylla 使用分区键直接查询分区,然后根据你提供的条件进一步筛选数据。这个查询模式你将会反复使用——通过分区键在分区内查询,并通过扩展 WHERE 子句进行进一步的筛选:

@ Row 1
------------+---------------------------------
 restaurant | Really Good Ramen
 ordered_at | 2023-05-11 18:00:00.000000+0000
 food       | Ramen
 review     | it really was good

(1 rows)

当你插入 Ernie’s Eats 的炸鸡评价时,你提到你把一些炸鸡留了下来。假设你想更新这个评价,反映出第二次食用时的美味感受,那么该如何进行更新呢?

2.3.3 更新数据

我要告诉你一个秘密:你已经知道如何更新数据了。你之前做过这件事;实际上,在两节前你就已经做过了。在 ScyllaDB 中,插入和更新非常相似,行为几乎完全一致,唯一的细微差别是行存活状态,我们将在本书的后面讨论。正因为这种结合行为,插入和更新通常会合并成“upsert”操作(插入或更新)。

在 SQL 中,成功的 UPDATE 表示之前存在一行数据,并且其中某些列的值被更新了。然而,在 Scylla 中,数据库并不会在更新之前检查该行是否存在;相反,它直接写入这些值,并假设你知道自己在做什么,更新的是一行已经存在的数据。ScyllaDB 希望尽可能快速地写入数据,它不希望执行较慢的读取操作来确认更新是否真正是更新而不是插入。

要更新数据,你可以运行一个 UPDATE 语句。它以 UPDATE 和你想更新的表名开头。接下来,指定一个 SET 子句:列出你要更新的列及其新值,列和值之间用等号连接。最后,添加一个 WHERE 子句,就像 SELECT 一样,告诉数据库你要更新哪些行。典型的性能警告是——你需要确保更新使用了分区键来筛选行。现在就可以运行下面的语句:

UPDATE initial.food_reviews
  SET review = 'Tasty, saved some for later, reheated well'
  WHERE restaurant = 'Ernie''s Eats'
    AND ordered_at = '2023-05-13 11:30:00'
    AND food = 'Fried chicken';

有用的提示!

总是先写 WHERE 子句,即使你不是在使用 ScyllaDB。这样做可以避免那些忘记写 WHERE 子句而在整个集群中执行更新的尴尬和可怕的时刻。

如果你读取更新后的行,你应该能够看到更新后的值:

cqlsh> SELECT * FROM initial.food_reviews
  WHERE restaurant = 'Ernie''s Eats'
    AND ordered_at = '2023-05-13 11:30:00'
    AND food = 'Fried chicken';

炸鸡确实很好地被重新加热了!你可以看到更新后的评价,但未更改的列保持不变:

@ Row 1
------------+--------------------------------------------
 restaurant | Ernie's Eats
 ordered_at | 2023-05-13 11:30:00.000000+0000
 food       | Fried chicken
 review     | Tasty, saved some for later, reheated well

(1 rows)

读取数据后,你可以确认更新已生效。实际上,更新就是插入——如果你没有更新某个列,它会保持原来的值。如果该行不存在,你的更新将插入新值。我们将在后续章节更深入地探讨这个过程的原理,但目前请记住,更新就是插入,插入也可以视作更新。以下插入语句与更新语句是等效的。如果你运行它,将得到相同的结果:

cqlsh> INSERT INTO initial.food_reviews(
    restaurant,
    ordered_at,
    food,
    review
) VALUES (
    'Ernie''s Eats',
    '2023-05-13 11:30:00',
    'Fried chicken',
    'Tasty, saved some for later, reheated well'
);

有时候,你可能不希望更新数据,而是希望它消失。为了实现数据的“消失”,你可以运行一个 DELETE 查询。

2.3.4 删除数据

继续 CQL 的主题,DELETE 语句与 SQL 中的 DELETE 非常相似。你指定想要 DELETE FROM,然后添加表名和 WHERE 子句,执行时,Scylla 会删除匹配的行。

假设你在镇上走着走着,发现 "Really Good Ramen" 关门了——它将重新开张,提供不同的菜系。因为你不再能去那里,你决定从数据库中删除它的所有评论。要删除这些评论,你可以运行以下查询:

cqlsh> DELETE FROM initial.food_reviews
  WHERE restaurant = 'Really Good Ramen';

沿着分区键的主题,DELETE 语句在使用分区键进行删除时效果最好。否则,Scylla 需要查询所有分区,以找到匹配的行。

到目前为止,你已经学会了如何在 ScyllaDB 中创建、更新、读取和删除数据。在这些示例中,你没有考虑故障——数据库正常工作。然而,ScyllaDB 的一个核心优势就是它的容错能力。接下来,让我们看看它的容错如何在实践中发挥作用,来对你的数据库集群施加一些混乱吧!

2.4 处理故障

当你的副本因子设置为 3 时,你启动了一个三节点集群,以容忍单个节点的故障。如果在查询中使用 quorum 一致性,那么即使丢失了一个节点,你的集群仍然可以正常处理所有查询。因此,如果你关闭一个节点,你仍然能够成功运行 quorum 查询。让我们通过实验来验证这一点。

2.4.1 关闭一个节点

测试 quorum 查询的第一步是关闭一个节点。幸运的是,Docker 使得终止容器变得非常简单。通过运行 docker ps 命令(该命令的名字来源于 Unix 的 ps 进程状态程序),你可以查看所有正在运行的容器:

$ docker ps

nodetool status 类似,docker ps 提供了关于容器状态的许多高层信息。你可以看到你的三个节点——scylla-1scylla-2scylla-3,并且会看到它们的 ID、运行时、端口和名称等信息:

CONTAINER ID   IMAGE             COMMAND                  CREATED
  ↪ STATUS        PORTS↪
  ↪ NAMES
1f2fe0375097   scylladb/scylla:5.1   "/docker-entrypoint.…"   24 hours ago↪
  ↪ Up 24 hours   22/tcp, 7000-7001/tcp, 9042/tcp, 9160/tcp,↪
                   ↪ 9180/tcp, 10000/tcp↪
  ↪ scylla-3
9e81feedc38c   scylladb/scylla:5.1   "/docker-entrypoint.…"   24 hours ago↪
  ↪ Up 24 hours   22/tcp, 7000-7001/tcp, 9042/tcp, 9160/tcp,↪
                   ↪ 9180/tcp, 10000/tcp↪
  ↪ scylla-2
1e76b29391ed   scylladb/scylla:5.1   "/docker-entrypoint.…"   24 hours ago↪
  ↪ Up 24 hours   22/tcp, 7000-7001/tcp, 9042/tcp, 9160/tcp,↪
                   ↪ 9180/tcp, 10000/tcp↪
  ↪ scylla-1

如果你想对查询一致性做一些“疯狂科学家”实验,可以通过运行 docker stop 来关闭 scylla-3 节点:

$ docker stop scylla-3

此命令会输出已停止的容器名称:

scylla-3

如果你再次运行 docker ps,你会看到容器已经停止。scylla-1scylla-2 会在日志中抱怨它们无法连接到 scylla-3。在这个节点关闭的情况下,你可以观察集群的变化效果。

2.4.2 一致性实验

你运行的每个查询都有一个一致性级别,表示查询成功所需响应的节点数量。在 cqlsh 中查看当前的一致性级别,可以运行 CONSISTENCY 命令:

cqlsh> CONSISTENCY;

cqlsh 中,默认的一致性级别是 ONE。这个级别表示只需要一个节点成功响应,查询就算成功:

Current consistency level is ONE.

如果你运行一个读取操作,它仍然会正常工作,因为集群中有两个健康的节点:

cqlsh> SELECT * FROM initial.food_reviews
  WHERE restaurant = 'Ernie''s Eats';

果然,你的查询成功执行,返回以下结果:

@ Row 1
------------+--------------------------------------------
 restaurant | Ernie's Eats
 ordered_at | 2023-05-09 12:00:00.000000+0000
 food       | Grilled cheese
 review     | Outstanding

@ Row 2
------------+--------------------------------------------
 restaurant | Ernie's Eats
 ordered_at | 2023-05-13 11:30:00.000000+0000
 food       | Fried chicken
 review     | Tasty, saved some for later, reheated well

(2 rows)

一致性级别的另一个极端是 ALL。一致性级别有描述性的名称;如你所料,这个级别要求所有节点都必须响应。由于不是所有节点都存活,你可以预期这个级别会导致查询失败。让我们通过将级别更改为 ALL 并重新运行查询来验证这个假设:

cqlsh> CONSISTENCY all;       #1
Consistency level set to ALL.
cqlsh> SELECT * FROM initial.food_reviews
  WHERE restaurant = 'Ernie''s Eats';     #2
NoHostAvailable: ...    #3
  • #1 设置一致性级别为 ALL
  • #2 重新运行查询,要求所有节点都响应。
  • #3 查询失败,报告 NoHostAvailable 错误。

如你所料!你将一致性级别设置为 ALL 后,查询没有成功。由于你使得 scylla-3 节点不可用,查询返回了 NoHostAvailable 错误。

一致性的一种折中方案是使用 local quorum,它要求同一数据中心的大多数节点都成功响应。因为你集群中有三个节点,且只关闭了一个节点,所以仍然应该有多数节点或者法定人数(quorum)能够响应请求。你可以通过将一致性级别设置为 QUORUM 并再次执行读取操作来验证这一点:

cqlsh> CONSISTENCY quorum;         #1
Consistency level set to QUORUM.

cqlsh> SELECT * FROM initial.food_reviews
WHERE restaurant = 'Ernie''s Eats';   #2

查询结果如下:

@ Row 1
------------+--------------------------------------------
 restaurant | Ernie's Eats
 ordered_at | 2023-05-09 12:00:00.000000+0000
 food       | Grilled cheese
 review     | Outstanding

@ Row 2
------------+--------------------------------------------
 restaurant | Ernie's Eats
 ordered_at | 2023-05-13 11:30:00.000000+0000
 food       | Fried chicken
 review     | Tasty, saved some for later, reheated well

(2 rows)         #3
  • #1 设置一致性级别为 QUORUM
  • #2 重新运行查询,要求大多数节点响应。
  • #3 查询成功执行。

查询再次成功;即使 scylla-3 节点宕机,仍然能够返回结果。Quorum 查询 提供比 ONE 更强的一致性。想象一下一个网络分区的情况,假设 scylla-3 节点仍然可用,但无法与其他节点通信:如果你使用 ONE,它可能是唯一响应请求的节点,且可能拥有与集群中其他节点不一致的数据。虽然不如 ALL 强大,但 quorum 查询允许一个节点的丢失,而 ALL 不允许节点丢失。Quorum 是 ScyllaDB 中一致性的一个很好的折中方案,类似于“金发姑娘”一致性(恰到好处)。

总结

ScyllaDB 仅在 Linux 上运行,但你可以使用 Docker 在本地运行 Scylla,而无需配置服务器。

Scylla 集群是通过逐个节点构建的,第一个节点作为种子节点,其他节点连接到它。

nodetool 让你与集群进行交互并分析集群,支持多种命令,你将在本书中多次使用它。

nodetool status 提供集群状态概览,告诉你每个节点是否在线、节点的状态以及它们在集群中的位置。

当一个节点加入集群时,它在 nodetool status 中首次显示为 UJ(正在加入),然后转为 UN(正常运行)。

数据被组织成行,存储在按 keyspace 分组的表中。每行有若干列,每列标识一项数据。行通过主键唯一标识,主键也定义了数据的排序方式。

在一个表中,Scylla 会根据主键的前部分将行分组到不同的分区中,分区作为集群中复制单元,每个分区至少分配给集群中的一个节点。这个复制是由 keyspace 的复制设置驱动的。

cqlsh 是一个命令行界面,用来向集群发送查询,允许你创建表和 keyspace,并进行数据的读取和写入。

Scylla 使用 CQL(Cassandra Query Language)进行查询,CQL 是一种故意与 SQL 非常相似的查询语言,使用 familiar 的 INSERTSELECT 来进行数据的写入和读取。

创建 keyspace 和表是通过 CREATE KEYSPACECREATE TABLE 语句来完成的。

在一个表中,每一列都有一个类型,这个类型必须在 CREATE TABLE 语句中指定。

为了获得最佳性能,应该尽量让每个查询只读取一个分区。每个查询会发送到协调节点,协调节点会联系可能持有该数据的节点;如果你没有通过分区键查询,那么协调节点需要联系集群中的每一个节点。

Scylla 的 UPDATE 语句是 upsert 操作,若已存在值,则更新数据;若不存在,则插入数据。

你可以通过 DELETE 语句从 Scylla 中删除数据。