postgresql - 在定期更改数据库密码时实现连接重建机制
问题描述
我们正在使用具有AWS RDS IAM 授权功能的 PostgreSQL 数据库——这意味着我们的应用程序需要每 10 分钟左右刷新一次授权令牌(因为令牌的有效期为 15 分钟)。此令牌用作数据库密码,我需要定期更新它。我们正在使用 Dropwizard 框架,该框架利用了处理连接池的Apache Commons DBCP 组件。
我能够增强配置类,以便它执行 AWS API 调用来获取令牌,而不是从配置文件中读取密码。但是,这仅在应用程序启动期间工作一次,持续 15 分钟。我想定期为令牌调用 AWS API 并处理连接的创建以及使旧连接无效。
import org.jooq.Configuration;
import org.jooq.impl.DefaultConfiguration;
import io.dropwizard.setup.Environment;
import org.example.myapp.ApplicationConfiguration;
// more less relevant imports...
@Override
public void run(ApplicationConfiguration configuration, Environment environment) {
Configuration postgresConfiguration = new DefaultConfiguration().set(configuration.getDbcp2Configuration()
.getDataSource())
.set(SQLDialect.POSTGRES_10)
.set(new Settings().withExecuteWithOptimisticLocking(true));
// this DSLContext object needs to be refreshed/recreated every 10 minutes with the new password!
KeysDAO.initialize(DSL.using(postgresConfiguration));
// rest of the app's config
}
我怎样才能实现这样的连接重建机制?org.jooq.ConnectionProvider 看起来很有希望,但我需要更多关于如何定期注入密码的指导(并实现自定义 ConnectionProvider)。任何提示将不胜感激。
编辑:今天早上,我能够确认在重新部署后数据库交互是可能的,并且恰好在 15 分钟后,我得到了第一个例外:
org.postgresql.util.PSQLException: FATAL: PAM authentication failed for user "jikg_service"
at org.postgresql.core.v3.ConnectionFactoryImpl.doAuthentication(ConnectionFactoryImpl.java:514)
at org.postgresql.core.v3.ConnectionFactoryImpl.tryConnect(ConnectionFactoryImpl.java:141)
at org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl(ConnectionFactoryImpl.java:192)
at org.postgresql.core.ConnectionFactory.openConnection(ConnectionFactory.java:49)
at org.postgresql.jdbc.PgConnection.<init>(PgConnection.java:195)
at org.postgresql.Driver.makeConnection(Driver.java:454)
at org.postgresql.Driver.connect(Driver.java:256)
at org.apache.commons.dbcp2.DriverConnectionFactory.createConnection(DriverConnectionFactory.java:39)
at org.apache.commons.dbcp2.PoolableConnectionFactory.makeObject(PoolableConnectionFactory.java:256)
at org.apache.commons.pool2.impl.GenericObjectPool.create(GenericObjectPool.java:868)
at org.apache.commons.pool2.impl.GenericObjectPool.ensureIdle(GenericObjectPool.java:927)
at org.apache.commons.pool2.impl.GenericObjectPool.ensureMinIdle(GenericObjectPool.java:906)
at org.apache.commons.pool2.impl.BaseGenericObjectPool$Evictor.run(BaseGenericObjectPool.java:1046)
at java.base/java.util.TimerThread.mainLoop(Timer.java:556)
at java.base/java.util.TimerThread.run(Timer.java:506)
Suppressed: org.postgresql.util.PSQLException: FATAL: pg_hba.conf rejects connection for host "172.30.19.218", user "my_db_user", database "my_db_development", SSL off
at org.postgresql.core.v3.ConnectionFactoryImpl.doAuthentication(ConnectionFactoryImpl.java:514)
at org.postgresql.core.v3.ConnectionFactoryImpl.tryConnect(ConnectionFactoryImpl.java:141)
at org.postgresql.core.v3.ConnectionFactoryImpl.openConnectionImpl(ConnectionFactoryImpl.java:201)
... 12 common frames omitted
这些异常每分钟都会重复。
解决方案
我欠大家一个解释。我忘了提到一个重要的细节——我们实际上使用的是内部开发的 Dropwizard 的修改版本,它使用捆绑的 Apache Commons DBCP(afaik 不是 Dropwizard 的正式一部分)以及其他组件。我最终放弃了 Apache Commons DBCP 以支持HikariCP——这使得在运行时更新池配置成为可能。虽然没有得到官方支持,但该库的创建者暗示它可能会起作用,并且在我们的场景中它确实有效。下面是一个示例解决方案。
import org.jooq.Configuration;
import org.jooq.impl.DefaultConfiguration;
import io.dropwizard.setup.Environment;
import org.example.myapp.ApplicationConfiguration;
// more less relevant imports...
@Override
public void run(ApplicationConfiguration configuration, Environment environment) {
HikariDataSource hikariDataSource = loadDatabaseConfiguration(configuration.getDatabaseConfiguration());
new DbConfigurationLoader(hikariDataSource).start();
// this DSLContext object now has the reference to DataSource object that has an always-fresh password!
KeysDAO.initialize(DSL.using(hikariDataSource, SQLDialect.POSTGRES_10, new Settings().withExecuteWithOptimisticLocking(true)));
// rest of the app's config
}
private HikariDataSource loadDatabaseConfiguration(DatabaseConfiguration configuration) {
HikariDataSource hikariDataSource = new HikariDataSource();
hikariDataSource.setJdbcUrl(configuration.getJdbcUrl());
hikariDataSource.setDriverClassName(configuration.getDriverClassName());
hikariDataSource.setMinimumIdle(configuration.getMinimumIdle());
hikariDataSource.setMaximumPoolSize(configuration.getMaximumPoolSize());
hikariDataSource.setUsername(configuration.getJdbcUser());
return hikariDataSource;
}
private class DbConfigurationLoader extends Thread {
private final HikariDataSource hikariDataSource;
private final RdsTokenProvider rdsTokenProvider;
public DbConfigurationLoader(HikariDataSource hikariDataSource) {
this.rdsTokenProvider = new RdsTokenProvider();
this.hikariDataSource = hikariDataSource;
}
@Override
public void run() {
while (true) {
hikariDataSource.setPassword(rdsTokenProvider.getToken());
try {
Thread.sleep(/* token is valid for 15 minutes, so it makes sense to refresh it more often */);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
希望这可以在将来节省一些时间。
推荐阅读
- anaconda - 即使 PC 进入睡眠状态,如何让 Jupyter 笔记本保持清醒和运行
- javascript - 类型不匹配。值 (x) 与列索引 1 中的类型编号不匹配
- java - 日历日期不同
- python - 为什么我没有得到以下代码的输出?
- php - 如何从数据库中将值插入列表框中
- opencart - 在目录部分(Opencart)获取登录用户名
- javascript - PHP 嵌入式 JavaScript - ES6 反引号 - 它们在嵌套时工作吗?
- node.js - Bot 在通过模拟器进行测试时未识别意图
- output - @ngrx Angular 6/7 通过组件发射对象
- python - BeautifulSoup 成功写入 html 但 find_all 什么也不返回