基于JDBC的负载均衡

Hologres从 V1.3版本开始,支持在JDBC配置多个只读从实例以支持简单的负载均衡。本文为您介绍如何基于JDBC实现负载均衡。

背景信息

Hologres中一个主实例可以绑定多个只读从实例,实例与实例之间共享存储,但是计算资源是互相隔离的,从而实现读写分离高可用部署,详情请参见主从实例读写分离部署(共享存储)

Hologres支持使用JDBC配置多个只读从实例,实现简单的负载均衡,如下图所示:image..png

您使用JDBC配置多个只读从实例可以实现以下功能:

  • 将查询请求随机分配到您的只读从实例,避免单个只读从实例负载过高。

  • 查询请求按照顺序连接只读从实例,极大程度避免因为只读从实例故障导致服务不可用,存在以下情况:

    • 当只读从实例1连接失败时,JDBC会自动尝试连接只读从实例2。

    • 当只读从实例1和只读从实例2连接失败时,JDBC会自动尝试连接只读从实例3。

    • 只有当只读从实例1、只读从实例2和只读从实例3均连接失败时,系统才会提示连接失败。

从Hologres V2.0.10版本开始,targetServerType参数支持更加丰富的取值,扩展负载均衡使用的场景。

使用说明

前提条件

  • 已购买多个Hologres只读从实例并绑定至Hologres主实例,详情请参见配置共享存储的主从实例

  • 已下载42.3.2以上版本的Postgres JDBC驱动,详情请参见JDBC

命令格式

JDBC配置多个只读从实例需要在连接URL中将多个只读从实例的Endpoint:Port信息用半角逗号(,)分隔,命令格式如下:

jdbc:postgresql://<Endpoint1>:<Port1>,<Endpoint2>:<Port2>,<Endpoint3>:<Port3>.../<DBNAME>?user=<AccessKey ID>&password=<AccessKey Secret>&targetServerType=any&loadBalanceHosts=<value>[&hostRecheckSeconds=<value>]

参数说明

参数

描述

Endpoint

Hologres实例的网络地址。

进入Hologres管理控制台实例详情页获取网络地址。

Port

Hologres实例的端口。

进入Hologres管理控制台实例详情页获取端口。

DBNAME

Hologres创建的数据库名称。

AccessKey ID

当前阿里云账号的AccessKey ID。

您可以单击AccessKey 管理,获取AccessKey ID。

AccessKey Secret

当前阿里云账号的AccessKey Secret。

您可以单击AccessKey 管理,获取AccessKey Secret。

targetServerType

允许连接到指定状态的只读从实例。取值为any,即表示可以连接到URL中的任意Endpoint。

仅Hologres V2.0.10及以上版本支持如下取值:

  • master:仅连接主实例。

  • slave:仅连接只读从实例。

  • preferSlave:优先连接只读从实例,如果连接不上只读从数据库才连接到主实例。

JDBC会根据GUC参数in_hot_standby的取值判断实例为主实例还是只读从实例。in_hot_standby取值说明如下:

  • off:为主实例。

  • on:为只读从实例。

loadBalanceHosts

指定尝试连接只读从实例的顺序,取值如下:

  • False(默认):按连接URL内顺序连接只读从实例。

  • True:随机连接只读从实例。

hostRecheckSeconds

可连接Endpoint列表的缓存时间,默认为10s

若您希望调整该缓存的时间,可以修改hostRecheckSeconds的参数值,如下示例将缓存改为30s

jdbc:postgresql://{ENDPOINT1}:{PORT1},{ENDPOINT2}:{PORT2},{ENDPOINT3}:{PORT3}.../{DBNAME}?targetServerType=any&loadBalanceHosts=true&hostRecheckSeconds=30

说明

关于更多JDBC配置说明,详情请参见JDBC的手册

使用示例

  • 如下示例会将查询随机分发到三个只读从实例上,并且当其中一个实例连接失败时,JDBC会自动尝试切换到另一个实例连接。

    import java.sql.*;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Properties;
    
    public class hatest {
        public static void main(String[] args) {
            // 设置Hologres实例的连接endpoint,只读从实例1
            String endpoint1 = "hgpostcn-cn-wxxxxxxxx01-cn-shanghai.hologres.aliyuncs.com:80";
            // 设置Hologres实例的连接endpoint,只读从实例2
            String endpoint2 = "hgpostcn-cn-wxxxxxxxx02-cn-shanghai.hologres.aliyuncs.com:80";
            // 设置Hologres实例的连接endpoint,只读从实例3
            String endpoint3 = "hgpostcn-cn-wxxxxxxxx03-cn-shanghai.hologres.aliyuncs.com:80";      
            // 设置待连接的数据库名
            String dbname = "postgres";
            String jdbcUrl = "jdbc:postgresql://" + endpoint1 + "," + endpoint2 + "," + endpoint3 + "/" + dbname;
            Properties properties = new Properties();
            // 设置连接数据库的用户名
            properties.setProperty("user", "xxxx");
            //设置连接数据库的密码
            properties.setProperty("password", "xxxx");
            // 配置targetServerType,此处配置为any,表示可以对任意endpoint发送请求
            properties.setProperty("targetServerType", "any");
            // 配置LoadBalance策略,此处配置true,表示开启LoadBalance
            properties.setProperty("loadBalanceHosts", "true");
            // 配置hostRecheckSeconds时间,此处配置为10秒
            properties.setProperty("hostRecheckSeconds", "10");
            try {
                Class.forName("org.postgresql.Driver");
                Connection connection = DriverManager.getConnection(jdbcUrl, properties);
                PreparedStatement preparedStatement = connection.prepareStatement("show hg_frontend_endpoints;" );
                ResultSet resultSet = preparedStatement.executeQuery();
                while (resultSet.next()) {
                    ResultSetMetaData rsmd = resultSet.getMetaData();
                    int columnCount = rsmd.getColumnCount();
                    Map map = new HashMap();
                    for (int i = 0; i < columnCount; i++) {
                        map.put(rsmd.getColumnName(i + 1).toLowerCase(), resultSet.getObject(i + 1));
                    }
                    System.out.println(map);
                }
            } catch (Exception exception) {
                exception.printStackTrace();
            }
        }
    }
  • 如下示例可以将查询轮询100次分发到2个实例上。

    import java.sql.*;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Properties;
    
    public class hatest {
        public static void main(String[] args) {
            int x = 1;
            while( x <= 100 ){
                // 设置Hologres实例的连接endpoint,主实例
                String endpoint1 = "hgpostcn-cn-wxxxxxxxx04-cn-hangzhou.hologres.aliyuncs.com:80";
                // 设置Hologres实例的连接endpoint,只读从实例
                String endpoint2 = "hgpostcn-cn-wxxxxxxxx05-cn-hangzhou.hologres.aliyuncs.com:80";
                // 设置待连接的数据库名
                String dbname = "postgres";
                String jdbcUrl = "jdbc:postgresql://" + endpoint1 + "," + endpoint2 + "/" + dbname ;
                Properties properties = new Properties();
                // 设置连接数据库的用户名
                properties.setProperty("user", "xxx");
                // 设置连接数据库的密码
                properties.setProperty("password", "xxx");
                // 配置targetServerType,此处配置为any,表示可以对任意endpoint发送请求
                properties.setProperty("targetServerType", "any");
                // 配置LoadBalance策略,此处配置true,表示开启LoadBalance
                properties.setProperty("loadBalanceHosts", "true");
                // 配置hostRecheckSeconds时间,此处配置为10秒
                properties.setProperty("hostRecheckSeconds", "10");
                try {
                    Class.forName("org.postgresql.Driver");
                    Connection connection = DriverManager.getConnection(jdbcUrl, properties);
                    PreparedStatement preparedStatement = connection.prepareStatement("show hg_frontend_endpoints;" );
                    ResultSet resultSet = preparedStatement.executeQuery();
                    while (resultSet.next()) {
                        ResultSetMetaData rsmd = resultSet.getMetaData();
                        int columnCount = rsmd.getColumnCount();
                        Map map = new HashMap();
                        for (int i = 0; i < columnCount; i++) {
                            map.put(rsmd.getColumnName(i + 1).toLowerCase(), resultSet.getObject(i + 1));
                        }
                        System.out.println(map);
                    }
                } catch (Exception exception) {
                    exception.printStackTrace();
                }
                x++;
            }
        }
    }

    此时观察两个实例的监控信息,可以看到两个实例的连接数基本一致,查看实例的监控信息请参见查看监控指标

    • 实例hgpostcn-cn-wxxxxxxxx04的监控信息。image..png

    • 实例hgpostcn-cn-wxxxxxxxx05的监控信息。image..png