Connect SE JDBC 表类型:访问其他 DBMS 的表

在本篇博客中,我们将展示如何从 MariaDB Server 访问 MySQL 和 MariaDB DBMS 的表。为此,我们将使用 Connect Storage Engine (SE),它支持不同的表类型选项。在本例中,我们将使用 JDBC 表类型。要使用 JDBC 表类型,我们需要在 Connect SE 的 CREATE TABLE 定义中指定它,并且需要访问的 DBMS 的 Java 连接器。为了演示目的,我们将使用容器,但这同样适用于服务器运行在虚拟机或裸机上的情况。只需确保机器可以通过 TCP/IP 互相访问即可。只要有相应的 Java 连接器,同样的过程可用于访问任何其他 DBMS。

为了从 MariaDB Server 访问另一个 DBMS 的表,我们需要:

  1. 源 - 位于外部 DBMS 中的表,我们将从中“获取”数据。(在本例中,我们将使用 MariaDB 和 MySQL 作为“外部”DBMS。)
  2. 目标 - 位于 MariaDB Server 中的本地表。
  3. 能够与外部 DBMS 通信的 Java 连接器以及 JDBC 的依赖项。
  4. 在目标系统上安装了 Connect SE 插件。
  5. 访问外部 DBMS 和我们的源表的凭据。

如果您不想查看完整的设置过程,可以跳过下面的分步指南,通过运行第 6 节中的 docker-compose 脚本来查看连接的实际效果。您可以在此仓库中找到本博客文章中的文件。

1. 在 MySQL 和 MariaDB DBMS 中创建源表

我们将创建两个容器,mariadb-sourcemysql-source,两者都运行各自的数据库系统。我们将在两个容器中都创建一个表,并填充一些行。创建表的 SQL 命令将放在容器的特殊文件夹 /docker-entrypoint-initdb.d 内的 .sql 文件中。该目录下任何 SQL 文件都会在初始化新实例时运行。我们将使用卷挂载点来实现这一点。

1.1 MariaDB Server 源表

这是我们的 data.sql 文件内容:

$ cat mariadb_data/data.sql 
CREATE TABLE t_maria (t int);
INSERT INTO t_maria VALUES (1),(2),(3);

为了让我们的容器互相通信,我们将创建一个自定义的 docker network(用户定义的桥接网络)。这将使我们能够在容器之间实现自动 DNS 解析。默认的桥接网络不支持这一点。(更多详情见第 5 节)。

$ docker network create jdbc_connect

接下来,我们将启动 mariadb-source 容器。

$ docker container run \
    --name mariadb-source \
    --rm \
    -e MARIADB_ALLOW_EMPTY_ROOT_PASSWORD=1 \
    -e MARIADB_DATABASE=db_maria \
    -v$PWD/mariadb_data:/docker-entrypoint-initdb.d:z \
    -d --network jdbc_connect \
    mariadb:latest

容器初始化完成后,我们可以检查数据。

$ docker exec -t mariadb-source mariadb -e "select * from t_maria" db_maria
+------+
| t    |
+------+
|    1 |
|    2 |
|    3 |
+------+

1.2 MySQL Server 源表

我们将对 MySQL 容器遵循类似的过程。这是我们的数据文件,放在不同的目录以便于挂载。

$ cat mysql_data/data.sql
CREATE TABLE t_mysql (t int);
INSERT INTO t_mysql VALUES (4),(5),(6);

让我们启动容器。

$ docker container run \
    --name mysql-source \
    --rm \
    -e MYSQL_ALLOW_EMPTY_PASSWORD=1 \
    -e MYSQL_DATABASE=db_mysql \
    -v$PWD/mysql_data:/docker-entrypoint-initdb.d:z \
    -d --network jdbc_connect \
    mysql:latest

并检查数据。

$ docker exec -t mysql-source mysql -e "select * from t_mysql" db_mysql
+------+
| t    |
+------+
|    4 |
|    5 |
|    6 |
+------+

1.3 MariaDB DBMS 目标

现在我们的源已经准备就绪,让我们创建目标 MariaDB Docker 容器。这个容器我们不会添加任何预加载数据。

$ docker container run --name mariadb-target --rm -e MARIADB_ALLOW_EMPTY_ROOT_PASSWORD=1 -d --network jdbc_connect mariadb:latest

现在我们需要配置目标容器所需的依赖项。我们可以在 Dockerfile 中完成此操作,但让我们在容器中进行实时实验。

2. 源 DBMS 的 Java 连接器和依赖项

2.1 JDBC 的依赖项

让我们进入容器的 shell 并开始配置。

$ docker exec -it mariadb-target bash

JDBC 有以下要求: java, jvmjar。前两个二进制文件是 openjdk-11-jre-headless 的一部分,但由于我们将在下方使用 jar 来验证某些 jar 文件的内容,我们将使用 openjdk-11-jdk-headless 包,它将 openjdk-11-jre-headless 作为依赖项。

$ apt update && apt install openjdk-11-jdk-headless

检查安装(java, javac, jar),它们应该已经安装。

$ java --version
openjdk 11.0.18 2023-01-17
OpenJDK Runtime Environment (build 11.0.18+10-post-Ubuntu-0ubuntu122.04)
OpenJDK 64-Bit Server VM (build 11.0.18+10-post-Ubuntu-0ubuntu122.04, mixed mode, sharing)

此外,查找 libjvm.so 的位置。它应该位于 /usr/lib/jvm/java-11-openjdk-amd64/lib/server/libjvm.so。我们需要将其指定为 connect_jvm_path 的一部分。

2.2 安装 Java 连接器

为了通过 JDBC 访问表,我们需要源 DBMS 的 Java 连接器。在本例中,我们需要在目标系统上安装 MySQL Connector/JMariaDB Connector/J。我们还需要包装器文件。这些包装器文件是 mariadb-test-data 包的一部分。JavaWrappers.jar 包含 JdbcInterface 的 MySQL 和 MariaDB 接口的字节码子类。(给读者的说明:我们知道当前的打包方案并不理想,我们正在努力改进这一点,并将 JavaWrappers 放入适当的包中)。

$ apt install mariadb-test-data    # Install the package
$ dpkg -L mariadb-test-data | \
     grep JavaWrapper  # Let's see where JavaWrappers was placed.
/usr/share/mysql/mysql-test/plugin/connect/connect/std_data/JavaWrappers.jar

jar 文件中有多个包装器。可以查看 jar 文件内容如下:

$ jar tf /usr/share/mysql/mysql-test/plugin/connect/connect/std_data/JavaWrappers.jar \
    | grep class
wrappers/PostgresqlInterface.class
wrappers/OracleInterface.class
wrappers/ApacheInterface.class
wrappers/Client.class
wrappers/JdbcInterface.class
wrappers/MysqlInterface.class
wrappers/MariadbInterface.class

务必记住 JavaWrappers.jar 文件本身的路径,即 /usr/share/mysql/mysql-test/plugin/connect/connect/std_data/JavaWrappers.jar,稍后将用作 connect_class_path。您可以自由移动此文件,但请务必记住其路径。

根据您要连接的数据库,您需要一个额外的 Java 连接器客户端。

对于 MariaDB Connector/J,以及 Debian/Ubuntu 中的 MySQL,我们可以使用 libmariadb-java 包。对于这个包,客户端位于 /usr/share/java/mariadb-java-client.jar。我们需要将此路径附加到 connect_class_path 系统变量中,就像您为常规的 bash "PATH" 变量所做的那样。不同的路径通过 : 分隔。

3. 在目标系统上安装 Connect SE 插件

这一步非常简单,之前在上一篇博客中已经介绍过。由于我们已经登录到目标容器,让我们为其安装包。

$ apt install mariadb-plugin-connect
$ ls /usr/lib/mysql/plugin/ha_connect* 
/usr/lib/mysql/plugin/ha_connect.so

这将在容器内安装插件,但 MariaDB Server 尚未知道它应该在启动时实际加载它。

4. 为 JDBC 配置目标 MariaDB Server

让我们创建一个配置文件,以便在 MariaDB Server 启动时加载 Connect SE,并设置查找 jvm 和 JavaWrappers 所需的配置变量。以下是必要的更改:

[mariadb]
plugin_load_add=ha_connect.so
connect_jvm_path=/usr/lib/jvm/java-11-openjdk-amd64/lib/server/
connect_class_path=/usr/share/mysql/mysql-test/plugin/connect/connect/std_data/JavaWrappers.jar:/usr/share/java/mariadb-java-client.jar

您可以将此配置块添加到 /etc/mysql/mariadb.conf.d/50-server.cnf 中,但您需要重新启动 docker 容器以使更改生效。

$ docker restart mariadb-target

另一种方法是在宿主机上创建一个自定义配置文件,并将其挂载到容器内的 /etc/mysql/mariadb.conf.d/ 文件夹中。在第 6 节中使用 docker-compose 时,我们就是这样做的。

最后,让我们开始检查配置。首先检查 Connect SE 是否正确安装。它应该显示在 show plugins 中。

$ docker exec -it mariadb-target mariadb -uroot  -e "show plugins soname like '%connect%';"
+---------+--------+----------------+---------------+---------+
| Name    | Status | Type           | Library       | License |
+---------+--------+----------------+---------------+---------+
| CONNECT | ACTIVE | STORAGE ENGINE | ha_connect.so | GPL     |
+---------+--------+----------------+---------------+---------+

其次,让我们检查 connect_jvm_pathconnect_class_path 变量是否正确。

$ docker exec -t mariadb-target mariadb -e "select @@connect_jvm_path, @@connect_class_path\G"
*************************** 1. row ***************************
@@connect_jvm_path: /usr/lib/jvm/java-11-openjdk-amd64/lib/server/
@@connect_class_path: /usr/share/mysql/mysql-test/plugin/connect/connect/std_data/JavaWrappers.jar:/usr/share/java/mariadb-java-client.jar

5. 访问“外部源”表

我们将首先尝试访问位于“源”MariaDB Server 中的表。为此,我们将在本地 MariaDB Server 中创建一个表。该表将使用 Connect SE,并带有一些特殊的 JDBC 特定参数。这些参数指定表类型和连接 URL。表类型指示 Connect SE 使用什么驱动程序来获取数据,连接字符串则传递给驱动程序。

5.1 从 localhost 上的表获取数据

在我们尝试连接到源容器之前,让我们进行一次“回环”连接,以测试 JDBC 在目标容器中是否正常工作。首先启动一个 mariadb 客户端连接。

$ docker exec -it mariadb-target mariadb -u root

现在创建一个 test 数据库和一个示例表 t

MariaDB [(none)]> create database test; use test;
MariaDB [test]> create table t(t int); insert into t values (1),(2);

接下来创建一个 Connect SE 表,使用 JDBC 连接到它。


MariaDB [test]> create table t_jdbc engine=connect table_type=JDBC tabname=t connection='jdbc:mariadb://localhost/test?user=root&password';
MariaDB [test]> select * from t_jdbc;
+------+
| t    |
+------+
|    1 |
|    2 |
+------+

5.2 从远程主机上的表获取数据 (mariadb-source 和 mysql-source)

由于我们在同一个网络上创建了所有容器,容器名称可以解析为容器的私有 IP 地址。

仍在 mariadb-target 容器中,创建一个表,其连接字符串指向 mariadb-source。请注意,我们使用了数据库的 root 用户。由于我们在创建 mariadb-source 容器时创建了空密码的 root 用户,因此我们在 jdbc 连接字符串中没有传递密码。

MariaDB [test]> create table db_mariadb_target engine=connect table_type=JDBC tabname=t_maria connection='jdbc:mariadb://mariadb-source/db_maria?user=root&password'\G
MariaDB [test]> select * from db_mariadb_target;
+------+
| t    |
+------+
|    1 |
|    2 |
|    3 |
+------+

类似地,要连接到我们的 mysql-source 容器,我们只需要调整连接字符串。

MariaDB [test]> create table db_mysql_target engine=connect table_type=JDBC tabname=t_mysql connection='jdbc:mariadb://mysql-source/db_mysql?user=root&password'\G
MariaDB [test]> select * from db_mysql_target;
+------+
| t    |
+------+
|    4 |
|    5 |
|    6 |
+------+

太棒了!我们通过 JDBC 表类型从远程 DBMS 访问到了我们的目标(本地)容器。

5.3 修改 Connect SE 表

出于所有目的,应用程序可以像使用“目标”DBMS 的一部分一样使用此表。然而,如果我们想获得 InnoDB 的所有性能优势怎么办?好吧,我们可以将 Connect SE 表迁移到 InnoDB。数据不会随外部表的更改而更新,但我们将拥有转换为 InnoDB 时刻的所有数据。

MariaDB [test]> show tables;
+-------------------+
| Tables_in_test    |
+-------------------+
| db_mariadb_target |
| db_mysql_target   |
| t                 |
| t_jdbc            |
+-------------------+
4 rows in set (0.000 sec)

为了导入数据,我们首先创建一个克隆表。然后对其运行 ALTER TABLE 以将存储引擎更改为 InnoDB。这样我们将保留 Connect SE 表属性,以防将来想要切换回来。

MariaDB [test]> create table temp like db_mysql_target;

MariaDB [test]> show create table temp\G
*************************** 1. row ***************************
       Table: temp
Create Table: CREATE TABLE `temp` (
  `t` int(10) DEFAULT NULL
) ENGINE=CONNECT DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci CONNECTION='jdbc:mysql://mysql-source/db_mysql?user=root&password' `TABLE_TYPE`='JDBC' `TABNAME`='t_mysql'
1 row in set (0.000 sec)


MariaDB [test]> alter table temp engine=InnoDB;
Query OK, 3 rows affected (0.037 sec)              
Records: 3  Duplicates: 0  Warnings: 0

MariaDB [test]> show create table temp\G
*************************** 1. row ***************************
       Table: temp
Create Table: CREATE TABLE `temp` (
  `t` int(10) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci CONNECTION='jdbc:mysql://mysql-source/db_mysql?user=root&password' /* `TABLE_TYPE`='JDBC' `TABNAME`='t_mysql' */
1 row in set (0.000 sec)

6. 使用 Docker Compose 实现自动化

使用 Docker 和容器的好处在于,我们可以将大部分工作自动化。在此 GitHub 仓库中,您将找到一个 docker-compose 文件及相关脚本,只需运行 docker-compose up 即可实现相同的结果。

启动后,您可以直接检查表。

docker exec -t mariadb-target mariadb test -e  "select 'mysql' as src,t from db_mysql_target union all select 'mariadb',t from db_mariadb_target"
+---------+------+
| src     | t    |
+---------+------+
| mysql   |    4 |
| mysql   |    5 |
| mysql   |    6 |
| mariadb |    1 |
| mariadb |    2 |
| mariadb |    3 |
+---------+------+

阅读更多

欢迎反馈

如果您在本博客中遇到任何问题、设计上的问题或未按预期工作的边界情况,请告知我们。欢迎在 Zulip 上讨论。一如既往,您可以在我们的 JIRA bug/feature request 系统(MDEV 项目)中提交遇到的任何 bug/feature request。