卡桑德拉。 只懂Oracle如何不死

你好,哈布尔。

我的名字是 Misha Butrimov,我想向您介绍一些有关 Cassandra 的信息。 我的故事对于那些从未接触过 NoSQL 数据库的人来说将会很有用——它有很多你需要了解的实现特性和陷阱。 如果您除了 Oracle 或任何其他关系数据库之外没有见过任何东西,这些东西将救您的命。

卡桑德拉有什么好处? 它是一个 NoSQL 数据库,设计无单点故障,可扩展性良好。 如果您需要为某些数据库添加几个 TB,您只需将节点添加到环中即可。 将其扩展到另一个数据中心? 将节点添加到集群。 增加已处理的 RPS? 将节点添加到集群。 它也在相反的方向上起作用。

卡桑德拉。 只懂Oracle如何不死

她还有什么擅长的? 这是关于处理大量请求。 但多少才算是很多呢? 每秒 10、20、30、40 万个请求并不算多。 每秒还有 100 万个记录请求。 有公司表示他们每秒保留 2 万个请求。 他们可能不得不相信这一点。

原则上,Cassandra 与关系数据有一个很大的区别——它与它们根本不相似。 记住这一点非常重要。

并非所有看起来相同的东西都一样工作

有一次一位同事来找我问:“这是一个 CQL Cassandra 查询语言,它有一个 select 语句,它有 where,它有 and。 我写信但没用。 为什么?”。 将 Cassandra 像关系数据库一样对待是暴力自杀的完美方式。 我不是在宣传它,它在俄罗斯是被禁止的。 你只会设计出错误的东西。

例如,一位客户来找我们说:“让我们为电视剧建立一个数据库,或者为菜谱目录建立一个数据库。 我们会在那里提供菜肴或电视剧和演员的名单。” 我们高兴地说:“走吧!” 只需发送两个字节,几个符号即可完成,一切都会非常快速可靠地工作。 一切都很好,直到顾客过来说家庭主妇也在解决相反的问题:她们有一份产品清单,她们想知道自己想做什么菜。 你死了。

这是因为 Cassandra 是一个混合数据库:它同时提供键值并将数据存储在宽列中。 在 Java 或 Kotlin 中,可以这样描述:

Map<RowKey, SortedMap<ColumnKey, ColumnValue>>

即,还包含排序映射的映射。 该映射的第一个键是行键或分区键 - 分区键。 第二个键是已排序映射的键,是聚类键。

为了说明数据库的分布,我们画三个节点。 现在您需要了解如何将数据分解为节点。 因为如果我们把所有东西都塞进一个(顺便说一句,可以有一千个、两千个、五个——只要你喜欢),这并不是真正的分配。 因此,我们需要一个返回数字的数学函数。 只是一个数字,一个会落入某个范围的长整型。 我们将有一个节点负责一个范围,第二个节点负责第二个范围,第 n 个节点负责第 n 个范围。

卡桑德拉。 只懂Oracle如何不死

该数字是使用哈希函数获取的,该函数应用于我们所说的分区键。 这是在主键指令中指定的列,并且该列将成为映射的第一个也是最基本的键。 它决定哪个节点将接收哪些数据。 在 Cassandra 中创建表的语法与 SQL 几乎相同:

CREATE TABLE users (
	user_id uu id,
	name text,
	year int,
	salary float,
	PRIMARY KEY(user_id)

)

本例中的主键由一列组成,它也是分区键。

我们的用户将如何表现? 有些将前往一个节点,有些将前往另一个节点,有些将前往第三个节点。 结果是一个普通的哈希表,也称为映射,在Python中也称为字典,或者是一个简单的键值结构,我们可以从中读取所有值,通过键读写。

卡桑德拉。 只懂Oracle如何不死

选择:何时允许过滤变成全面扫描,或者不做什么

让我们写一些选择语句: select * from users where, userid = 。 事实证明,就像在 Oracle 中一样:我们编写 select,指定条件,一切正常,用户得到它。 但是,例如,如果您选择具有特定出生年份的用户,Cassandra 会抱怨它无法满足该请求。 因为她根本不知道我们如何分发有关出生年份的数据 - 她只有一列指示为键。 然后她说:“好吧,我仍然可以满足这个要求。 添加允许过滤。” 我们添加指令,一切正常。 就在这时,可怕的事情发生了。

当我们运行测试数据时,一切都很好。 当您在生产中执行查询时,例如,我们有 4 万条记录,那么一切对我们来说都不是很好。 因为允许过滤是一个指令,允许Cassandra从所有节点、所有数据中心(如果这个集群中有很多数据中心)收集该表中的所有数据,然后才对其进行过滤。 这是全扫描的类似物,几乎没有人对此感到满意。

如果我们只需要通过 ID 来获取用户,我们就可以接受。 但有时我们需要编写其他查询并对选择施加其他限制。 因此,我们记住:这都是一个有分区键的映射,但里面是一个排序的映射。

她还有一把钥匙,我们称之为聚类钥匙。 该键又由我们选择的列组成,借助该键,Cassandra 了解其数据的物理排序方式以及如何位于每个节点上。 也就是说,对于某些分区键,聚类键将准确地告诉您如何将数据推送到这棵树中,以及它将在其中占据什么位置。

这实际上是一棵树,在那里简单地调用了一个比较器,我们以对象的形式向其传递一组特定的列,并且它也被指定为列的列表。

CREATE TABLE users_by_year_salary_id (
	user_id uuid,
	name text,
	year int,
	salary float,
	PRIMARY KEY((year), salary, user_id)

请注意主键指令;它的第一个参数(在我们的例子中为年份)始终是分区键。 它可以由一列或多列组成,这并不重要。 如果有多个列,则需要再次删除括号中的列,以便语言预处理器知道这是主键,而其后面的所有其他列都是聚类键。 在这种情况下,它们将按照它们出现的顺序在比较器中传输。 也就是说,第一列更重要,第二列不太重要,依此类推。 例如,我们如何编写数据类的 equals 字段:我们列出字段,并为它们编写哪些字段较大,哪些字段较小。 在 Cassandra 中,相对而言,这些是数据类的字段,将应用为其编写的 equals。

我们设置排序并施加限制

您需要记住,排序顺序(降序、升序等)是在创建键时同时设置的,并且以后无法更改。 它从物理上决定了数据的排序方式和存储方式。 如果您需要更改聚类键或排序顺序,则必须创建一个新表并将数据传输到其中。 这不适用于现有的。

卡桑德拉。 只懂Oracle如何不死

我们在表中填满了用户,发现他们落入一个环中,首先按出生年份,然后在每个节点上按工资和用户 ID。 现在我们可以通过施加限制来选择。

我们的工作又出现了 where, and,我们有了用户,一切又恢复正常了。 但是,如果我们尝试仅使用 Clustering key 的一部分,并且是不太重要的 key,那么 Cassandra 会立即抱怨它无法在映射中找到该对象(该对象具有 null 比较器的这些字段)和该对象所在的位置。那是刚刚设定好的,-他躺着的地方。 我将不得不再次从该节点提取所有数据并对其进行过滤。 这是节点内全扫描的模拟,这很糟糕。

在任何不清楚的情况下,创建一个新表

如果我们希望能够通过ID、年龄、薪资来定位用户,我们应该怎么做? 没有什么。 只需使用两个表即可。 如果您需要以三种不同的方式接触用户,就会有三个表。 我们节省螺丝空间的日子已经一去不复返了。 这是最便宜的资源。 它的成本远低于响应时间,这可能对用户不利。 对于用户来说,一秒钟内收到东西比十分钟内收到东西要愉快得多。

我们用不必要的空间和非规范化数据来换取良好扩展和可靠运行的能力。 毕竟,事实上,一个由三个数据中心组成的集群,每个数据中心有五个节点,具有可接受的数据保存水平(当没有丢失任何数据时),能够在一个数据中心死亡后完全幸存下来。 其余两个节点各有两个节点。 只有在此之后,问题才开始出现。 这是一个非常好的冗余,值得几个额外的 SSD 驱动器和处理器。 因此,为了使用 Cassandra,它绝不是 SQL,其中没有关系、外键,您需要了解简单的规则。

我们根据您的要求设计一切。 最主要的不是数据,而是应用程序如何使用它。 如果它需要以不同的方式接收不同的数据或者以不同的方式接收相同的数据,我们必须以方便应用程序的方式来放置。 否则,我们将在全扫描中失败,Cassandra 不会给我们带来任何优势。

数据非规范化是常态。 我们忘记了范式,我们不再拥有关系数据库。 如果我们把某样东西放下 100 次,它就会躺下 100 次。 还是比停下来便宜。

我们选择用于分区的键,以便它们正常分布。 我们不希望密钥的散列落在一个狭窄的范围内。 也就是说,上面例子中的出生年份是一个坏例子。 更准确地说,如果我们的用户按出生年份正态分布,那就太好了;如果我们谈论的是五年级学生,那就不好了——那里的划分不会很好。

排序在聚类键创建阶段选择一次。 如果需要更改,我们将不得不使用不同的键更新表。

最重要的是:如果我们需要以 100 种不同的方式检索相同的数据,那么我们将拥有 100 个不同的表。

来源: habr.com

添加评论