Azure 上的 Java 应用和数据现代化 - 第五部分:数据现代化





2.00/5 (2投票s)
如何创建 Citus 数据库,然后适配 Java 应用使用它。
PetClinic
应用程序依赖于关系数据库,以确保它能捕获诊所已治疗的宠物,并详细说明宠物、主人和兽医之间的关系。PostgreSQL 数据库非常健壮,但随着诊所的发展,数据库必须进行扩展以支持这种增长。虽然我们可以将数据库运行在更强大的机器上,但单台机器能够并行处理的任务数量是有限的。
当表包含大量行但每次事务只需要其中几行时,关系数据库会遇到困难。例如,“visits”表可能包含 100,000 次就诊的记录,但员工可能只需要查看其中十几次的记录来安排下个月的就诊。此外,当只有六名兽医在相关地点工作时,搜索涉及 100 名兽医的就诊记录是没有必要的。
幸运的是,有一种数据分区的方法可以清除杂乱的数据,同时保留数据的可用性。这个过程称为分片。正如微软关于分片模式的文章中所讨论的,分片旨在将数据划分为更小的集合,并将每个集合保存在一个节点上——数据库的一个独立实例。这减少了节点在查找所需信息时必须处理的数据量。
此外,将数据分配给单个节点可确保有可用资源来运行任务。当负载较轻时,一台物理机可能运行多个节点。但随着日常交易量增长并需要扩展时,节点可以迁移到独立的机器上。
在像 PetClinic
这样的事务性应用程序中,开发人员通常根据查询方式对数据进行分片。在宠物诊所,大多数日常操作都涉及安排就诊或查看日程。这些任务通常围绕就诊数据进行。如果我们决定因诊所数据的规模和易变性而对其进行分片,我们需要一个稳定、唯一且在所有就诊记录中都一致的值。
例如,我们在每位兽医加入诊所时为其分配一个唯一值。所有兽医的值集不受影响,因此此键是稳定的。此外,所有就诊记录都链接到一位兽医,因此我们可以使用每位兽医的唯一 ID 将整个就诊记录集划分为更小的块。
Citus 将 PostgreSQL 扩展为一个分布式数据库。微软的实现是Azure Database for PostgreSQL — Hyperscale (Citus)。Hyperscale (Citus) 使我们能够通过将数据分布到多个节点来构建世界一流的关系数据库——例如 PetClinic
的数据库。我们可以将节点放置在离办公室最近的区域。因此,兽医的数据与办公室在同一区域,消除了网络延迟并保持了报告或分析的数据可用性。
宠物诊所的全球扩张是 Azure Database for PostgreSQL 的一项直接的进展。它只不过是 PostgreSQL,而已被分布式化。这还提供了 NoSQL 数据库的性能,同时避免了重新设计数据和应用程序以在 Cosmos DB 或 Cassandra 等数据库上运行的麻烦。
虽然 Citus 非常强大,但它至少需要三台机器。这样扩展的成本可能比将数据库迁移到一台更大的机器上高得多。请谨慎做出此决定。
根据复制要求,我们可以通过实施多个数据库,利用夜间批处理流程复制数据,并在 Azure Functions 上运行微服务来处理分布式数据,从而节省运营成本。这需要时间和精力来设计和实施,因此 Citus 是迁移到云原生架构以实现即时扩展的更直接的第一步。
使用 Cosmos DB 来处理此应用程序是可行的,但需要对数据进行反规范化。Spring Boot 提供了 Java Persistence Architecture (JPA) 的实现,PetClinic
的实现依赖于此。微软提供了Azure Spring Data Cosmos 客户端库(Java 版),这可能有助于替换 JPA,但反规范化数据意味着重写应用程序,这超出了本文的范围。
无论如何,以下过程只需进行适度的更改即可交付高性能应用程序,从而为考虑如何迁移到 NoSQL 环境留出时间。
下面的演示将使用 API 对应用程序进行重构,使其真正云原生。它强调了基于服务的体系结构可以隐藏数据实现,从而提供另一种将应用程序与 NoSQL 数据库更改隔离开来的方法。
实现
在继续之前,您必须实现您的集群。您可以使用本系列第二篇文章中详细介绍的流程将数据迁移到此集群。
创建数据库
Citus 是 PostgreSQL 的一种实现,因此实现数据库的过程与上一篇文章中的过程类似。
首先登录 Azure 门户,然后在主页上,点击创建资源。然后,在搜索框中键入“Azure Database for P”。在结果列表中,选择Azure Database for PostgreSQL。
在下一页上,点击创建,然后向下滚动直到看到Hyperscale (Citus) 服务器组并点击创建。
选择您的资源组或创建一个新的资源组
要开始配置服务器组,请填写服务器组名称并选择PostgreSQL 版本。
接下来,配置服务器组。对于本教程,请选择基本层。
然后向下滚动以配置节点。选择可用的最小节点。
设置好后,点击保存。这样就完成了服务器组的配置。
向下滚动并设置管理员帐户密码,然后点击下一步: 网络。
默认情况下,Citus 会阻止对数据库的所有访问。您将配置从本地计算机和其他 Azure 服务的访问。配置好网络后,点击审核 + 创建。
查看配置并点击创建。部署需要一段时间,但完成后,您将看到以下内容
迁移数据
在导入数据之前,您必须创建 PetClinic
角色。
在 Azure 门户中,导航到资源并点击角色。
在新窗格中,点击添加。在下一个窗格中,设置用户名和密码。请注意,您不能使用 PetClinic
应用程序中使用的 petclinic
密码。
添加角色后,导入数据。为了管理分区表,Citus 限制了创建数据库的能力,因此您无法创建 PetClinic
数据库。相反,您将使用 PSQL(PostgreSQL 的终端前端)在 Citus 数据库中创建一个由 petclinic
用户拥有的 petclinic
schema。端口始终是 5432,命令的主机名格式为
c.<Server Group name>.postgres.azure.com
最后两个参数是用户名 Citus 和数据库名称 Citus。使用此命令启动 PSQL
C:\Projects\PetClinic\postgresDb>psql
--host=c.pet-clinic-demo-group.postgres.database.azure.com --port=5432 citus citus
密码是在设置数据库时设置的密码。在本演示中,它是 P@ssword。创建 petclinic
schema 的命令是
CREATE SCHEMA petclinic AUTHORIZATION citus;
将访问权限设置为用户的 petclinic
的命令是
GRANT ALL ON SCHEMA petclinic to petclinic;
导入数据
现在 schema 已存在,导入数据。在将表分发到分片时,某些值会存在于多个位置。例如,当前数据库依赖于序列表为大多数表生成下一个 ID。但是,每个节点都有这些表的独立副本,无法同步值。
因此,Citus 会阻止您分发任何生成标识的表。此应用程序中的几乎所有表都使用此机制来生成主键,因此您必须进行更改才能分发表。
最佳解决方案是将 ID 列更改为 varchar
以接受通用唯一标识符 (UUID)。但是,这意味着更改创建键的表中的值以及所有引用这些键的外键。它还意味着修改应用程序,以便在添加记录时提供这些值。
相反,此演示使用了 PostgreSQL 的一个旧功能:SERIAL
关键字。它提供了必要的行为,并且 Citus 可以分发表,而无需更改应用程序以提供键。
这只是迁移数据所需的唯一一项更改。您还必须通过以下方式修改原始数据库导出脚本:
- 连接到 Citus 数据库(
petclinic
数据库不存在) - 删除创建
petclinic
数据库的命令 - 更改引用以指向
petclinic
schema - 删除
visits
表上的主键约束(稍后将添加新主键) - 将所有
GENERATED
值更改为SERIAL
值 - 删除重置序列值的命令(将在导入数据后执行)
要进行这些更改,请连接到 Citus 数据库。
\connect citus
注释掉以下命令,以避免尝试创建数据库
--CREATE DATABASE petclinic WITH TEMPLATE = template0 ENCODING = 'UTF8' LOCALE = 'English_United States.1252';
--ALTER DATABASE petclinic OWNER TO petclinic;
将 public
替换为 petclinic
。您可以在 citusData2.pg 文件中查看这些更改。
删除 visits
表上的主键约束(稍后将添加新主键)。
--ALTER TABLE ONLY petclinic.visits
-- ADD CONSTRAINT visits_pkey PRIMARY KEY (id);
将主键类型从 integer
更改为 SERIAL
。
这是每个表更改的示例
CREATE TABLE petclinic.owners (
id SERIAL,
first_name text,
last_name text,
address text,
city text,
telephone text
);
脚本修改了所有表以添加生成值。
以下是注释掉的代码示例,该代码更新了 vets
表。您已对所有表进行了此更改。
--ALTER TABLE petclinic.vets ALTER COLUMN id ADD GENERATED BY DEFAULT AS IDENTITY (
-- SEQUENCE NAME petclinic.vets_id_seq
-- START WITH 1
-- INCREMENT BY 1
-- NO MINVALUE
-- NO MAXVALUE
-- CACHE 1
--);
删除重置生成值序列的命令。这是更改的示例
-- Name: owners_id_seq; Type: SEQUENCE SET; Schema: petclinic; Owner: petclinic
--SELECT pg_catalog.setval('petclinic.owners_id_seq', 11, true);
进行这些更改后,运行脚本导入数据。在此命令中,使用 -U petclinic
选项设置用户名,使用 -f citusData2.pg
设置包含导入命令的文件名。最后一个参数将数据库设置为 citus
C:\ContentLab\Projects\PetClinic\postgresDb>psql
--host=c.pet-clinic-demo-group.postgres.database.azure.com --port=5432 -U petclinic -f citusData2.pg citus
完整的列表很长,将在此处截断。开头部分如下
下一张图片显示了结尾。您不应看到任何错误消息。
分发表
引言讨论了使用 vet_id
对 visits
数据进行分片。但是,PetClinic
应用程序在当前数据设计中并未在 visits
表中实现 vet id
列。此外,兽医与宠物或主人之间没有关联。因此,无法跟踪哪位兽医正在治疗宠物。
您将通过向 visits
表添加 vet id
列来解决此问题。然后,您将添加一个新主键,该主键是 vet_id
和 table id
值的复合。之后,您将分片并分发访问数据。最后,您将把其余表作为引用表进行分片,使其在每个节点上本地可用。
首先,添加 Vets
列。此时您将不实现 not null
约束。
使用以下 PSQL 命令执行此操作。
使用以下命令启动 PSQL
C:\ContentLab\Projects\PetClinic\postgresDb>psql
--host=c.pet-clinic-demo-group.postgres.database.azure.com --port=5432 -U petclinic citus
然后使用以下命令添加新列
alter table visits add column vet_id integer;
之后,添加外键约束。这些命令在屏幕截图中有两行。
alter table visits add foreign key (vet_id) references vets(id);
接下来,填充空的 vet_id
值。由于 PetClinic
从未将每次就诊分配给特定的兽医,您有两个选择:
- 将所有就诊分配给现有的
vet
。 - 创建一个名为“
未分配
”的vet
,并将所有就诊分配给该兽医。
实现第二个选项,以便应用程序可以跟踪哪些就诊未与兽医相关联。使用您当前的 PSQL 会话执行此操作。
首先,使用基本的 SELECT
语句检索表中所有兽医的列表,以查找最高索引
select * from vets;
然后使用 SQL INSERT
插入“未分配
”
Insert into vets (id, first_name, last_name)
Values (7, ‘Not’, ‘Assigned’);
并使用 SQL SELECT
验证更改
select * from vets;
现在您有了新兽医的索引,您可以将其插入 visits
表并使用以下命令验证更改
update visits set vet_id=7;
select * from visits;
现在数据已完成,并且每一行在构成新键的列中都有有效值,添加复合键
alter table visits add primary key (id, vet_id);
SERIAL
功能会自动设置 not null
约束。\d
visits 命令描述了表。
在分片 visits
表之前,您必须定义引用表。Citus 提供了一个函数来实现这一点,您可以使用 PSQL SELECT
命令执行该函数。以下屏幕截图是 owners
表的,但 visits
表之外的所有其他表都需要此更改
select create_reference_table(‘owners’);
最后,您可以使用 Citus create_distributed_table
函数分片 visits
表
select create_distributed_table(‘visits’, ‘vet_id’ );
要验证数据库是否正常运行,请运行以下查询(为便于阅读,此处分为多行显示)
select vets.first_name, vets.last_name, pets.name,
visits.visit_date, visits.description,
owners.first_name, owners.last_name
from pets
join owners
on pets.owner_id = owners.id
left join visits
on visits.pet_id = pets.id
join vets
on visits.vet_id = vets.id;
如果您想了解更多关于分片表内容的信息,请尝试 select * from citus_shards
。PostgreSQL 提供了 citus_shards
视图来概述分片。您可以在 Citus 表和视图中找到有关这些表和视图的更多信息。
连接应用程序
要将 PetClinic
连接到新数据库,您必须从 Azure 获取连接字符串。
此外,您还可以使用它来更新 application-postgres.properties 文件
spring.datasource.url=${POSTGRES_URL:jdbc:postgresql://c.pet-clinic-demo-group.postgres.database.azure.com:5432/citus}
spring.datasource.username=${POSTGRES_USER:petclinic}
spring.datasource.password=${POSTGRES_PASS:P@ssword}
更新文件后,重新启动 PetClinic
,打开浏览器并检索您的应用程序主机的 URL。您可以看到应用程序已基本准备就绪。
但是,当前实现不支持添加新的就诊记录。尝试这样做会导致错误。
应用程序更新
要使用户能够将兽医与就诊记录相关联
- 在“新就诊”页面添加兽医列表供用户选择。
- 在 visit.java 中,添加
pet_id
和vet_id
以及 getter 和 setter。 - 在 VisitController.java 中,添加一个 vet 存储库。
- 在 VisitController.java 中,修改
loadPetWithVisit
函数以获取兽医列表并将其添加到模型中
向新就诊页面添加兽医列表
通过在添加就诊按钮上方添加兽医列表来更新 createOrUpdateVisitForm.html 页面,以便用户可以选择一个。
提供的 HTML 添加如下
<div class="form-group">
<label class="col-sm-2 control-label">Vet</label>
<div class="col-sm-10">
<select class="form-control" th:object="${vetList}" id="vetId" name="vetId">
<option th:each="vet : ${vetList}"
th:value="${vet.id}"
th:text="${vet.firstName}+' '+${vet.lastName}"></option>
</select>
<span class="fa fa-ok form-control-feedback" aria-hidden="true"></span>
</div>
</div>
您可以使用 inputField.html 中提供的输入片段,但它不支持选择选项,因此您直接添加了逻辑。
更新 Visit.java
为了支持将表单数据传输到网页,请添加两个字段
private int vet_id;
private int pet_id;
添加相应的 getter 和 setter
public int getVetId() {
return this.vet_id;
}
public void setVetId(int vet_id) {
this.vet_id = vet_id;
}
public int getPetId() {
return this.pet_id;
}
public void setPetId(int pet_id) {
this.pet_id = pet_id;
}
更新 Visit Controller
更改构造函数以接受 VetRepository
的实例,以便您可以获取兽医列表
public VisitController(OwnerRepository owners, VetRepository vets) {
this.owners = owners;
this.vets = vets;
}
修改 loadPetWithVisit
函数将该列表传递到模型中的网页
Owner owner = this.owners.findById(ownerId);
Pet pet = owner.getPet(petId);
Collection<Vet> vetList = this.vets.findAll();
model.put("pet", pet);
model.put("owner", owner);
model.put("vetList", vetList);
Visit visit = new Visit();
pet.addVisit(visit);
return visit;
这样就完成了所需的更改。如果您重新构建并重新部署应用程序,您可以返回“新就诊”页面,为就诊记录选择一位兽医,并成功添加就诊记录。
摘要
将 PetClinic
应用程序迁移到高性能关系数据服务,可以使您的实现相应地扩展,同时使用您现有的数据结构和 Spring Boot 对 Java Persistence Architecture 的支持。此外,这限制了使应用程序正常运行所需的更改。相比之下,迁移到非关系数据库将需要一项更大的任务,即完全重新设计应用程序以处理反规范化数据。
Azure Database for PostgreSQL — Hyperscale (Citus) 是一个关系数据库,它通过分片对数据进行分区。分区通过减少数据库在运行查询时看到的数据量以及使数据本地化到消费应用程序来提高性能。
尽管仍然使用关系数据库,但为了支持分区,进行了一些更改。更改集中在两个方面:修改表结构以使用不依赖于复制数据的键,以及将兽医 ID 附加到每次就诊记录以实现访问数据的分片。
这些更改突显了从传统关系数据库迁移到分区数据库的基本挑战。在分发数据时,至关重要的是要记住,该过程会在多个位置复制一些数据。此外,一些表仅包含数据集的一部分。通过包含分片键来限制范围可确保查询保持相关和功能正常。
Citus FAQ 识别了一些不受支持的查询,例如相关子查询,因此开发人员应提前规划。Spring Boot 的 JPA 实现的 PetClinic
数据模型没有使用这些查询类型中的任何一种,因此架构只需要很少的更改。
可扩展的数据有助于研究其他方法来提高应用程序的效率。例如,如果 PetClinic
为所有兽医运行报告,我们可以确定异步处理是否可以改善用户体验。我们还可以考虑处理同步和异步处理的必要更改,以及它们如何影响数据库响应。理解可扩展性问题可以揭示现有系统的潜在更改,并从一开始就实现新系统的可扩展性。
下一篇文章将探讨如何提高应用程序的可扩展性和可维护性,并采取进一步措施,通过云托管的、解耦的服务真正实现云原生。该过程将向您展示如何进行增量更改以拆分单体应用程序,从而保留其运行价值而不是从头开始重建。
要了解四家公司如何改造其关键任务 Java 应用程序以实现更好的性能、安全性和客户体验,请查看电子书现代化您的 Java 应用。