MariaDB Connector/J 故障转移支持 – 案例 Amazon Aurora

MariaDB Connector/J 在过去一年中发展了很多。在这篇文章中,我将讨论连接器中的故障转移功能,并就如何在某些特定情况下使用它们提供一些指导。我将在后面的文章中介绍的另一个重要新功能是 MariaDB Connector/J 现在也可以在多个服务器上进行负载均衡。

首先,我们需要连接器本身。请执行以下任一操作来获取撰写本文时的最新稳定版本 MariaDB Connector/J 1.2.3 版

  • https://mariadb.com/my_portal/download 下载二进制文件(需要登录)。登录后,在下载页面查找 Connectors / Java。
  • 该连接器也可通过 Maven 获取。要指示 Maven 包含 MariaDB Connector/J,请将以下依赖项添加到应用程序的 pom.xml 文件中
<dependency>
    <groupId>org.mariadb.jdbc</groupId>
    <artifactId>java-client</artifactId>
    <version>1.2.3</version>
</dependency>

添加到 MariaDB Connector/J 中的故障转移支持可用于多种高可用性场景

  1. 最典型的场景,使用普通的 MariaDB 或 MySQL 复制,设置包含一个用于写操作的主机和一个或多个用于读操作的从机。
  2. 多主设置,例如 MariaDB Galera Cluster,其中每个主机都是主节点
  3. 支持 Amazon Aurora 及其自动故障转移功能
  4. 连接器也支持在如第 1 点所述的普通复制拓扑中拥有多个主节点,但连接器不会感知主节点之间的差异,只会根据 JDBC URL 中给定的顺序选择其中一个。

为了演示故障转移能力,我将重点关注场景 3) 并设置一个 Amazon Aurora 集群,使用 Connector/J 连接到它并模拟集群中的故障转移,以查看连接器如何对此做出反应。如果您想了解更多关于 Aurora 的信息,请查阅其文档

首先,我将创建一个将作为集群主节点的 Aurora 实例

  • 要查找 Aurora,请转到您的 AWS 控制台,在 Databases 下点击 RDS,您应该会看到一个写着“Launch an Aurora DB instance”(启动 Aurora 数据库实例)的按钮。点击它。
  • 在“Specify DB Details”(指定数据库详细信息)页面输入所需信息。请注意,当您在字段之间移动时,右侧会显示对该字段的良好解释。完成后点击“Next Step”(下一步)
  • 在“Configure Advanced Settings”(配置高级设置)页面,第一部分很重要。选择 VPC 和 VPC 子网组或创建一个新的 VPC。在 VPC Security Group(s)(VPC 安全组)中选择应允许连接到 Aurora 集群的 VPC 安全组。

完成后,您应该会看到一个 Aurora 节点正在运行,如下所示

AWS Console -RDS Aurora

您会注意到在上述表格的 Replication Role(复制角色)列中显示为“Writer”(写入器)。

让我们为这个主节点添加一个从节点。在 Aurora 集群中,从节点被称为副本(replica)

  1. 为此,请点击上述表格中实例旁边的复选框。它将展开该行,您应该会看到一个“Instance Actions”(实例操作)按钮。点击它并选择“Create Aurora Replica”(创建 Aurora 副本)。
  2. 在下一个“Create Aurora Replica”(创建 Aurora 副本)页面,您必须填写“DB Instance Identifier*”(数据库实例标识符*)字段。除此之外,如果您不想更改任何默认值,可以使用默认设置。最后点击“Create Aurora Replica”(创建 Aurora 副本)按钮。

重复以上步骤,根据您希望集群拥有的副本数量来操作。最大数量为 15。

完成后,您应该会看到一个实例表格,如下所示

AWS Console RDS Aurora multiple

故障转移

MariaDB Connector/J 故障转移的文档可以在这里找到,连接器完整文档的首页可以在这里找到。

Aurora 的故障转移示例

接下来,我们编写一个使用 MariaDB 连接器故障转移功能的程序。下面您将看到 Java 类 TestConnection。请花些时间阅读代码。它非常直观,应该很容易理解。

import java.sql.*;
import java.text.SimpleDateFormat;
import java.util.Date;

public class TestConnection {

   public static void main(String[] args) {
       Connection conn = null;
       Statement stmt = null;
       String masterServer = "";
       String slaveServer = "";
       String connServer = "";
       SimpleDateFormat dateFormat = new SimpleDateFormat("HH:mm:ss.SSS");
       int i = 0;
       boolean readonly = (args.length >= 1) ? "true".equals(args[0]) : false;
       try {
           // specify connection to three nodes of aurora
           conn = DriverManager.getConnection("jdbc:mysql:aurora://"
                   + "aurora-demo-1.abcdefghijkl.us-east-1.rds.amazonaws.com:3306,"
                   + "aurora-demo-2.abcdefghijkl.us-east-1.rds.amazonaws.com:3306,"
                   + "aurora-demo-3.abcdefghijkl.us-east-1.rds.amazonaws.com:3306",
                   "xxxxxx", "yyyyyyyy");
           conn.setReadOnly(readonly); //read on master or replica
           while (i < 100) {
               try {
                   stmt = conn.createStatement();
                   slaveServer="";
                   // read server information from INFORMATION_SCHEMA
                   ResultSet rs = stmt.executeQuery("SELECT SERVER_ID, SESSION_ID FROM INFORMATION_SCHEMA.REPLICA_HOST_STATUS");
                   while (rs.next()) {
                       if (rs.getString(2).equals("MASTER_SESSION_ID"))
                           masterServer = rs.getString(1); // the node that is the master currently has a session named MASTER_SESSION_ID
                       else
                           slaveServer += (("".equals(slaveServer)) ? "" : ",") + rs.getString(1); // other nodes ares slaves
                   }

                   // get the server that is used by the current connection
                   String url = conn.getMetaData().getURL();
                   connServer = conn.getMetaData().getURL().substring(url.indexOf("//") + 2 , url.indexOf("."));

               } catch (SQLException e) {
                   continue;
               } catch (NullPointerException ne) {
                   ne.printStackTrace();
               }
               // print out timestamp and servers
               System.out.println(dateFormat.format(new Date()) + " connected-to:"
                       + connServer + " master:" + masterServer + " slave:"
                       + slaveServer);
               i++;
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
       } catch (SQLException e) {
           e.printStackTrace();
       } finally {
           try {
               if (stmt != null) {
                   stmt.close();
               }
               if (conn != null) {
                   conn.close();
               }
           } catch (SQLException sqle) {
               //eat exception
           }
       }
   }
}

上述示例 Java 程序中的重要步骤是指定 URL 的部分

// specify connection to two nodes of aurora
conn = DriverManager.getConnection("jdbc:mysql:aurora://aurora-demo-1.abcdefghijkl.us-east-1.rds.amazonaws.com:3306,aurora-demo-2.abcdefghijkl.us-east-1.rds.amazonaws.com:3306,aurora-demo-3.abcdefghijkl.us-east-1.rds.amazonaws.com:3306", "xxxxxxxx", "yyyyyyyy");

通过在 jdbc:mysql:aurora:// 中给出关键字 aurora,我们指示驱动程序应启用 Aurora 故障转移支持。接下来在 URL 中给出三个服务器:aurora-demo-1…、aurora-demo-2… 和 aurora-demo-3,最后是用户名和密码。

RDS AWS Console details

确保使用实例端点(绿色所示)而不是集群端点(红色所示)。

如果我们运行该程序,它将开始以大约 1 秒的间隔输出如下所示的行:
16:15:22.467 connected-to:aurora-demo-2 master:aurora-demo-2 slave:aurora-demo-1,aurora-demo-3
16:15:23.579 connected-to:aurora-demo-2 master:aurora-demo-2 slave:aurora-demo-1,aurora-demo-3

首先是时间指示器,然后是当前连接指向哪个服务器,再然后是 INFORMATION_SCHEMA 中哪个服务器是主节点、哪个服务器是从节点。INFORMATION_SCHEMA 中的这些数据是 Aurora 特有的,MariaDB 或 MySQL 中不存在。

更有趣的部分是当我们保持程序运行,并且 Aurora 集群中发生故障转移,即从节点变成主节点时。此时输出将如下所示
20:56:49.241 connected-to:aurora-demo-1 master:aurora-demo-1 slave:aurora-demo-2,aurora-demo-3
20:56:50.370 connected-to:aurora-demo-1 master:aurora-demo-1 slave:aurora-demo-2,aurora-demo-3
20:56:51.501 connected-to:aurora-demo-1 master:aurora-demo-1 slave:aurora-demo-2,aurora-demo-3
20:56:52.637 connected-to:aurora-demo-1 master:aurora-demo-1 slave:aurora-demo-2,aurora-demo-3
20:58:19.374 connected-to:aurora-demo-2 master:aurora-demo-2 slave:aurora-demo-1,aurora-demo-3
20:58:20.481 connected-to:aurora-demo-2 master:aurora-demo-2 slave:aurora-demo-1,aurora-demo-3
20:58:21.587 connected-to:aurora-demo-2 master:aurora-demo-2 slave:aurora-demo-1,aurora-demo-3

突出显示的行是有趣的部分。在运行这个小型 Java 程序时,我在 Aurora 集群中强制执行了故障转移。正如您所见,程序会等待一段时间,直到获得新的主连接,然后继续执行。正如您所见,原来的从节点 aurora-demo-2 已经成为主节点,故障转移后连接也建立到了它上面。从应用程序的角度来看,唯一可见的是服务器响应会有一个延迟。这就是自动故障转移!

如果新主节点的选举耗时超过 120 秒(failoverLoopRetries 参数的默认值),连接器将会抛出 SQLException。还有更多参数可以用来调整故障转移能力以满足您的需求。您可以在文档中找到它们。

Aurora 从节点上的故障转移

当使用带有“replication”参数或本例中“aurora”参数进行故障转移时,MariaDB Connector/J 具有 2 个底层连接;一个连接到主节点,一个连接到从节点。使用带有参数“true”的先前程序时,驱动程序将使用从连接。

当突然重启正在使用的从实例时,输出如下所示
16:27:24.524 connected-to:aurora-demo-3 master:aurora-demo-1 slave:aurora-demo-2,aurora-demo-3
16:27:25.638 connected-to:aurora-demo-3 master:aurora-demo-1 slave:aurora-demo-2,aurora-demo-3
16:27:26.759 connected-to:aurora-demo-3 master:aurora-demo-1 slave:aurora-demo-2,aurora-demo-3
16:27:27.982 connected-to:aurora-demo-1 master:aurora-demo-1 slave:aurora-demo-2,aurora-demo-3
16:27:29.089 connected-to:aurora-demo-1 master:aurora-demo-1 slave:aurora-demo-2,aurora-demo-3
16:27:30.196 connected-to:aurora-demo-1 master:aurora-demo-1 slave:aurora-demo-2,aurora-demo-3
16:27:31.310 connected-to:aurora-demo-1 master:aurora-demo-1 slave:aurora-demo-2,aurora-demo-3
16:27:32.435 connected-to:aurora-demo-1 master:aurora-demo-1 slave:aurora-demo-2,aurora-demo-3
16:27:33.542 connected-to:aurora-demo-1 master:aurora-demo-1 slave:aurora-demo-2,aurora-demo-3
16:27:34.649 connected-to:aurora-demo-1 master:aurora-demo-1 slave:aurora-demo-2,aurora-demo-3
16:27:35.757 connected-to:aurora-demo-1 master:aurora-demo-1 slave:aurora-demo-2,aurora-demo-3
16:27:36.872 connected-to:aurora-demo-1 master:aurora-demo-1 slave:aurora-demo-2,aurora-demo-3
16:27:37.982 connected-to:aurora-demo-1 master:aurora-demo-1 slave:aurora-demo-2,aurora-demo-3
16:27:39.089 connected-to:aurora-demo-1 master:aurora-demo-1 slave:aurora-demo-2,aurora-demo-3
16:27:40.200 connected-to:aurora-demo-2 master:aurora-demo-1 slave:aurora-demo-2,aurora-demo-3
16:27:41.301 connected-to:aurora-demo-2 master:aurora-demo-1 slave:aurora-demo-2,aurora-demo-3
16:27:42.406 connected-to:aurora-demo-2 master:aurora-demo-1 slave:aurora-demo-2,aurora-demo-3
16:27:43.522 connected-to:aurora-demo-2 master:aurora-demo-1 slave:aurora-demo-2,aurora-demo-3

正在使用连接到从节点 aurora-demo-3 的驱动程序立即切换到连接到主节点 aurora-demo-1。当建立新的从连接(到 aurora-demo-2)时,驱动程序将再次切换连接。

负载均衡及其他功能

MariaDB Connector/J 还包括负载均衡、服务器端预处理语句执行以及我将在后续博客文章中介绍的其他有趣新功能。为了正确测试负载均衡,我将设置一个连接池。