使用分片MySQL 数据库的 Rust 应用程序中进行狗狗评分
在 PlanetScale,我们打造了 PlanetScaleDB,这是一个基于开源工具 Vitess 的完全托管数据库即服务平台,它使 MySQL 的水平扩展超越单实例的能力。在这篇博客中,我们将解释 Vitess 和 PlanetScaleDB 中的分片工作原理。
一个分片数据库是由拥有相同关系型架构的多个数据库(分片)组成的集合。Vitess 允许你的应用程序将分片数据库视为一个庞大的单体数据库,而不必担心分片的复杂性。正因如此,你可以在 PlanetScaleDB 上以一个小型数据库开始,并扩展至庞大的规模,而无需更改应用逻辑。
在这篇博客文章中,我们将通过一个名为“Goodest Doggo”的样例狗狗评分应用程序探索 Vitess 的分片概念,包括 VSchema(分片架构)、Vindex(分片索引)以及 Vitess 序列。这个样例应用允许用户为小狗评分,并需要设计为支持大规模扩展。
Vitess 的分片有何不同?
分片数据库是由多个拥有相同关系型架构的数据库组成的集合。许多数据库系统通过分片对表数据进行扩展时,通常忽略了将关联数据共同存储的考虑。这会导致在事务一致性写入和数据读取方面效率低下。而 Vitess 则允许共同存储相关数据。
假设你有一个 users 表,其中包含 id 列作为主键,还有一个 orders 表,其自身的 id 列是主键,同时具备 user_id 列作为辅助键。Vitess 允许你基于 id 列对用户表进行分片,同时基于 user_id 列对订单表进行分片。这确保了特定用户的用户行和订单行位于同一个分片中。一个 VSchema(分片架构)允许你表达这一信息。
换句话说,就像关系型架构用表、列和索引来定义单一数据库内数据的组织方式,Vitess 使用 VSchema 来定义跨多个数据库中分片数据的组织方式。这使我们能够通过高效的机制访问这些分片上的数据。
分片架构的元素有哪些?
就像使用 SQL 语句定义关系型数据库架构一样(通过创建表定义来设置列和索引),你可以通过填写包含以下信息的 JSON 文档来定义 VSchema(或 PlanetScaleDB 中所称的分片方案):
- “sharded”:数据库是否分片
- “vindexes”:定义 VSchema 所使用的所有分片索引类型
- “tables”:关于每个表的条目,其中每个表条目包含以下信息:
- 基于表中某一列的主分片索引
- (可选)辅助分片索引
- (可选)用于生成 id 的序列定义
示例如下,一个名为 “puppers” 的表表示我们的应用中的狗狗数据:
CREATE TABLE IF NOT EXISTS puppers (
`id` BIGINT(22),
`name` VARCHAR(256),
`image` VARCHAR(256),
PRIMARY KEY(id)
);
以下是针对上述表的一个简单 VSchema,用于分片数据库,它展示了分片架构的三个元素:
{
"sharded": true,
"vindexes": {
"hash_vdx": {
"type": "hash"
}
},
"tables": {
"puppers": {
"column_Vindexes": [
{
"column": "id",
"name": "hash_vdx"
}
]
}
}
}
什么是分片索引(Vindex)?
关系型数据库架构包含表、列以及索引等元素。在单一数据库中,索引提高了访问某表中特定行的效率。同样,在分片化数据库中,分片索引(Vindex)允许你快速访问某一行,通过指定方法确定该行所在的分片。
分片索引有主索引和辅助索引两类。一个表只能有一个主分片索引,但可以有多个辅助分片索引。
分片索引如何将行映射到分片?
为了理解这一点,需要掌握关键空间(keyspaces)和关键空间 ID(keyspace_id)的概念。在 Vitess 中,每个分片数据库中的行都有一个 keyspace_id,但这个 keyspace_id 并未存储,而是通过对该行某列的值应用特定分片函数计算得出。
keyspace_id 的范围从 0x00 到 0xFF,这些范围代表整个关键空间。Vitess 分片覆盖这个范围。这也是为什么在 Vitess 中,分片数据库被称为关键空间(keyspace)。每个分片覆盖一个关键空间范围,并根据起始和结束 keyspace_id 命名。例如,在一个包含四个分片的关键空间中,范围划分如下:
10x00-0x40
20x40-0x80
30x80-0xC0
40xC0-0xFF
以下内容继续解释如何定义序列、组织数据以及运行示例应用程序。
什么是 Vitess 序列?
在非分片数据库中,当你需要为某一行分配单调递增的值时,可以定义一个 “自增(autoincrement)” 类型的列。而在分片的场景中,如果直接使用自增类型,你可能会在不同分片中生成重复的值。
Vitess 通过在 VSchema 中定义序列来解决这个问题,从而能够为某个表生成跨分片唯一且单调递增的 ID。Vitess 序列的底层实现依赖一个表中的行,该表存在于次数据库(secondary database)中。Vitess 通过允许缓存一定数量的值来降低写入操作的次数。
让我们通过在上述 puppers
表中使 id
列类型设置为 Vitess 序列来说明操作,这可以通过在 VSchema 中添加以下片段实现:
"auto_increment": {
"column": "id",
"sequence": "pupper_seq"
}
这样可以获得如下结构的分片架构:
{
"sharded": true,
"vindexes": {
"hash": {
"type": "hash"
}
},
"tables": {
"puppers": {
"column_Vindexes": [
{
"column": "id",
"name": "hash"
}
],
"auto_increment": {
"column": "id",
"sequence": "pupper_seq"
}
}
}
}
如何定义序列?
你可能注意到我们使用了 pupper_seq
名称,但并未定义它。这是如何运作的呢?
序列通常定义在一个非分片的关键空间,它与主要关键空间一起存在。在 Vitess 中,这个关键空间通常被称为 “查找关键空间(lookup keyspace)”。以下是定义查找关键空间中的序列的方法:
首先,创建一个名为 pupper_seq
的表,并在其中插入一行以初始化序列。注意 CREATE TABLE
语句中的注释 vitess_sequence
。注释非常重要,因为它被 Vitess 用来将该表与其他存储真实数据的表区分开。
CREATE TABLE IF NOT EXISTS pupper_seq (
`id` INT,
`next_id` BIGINT,
`cache` BIGINT,
PRIMARY KEY (id)
) comment 'vitess_sequence';
INSERT INTO pupper_seq (id, next_id, cache) VALUES (0, 1, 3);
接着,将以下 VSchema 应用于查找关键空间:
{
"sharded": false,
"tables": {
"pupper_seq": {
"type": "sequence"
}
}
}
通过上述两个步骤,可以将 pupper_seq
定义为 Vitess 序列,并在分片关键空间的 VSchema 中使用。
整合所有内容
最后,我们将为 Goodest Doggo 应用程序设计数据库架构。我们将创建两个关键空间:一个名为 lookup
,未分片;另一个名为 puppers
,分片。我们将从两个分片开始,并根据需要进行分片扩展。
数据组织方式
lookup
关键空间包含序列所需的表以及查找分片索引所需的表。puppers
关键空间包含实际数据表。
以下是这两个关键空间的数据库架构和 VSchema:
puppers
关键空间
puppers
关键空间中有三张表:puppers
、ratings
和 users
。
- 我们希望
id
列具有自增类型。 - 因为这是分片数据库,我们在架构中不直接指定自增类型,而在 VSchema 中定义基于序列的
id
列。 - 我们对
puppers
表基于id
列进行分片,而对ratings
表基于user_id
列分片。
以下是 puppers
数据库的架构和 VSchema:
CREATE TABLE `puppers` (
`id` BIGINT(22) NOT NULL,
`name` VARCHAR(256) DEFAULT NULL,
`image` VARCHAR(256) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
CREATE TABLE `ratings` (
`id` BIGINT(22) DEFAULT NULL,
`user_id` BIGINT(22) DEFAULT NULL,
`rating` BIGINT(20) DEFAULT NULL,
`pupper_id` BIGINT(22) DEFAULT NULL,
KEY `pupper_id` (`pupper_id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
CREATE TABLE `users` (
`id` BIGINT(22) NOT NULL,
`email` VARCHAR(64) DEFAULT NULL,
`password` VARBINARY(256) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
对应的 VSchema:
{
"sharded": true,
"vindexes": {
"binary_md5_vdx": { "type": "binary_md5" },
"hash_vdx": { "type": "hash" }
},
"tables": {
"puppers": {
"columnVindexes": [{ "column": "id", "name": "hash_vdx" }],
"autoIncrement": { "column": "id", "sequence": "pupper_seq" }
},
"ratings": {
"columnVindexes": [{ "column": "pupper_id", "name": "hash_vdx" }],
"autoIncrement": { "column": "id", "sequence": "rating_seq" }
},
"users": {
"columnVindexes": [{ "column": "id", "name": "binary_md5_vdx" }]
}
}
}
lookup
关键空间
以下是查找数据库的架构:
CREATE TABLE IF NOT EXISTS pupper_seq (
id INT,
next_id BIGINT,
cache BIGINT,
PRIMARY KEY (id)
) comment 'vitess_sequence';
INSERT INTO pupper_seq (id, next_id, cache) VALUES (0, 1, 3);
CREATE TABLE IF NOT EXISTS rating_seq (
id INT,
next_id BIGINT,
cache BIGINT,
PRIMARY KEY (id)
) comment 'vitess_sequence';
INSERT INTO rating_seq (id, next_id, cache) VALUES (0, 1, 3);
对应的 VSchema:
{
"sharded": false,
"tables": {
"pupper_seq": {
"type": "sequence"
},
"rating_seq": {
"type": "sequence"
}
}
}
想要亲自体验?
我们创建了一个 PlanetScaleDB 快速入门 Demo,它会为上述的狗狗评分应用程序创建数据库并填充数据。
操作步骤:
- 创建 PlanetScaleDB 账号。
- 选择快速入门 Demo VSchema。
- 这将启动一个集群和两个数据库,完成后会提供一个数据库 URL。
- 在此期间,下载并启动应用程序。
- 启动应用程序后,它会提供一个 URL(如 http://localhost:8000),可从本地浏览器访问。
- 应用程序会提示输入快速入门 Demo 生成的集群连接字符串。
- 将连接字符串输入到应用程序中,开始评分狗狗。
- 点击 “Show Data” 标签查看数据如何分布在分片中。
- 还可以通过 MySQL 客户端连接到数据库并运行查询,观察数据分布。
关注公众号:程序新视界,一个让你软实力、硬技术同步提升的平台
除非注明,否则均为程序新视界原创文章,转载必须以链接形式标明本文链接