能否用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
相关产品推荐
相关产品推荐

