You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否用HikariCP+Hibernate实现MySQL读实例的连接轮询与负载均衡?

实现方案

一、随机绑定读实例(HikariCP + Hibernate)

核心思路是自定义DataSource包装多个HikariCP实例,每个实例对应一个读实例的JDBC URL,在获取连接时随机选择一个Hikari实例,让每个连接绑定到固定的读实例。

步骤1:初始化多读实例的Hikari数据源集合

为每个读实例单独配置并初始化HikariDataSource:

List<HikariDataSource> readDataSources = new ArrayList<>();

// 读实例1配置
HikariConfig config1 = new HikariConfig();
config1.setJdbcUrl("jdbc:mysql://read-instance-1:3306/your_db");
config1.setUsername("db_user");
config1.setPassword("db_pass");
config1.setMaximumPoolSize(20); // 根据业务调整连接池大小
readDataSources.add(new HikariDataSource(config1));

// 读实例2、3同理配置
HikariConfig config2 = new HikariConfig();
config2.setJdbcUrl("jdbc:mysql://read-instance-2:3306/your_db");
// 其他配置...
readDataSources.add(new HikariDataSource(config2));

HikariConfig config3 = new HikariConfig();
config3.setJdbcUrl("jdbc:mysql://read-instance-3:3306/your_db");
// 其他配置...
readDataSources.add(new HikariDataSource(config3));

步骤2:实现自定义随机选择DataSource

包装上述Hikari数据源集合,在getConnection()时随机选取一个实例获取连接:

public class RandomReadDataSource implements DataSource {
    private final List<HikariDataSource> readDataSources;
    private final Random random = new Random();

    public RandomReadDataSource(List<HikariDataSource> readDataSources) {
        this.readDataSources = readDataSources;
    }

    @Override
    public Connection getConnection() throws SQLException {
        int randomIndex = random.nextInt(readDataSources.size());
        return readDataSources.get(randomIndex).getConnection();
    }

    // 实现DataSource接口的其他方法,直接委托给默认实例或选中的实例
    @Override
    public Connection getConnection(String username, String password) throws SQLException {
        return getConnection();
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return readDataSources.get(0).unwrap(iface);
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return readDataSources.get(0).isWrapperFor(iface);
    }

    // 关闭所有Hikari数据源
    public void shutdown() {
        readDataSources.forEach(HikariDataSource::close);
    }
}

步骤3:配置Hibernate使用自定义DataSource

如果是Spring Boot环境,直接在配置文件指定自定义DataSource类型:

spring.jpa.hibernate.ddl-auto=none
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
spring.datasource.type=com.yourpackage.RandomReadDataSource

纯Hibernate环境则在Configuration中设置:

Configuration hibernateConfig = new Configuration();
hibernateConfig.setProperty("hibernate.connection.datasource", new RandomReadDataSource(readDataSources));

二、基于应用端连接数选择负载最低的读实例

要实现“选连接数最少的实例”,需在自定义DataSource中统计每个读实例的活跃连接数,并在获取连接时筛选计数最小的数据源。

步骤1:维护数据源与连接数计数器的绑定

为每个Hikari数据源关联原子计数器,实时记录活跃连接数:

public class LoadBalancedReadDataSource implements DataSource {
    private static class DataSourceCounterPair {
        HikariDataSource dataSource;
        AtomicInteger activeConnCount;

        DataSourceCounterPair(HikariDataSource dataSource) {
            this.dataSource = dataSource;
            this.activeConnCount = new AtomicInteger(0);
        }
    }

    private final List<DataSourceCounterPair> readDataSources;

    public LoadBalancedReadDataSource(List<HikariDataSource> rawDataSources) {
        this.readDataSources = rawDataSources.stream()
                .map(DataSourceCounterPair::new)
                .collect(Collectors.toList());
    }

步骤2:代理Connection实现计数更新

通过动态代理包装Connection,在连接关闭时递减计数;获取连接时递增计数并选择负载最低的数据源:

private Connection wrapConnection(Connection rawConn, DataSourceCounterPair targetPair) {
    return (Connection) Proxy.newProxyInstance(
            Connection.class.getClassLoader(),
            new Class[]{Connection.class},
            (proxy, method, args) -> {
                if ("close".equals(method.getName())) {
                    targetPair.activeConnCount.decrementAndGet();
                }
                return method.invoke(rawConn, args);
            }
    );
}

@Override
public Connection getConnection() throws SQLException {
    // 筛选活跃连接数最少的数据源
    DataSourceCounterPair selected = Collections.min(readDataSources,
            Comparator.comparingInt(pair -> pair.activeConnCount.get()));
    
    selected.activeConnCount.incrementAndGet();
    return wrapConnection(selected.dataSource.getConnection(), selected);
}

步骤3:完成DataSource接口其他方法实现

参考RandomReadDataSource的方式,将unwrap、isWrapperFor等方法委托给数据源实例,同时实现全局关闭方法。

注意事项

  • 连接池大小适配:每个读实例的Hikari连接池大小需根据业务负载单独配置,避免单实例连接耗尽。
  • 自动伸缩适配:如果读实例支持自动伸缩,需在自定义DataSource中添加动态更新数据源列表的逻辑(比如监听服务发现事件,新增/移除数据源)。
  • 读写分离路由:若存在写操作,需单独配置主库数据源,通过Hibernate的@Transactional(readOnly = true)或自定义路由逻辑,将读请求导向多实例读库。

内容的提问来源于stack exchange,提问作者Dean Hiller

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 21:52:43