暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

PolarDB-X JDBC HA、三节点、负载均衡场景配置

PolarDB 2024-12-11
422

为了能更便捷地使用 PolarDB-X 2.0,PolarDB-X 提供了供 Java 使用的标准 JDBC 驱动,以实现:

  • 直连 PolarDB-X 2.0 标准版,并提供在 HA 后自动连接新主节点的能力
  • 直连 PolarDB-X 2.0 标准版,并在三节点计划切换时提供无感切换能力
  • 直连 PolarDB-X 2.0 企业版,并实现多节点的负载均衡

基本信息

Maven 依赖

驱动为 polardbx-connector-java,同时通过 provided 方式依赖 mysql-connector-java,便于用户自行选择使用的 MySQL JDBC connector。

<dependency>  
   <groupId>mysql</groupId>  
   <artifactId>mysql-connector-java</artifactId>  
   <version>8.0.33</version>  
</dependency>  
 
<dependency>  
   <groupId>com.alibaba.polardbx</groupId>  
   <artifactId>polardbx-connector-java</artifactId>  
   <version>2.1.1</version>  
</dependency>  

使用说明

JDBC URL 样例:

jdbc:polardbx://11.167.60.147:6991/test  

  • 驱动包名:com.alibaba.polardbx.Driver
  • 协议标志头:polardbx
  • ip端口指定:
    • 标准版三节点其中一个 IP:port 或者 VIP:port,或者多个 IP0:port0,IP1:port1(必须为同一集群中的节点,以英文逗号分隔),建连时会被路由到 leader 节点。
    • 企业版其中一个 IP:port 或者 VIP:port,或者多个 IP0:port0,IP1:port1(必须为同一集群中的节点,以英文逗号分隔),建连时会负载均衡到集群中随机一个读写节点。
  • 额外参数:
    • clusterId:集群 ID,用于校验避免错连,可以不指定,第一次连接会自动获取
    • haCheckConnectTimeoutMillis:HA 检测连接超时时间,默认 3000ms
    • haCheckSocketTimeoutMillis:HA 检测查询超时时间,默认 3000ms
    • haCheckIntervalMillis:HA 检测间隔时间,默认 1000ms
    • checkLeaderTransferringIntervalMillis:无感切换状态探测间隔,默认 100ms
    • leaderTransferringWaitTimeoutMillis:无感切换最长阻塞并等待切换时间,默认 5000ms
    • smoothSwitchover:是否支持连接池的无感高可用切换,通过在切换时 isClosed 时返回 true,让连接池抛弃该过时的连接,默认 true
  • 额外行为:
    • connectTimeout 在第一次获取 leader 或者 HA 切换时,会设置为 5000ms,其他情况默认无超时
  • 其他特征
    • JDK 8 标准编写,适配低版本
    • 参考标准 JDBC connector 以适配各种连接池
    • 在 HA 发生后,对已存在 Connection,会让所有请求报错
    • 拿连接时,会检查 HA 切换状态,实现无感切换
      驱动 JDBC URL 兼容 MySQL JDBC connector,支持设置 user、password、useSSL、characterEncoding、connectTimeout、socketTimeout、allowLoadLocalInfile、allowPublicKeyRetrieval、sslMode、characterEncoding、useCursorFetch、rewriteBatchedStatements、netTimeoutForStreamingResults、useServerPrepStmts、useUnicode 等常见参数。

关于无感切换能力

PolarDB-X,结合polardbx-connector-java驱动,提供了面向数据库的无感切换能力。
比如:DBA进行数据库主动主机下线的运维,此时数据库需要发生预期内的HA切换操作,基于PolarDB-X数据库+驱动的无感切换能力,可以提供业务SQL不中断、不报错的无感体验,极大的改善业务使用数据库的体验。

无感切换,工作原理示意:



PolarDB-X提供的无感切换,通过驱动实时感知 PolarDB-X 标准版三节点集群状态,在数据库HA切换动作完成之前:

  • 客户端驱动,阻塞分配连接返回,直到切换完成;
  • 针对业务已经获取连接尽快完成请求,并标记其不可复用

注意事项:

  • 客户端驱动,新增两个无感切换的检查点(获取链接、归还链接),通过实时感知 PolarDB-X 标准版三节点集群状态,实现HA切换后链接的动态重建能力,仅增加了获取新连接时间,但不会导致已有连接上请求报错,实现计划切换时应用无感。
  • 业务获取了数据库Connection链接,推荐使用标准 try-with-resources 实现的数据库操作,只要 try 块中的业务执行时间小于对应 PolarDB-X 设置的HA窗口(consensus_wait_millisecond_before_change_leader时间阈值,默认为1秒),就不会因为计划切换报错。而对于持有连接时长超出时间阈值的长事务,因为横跨切换流程,切换后会因为不能写入而报错。

目前PolarDB-X的无感切换能力,适配链接池:

  1. Druid >= 1.2.24版本
<dependency>  
   <groupId>com.alibaba</groupId>  
   <artifactId>druid</artifactId>  
   <version>1.2.24</version>  
</dependency>  

druid连接池参数的最佳实践:如何选择应用端连接池 ( https://help.aliyun.com/zh/polardb/polardb-for-xscale/configure-a-connection-pool-for-an-application-to-connect-to-a-polardb-x-instance )

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">  
       <property name="driverClassName" value="com.alibaba.polardbx.Driver" >  
       <!-- 基本属性 URL、user、password -->  
       <property name="url" value="jdbc:polardbx://ip:port/db?autoReconnect=true&rewriteBatchedStatements=true&socketTimeout=30000&connectTimeout=3000" >  
       <property name="username" value="root" >  
       <property name="password" value="123456" >  
       <!-- 配置初始化大小、最小、最大 -->  
       <property name="maxActive" value="20" >  
       <property name="initialSize" value="3" >  
       <property name="minIdle" value="3" >  
       <!-- maxWait 获取连接等待超时的时间 -->  
       <property name="maxWait" value="60000" >  
       <!-- timeBetweenEvictionRunsMillis 间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->  
       <property name="timeBetweenEvictionRunsMillis" value="60000" >  
       <!-- minEvictableIdleTimeMillis 一个连接在池中最小空闲的时间,单位是毫秒-->  
       <property name="minEvictableIdleTimeMillis" value="300000" >  
       <!-- 检测连接是否可用的 SQL -->  
       <property name="validationQuery" value="select 'z' from dual" >  
       <!-- 是否开启空闲连接检查 -->  
       <property name="testWhileIdle" value="true" >  
       <!-- 是否在获取连接前检查连接状态 -->  
       <property name="testOnBorrow" value="false" >  
       <!-- 是否在归还连接时检查连接状态 -->  
       <property name="testOnReturn" value="false" >  
       <!-- 是否在固定时间关闭连接。增加此参数可以均衡后端服务节点参数 -->  
       <property name="phyTimeoutMillis" value="600000" >  
       <!-- 是否在固定SQL使用次数之后关闭连接,增加此参数可以均衡后端服务节点参数-->  
       <property name="phyMaxUseCount" value="10000" >  
   </bean>  

快速使用例子(通用场景)

直接使用驱动连接 PolarDB-X

Class.forName("com.alibaba.polardbx.Driver");  
 
try (final Connection conn = DriverManager.getConnection(  
   "jdbc:polardbx://127.0.0.1:3306/", "root", "*****");  
   final Statement stmt = conn.createStatement()) {  
 
   try (final ResultSet rs = stmt.executeQuery("select 1")) {  
       for (int i = 0; i < rs.getMetaData().getColumnCount(); ++i) {  
           System.out.print(rs.getMetaData().getColumnName(i + 1) + "\t");  
       }  
       System.out.println();  
       while (rs.next()) {  
           for (int i = 0; i < rs.getMetaData().getColumnCount(); ++i) {  
               System.out.print(rs.getObject(i + 1) + "\t");  
           }  
           System.out.println();  
       }  
   }  
}  

使用 Druid 连接池

try (final DruidDataSource dataSource = new DruidDataSource()) {  
   dataSource.setUrl("jdbc:polardbx://127.0.0.1:3306/");  
   dataSource.setUsername("root");  
   dataSource.setPassword("*****");  
     
   // 当 druid 连接池版本小于等于 1.2.23 时,需要主动调用 setDriverClassName 和 setExceptionSorter,以适配 PolarDB-X 的驱动,新版本 1.2.24 及后续版本能够自动识别驱动  
   dataSource.setDriverClassName("com.alibaba.polardbx.Driver");  
   dataSource.setExceptionSorter(new MySqlExceptionSorter());  
 
   try (final Connection conn = dataSource.getConnection();  
       final Statement stmt = conn.createStatement()) {  
 
       try (final ResultSet rs = stmt.executeQuery("select 1")) {  
           for (int i = 0; i < rs.getMetaData().getColumnCount(); ++i) {  
               System.out.print(rs.getMetaData().getColumnName(i + 1) + "\t");  
           }  
           System.out.println();  
           while (rs.next()) {  
               for (int i = 0; i < rs.getMetaData().getColumnCount(); ++i) {  
                   System.out.print(rs.getObject(i + 1) + "\t");  
               }  
               System.out.println();  
           }  
       }  
   }  
}  

或者通过 xml 设置数据源后,通过代码,新增这两项适配新驱动:

dataSource.setDriverClassName("com.alibaba.polardbx.Driver");  
dataSource.setExceptionSorter(new MySqlExceptionSorter());  

使用 HikariCP 连接池

Class.forName("com.alibaba.polardbx.Driver");  
 
final HikariConfig config = new HikariConfig();  
config.setJdbcUrl("jdbc:polardbx://127.0.0.1:3306/");  
config.setUsername("root");  
config.setPassword("*****");  
config.setMaximumPoolSize(10);  
 
try (HikariDataSource dataSource = new HikariDataSource(config)) {  
   try (final Connection conn = dataSource.getConnection();  
       final Statement stmt = conn.createStatement()) {  
 
       try (final ResultSet rs = stmt.executeQuery("select 1")) {  
           for (int i = 0; i < rs.getMetaData().getColumnCount(); ++i) {  
               System.out.print(rs.getMetaData().getColumnName(i + 1) + "\t");  
           }  
           System.out.println();  
           while (rs.next()) {  
               for (int i = 0; i < rs.getMetaData().getColumnCount(); ++i) {  
                   System.out.print(rs.getObject(i + 1) + "\t");  
               }  
               System.out.println();  
           }  
       }  
   }  
}  

使用 DBCP 连接池

Class.forName("com.alibaba.polardbx.Driver");  
 
final BasicDataSource dataSource = new BasicDataSource();  
dataSource.setUrl("jdbc:polardbx://127.0.0.1:3306/");  
dataSource.setUsername("root");  
dataSource.setPassword("*****");  
dataSource.setInitialSize(5);  
 
try (final Connection conn = dataSource.getConnection();  
   final Statement stmt = conn.createStatement()) {  
 
   try (final ResultSet rs = stmt.executeQuery("select 1")) {  
       for (int i = 0; i < rs.getMetaData().getColumnCount(); ++i) {  
           System.out.print(rs.getMetaData().getColumnName(i + 1) + "\t");  
       }  
       System.out.println();  
       while (rs.next()) {  
           for (int i = 0; i < rs.getMetaData().getColumnCount(); ++i) {  
               System.out.print(rs.getObject(i + 1) + "\t");  
           }  
           System.out.println();  
       }  
   }  
}  

快速验证样例

  • 在测试开发环境配置 JDK 环境
  • 下载 mysql-connector-j-8.0.33.jar polardbx-connector-java-2.1.1.jar
  • 编写测试文件,注意替换为自己集群的地址、用户名、密码,并确保网络能连通,如下代码展示连接 PolarDB-X 标准版集群,并打印集群的相关信息
import java.sql.Connection;  
import java.sql.DriverManager;  
import java.sql.ResultSet;  
import java.sql.Statement;  
 
public class TestDriver {  
   public static void main(String[] args) throws Exception {  
       Class.forName("com.alibaba.polardbx.Driver");  
 
       try (final Connection conn = DriverManager.getConnection(  
           "jdbc:polardbx://127.0.0.1:3306/", "root", "****");  
           final Statement stmt = conn.createStatement()) {  
 
           try (final ResultSet rs = stmt.executeQuery("select * from information_schema.alisql_cluster_global")) {  
               for (int i = 0; i < rs.getMetaData().getColumnCount(); ++i) {  
                   System.out.print(rs.getMetaData().getColumnName(i + 1) + "\t");  
               }  
               System.out.println();  
               while (rs.next()) {  
                   for (int i = 0; i < rs.getMetaData().getColumnCount(); ++i) {  
                       System.out.print(rs.getObject(i + 1) + "\t");  
                   }  
                   System.out.println();  
               }  
           }  
       }  
   }  
}  

  • 编译运行
$javac TestDriver.java  
 
$ll  
total 2812  
-rw-r--r-- 1 chenyu.zzy users 2481560 Dec  3 17:26 mysql-connector-j-8.0.33.jar  
-rw-r--r-- 1 chenyu.zzy users  384133 Dec  3 17:19 polardbx-connector-java-2.1.1.jar  
-rw-r--r-- 1 chenyu.zzy users    2442 Dec  3 17:29 TestDriver.class  
-rw-r--r-- 1 chenyu.zzy users    1102 Dec  3 17:29 TestDriver.java  
 
# 第一次运行时,高可用会打印获取到的集群信息到日志接口中  
$java -cp .:mysql-connector-j-8.0.33.jar:polardbx-connector-java-2.1.1.jar TestDriver  
Dec 03, 2024 5:29:58 PM com.alibaba.polardbx.HaManager info  
INFO: Backend cluster state changed to: [{"tag":"127.0.0.1:6991","host":"127.0.0.1","port":6991,"xport":34991,"paxos_port":14991,"role":"Leader","peers":[{"tag":"11.167.60.147:6992","host":"11.167.60.147","port":6992,"xport":-1,"paxos_port":14992,"role":"Follower","version":"8.0.32-X-Cluster-8.4.20-20241014","cluster_id":6990,"update_time":"2024-12-03 17:29:58 GMT+08:00"},{"tag":"11.167.60.147:6993","host":"11.167.60.147","port":6993,"xport":-1,"paxos_port":14993,"role":"Follower","version":"8.0.32-X-Cluster-8.4.20-20241014","cluster_id":6990,"update_time":"2024-12-03 17:29:58 GMT+08:00"},{"tag":"127.0.0.1:6991","host":"11.167.60.147","port":6991,"xport":34991,"paxos_port":14991,"role":"Leader","version":"8.0.32-X-Cluster-8.4.20-20241014","cluster_id":6990,"update_time":"2024-12-03 17:29:58 GMT+08:00"}],"version":"8.0.32-X-Cluster-8.4.20-20241014","cluster_id":6990,"update_time":"2024-12-03 17:29:58 GMT+08:00"}]  
SERVER_ID IP_PORT MATCH_INDEX NEXT_INDEX ROLE HAS_VOTED FORCE_SYNC ELECTION_WEIGHT LEARNER_SOURCE APPLIED_INDEX PIPELINING SEND_APPLIED  
1 11.167.60.147:14991 264333 0 Leader Yes No 9 0 264332 No No  
2 11.167.60.147:14992 264333 264334 Follower Yes No 9 0 264333 Yes No  
3 11.167.60.147:14993 264333 264334 Follower Yes No 1 0 264333 Yes No  
 
# 再次运行由于集群信息没有变化,则不会打印相关信息(集群信息会被缓存以加快初始化速度)  
$java -cp .:mysql-connector-j-8.0.33.jar:polardbx-connector-java-2.1.1.jar TestDriver  
SERVER_ID IP_PORT MATCH_INDEX NEXT_INDEX ROLE HAS_VOTED FORCE_SYNC ELECTION_WEIGHT LEARNER_SOURCE APPLIED_INDEX PIPELINING SEND_APPLIED  
1 11.167.60.147:14991 264333 0 Leader Yes No 9 0 264332 No No  
2 11.167.60.147:14992 264333 264334 Follower Yes No 9 0 264333 Yes No  
3 11.167.60.147:14993 264333 264334 Follower Yes No 1 0 264333 Yes No  

文章转载自PolarDB,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论