序文
この記事のネタ元は友人との技術交流で、当時友人から「Apolloはnacosほど使いにくいし、Apolloのせいでオンライン事故も起きた」とぼやかれていました。
物語の背景は大まかに次のようなものです
少し前に、私の友人の部門のデータベースがダウンし、業務が正常に運営できなくなりました。そのとき、私の友人のデータベース情報は Apollo 上に設定されていました。私の友人のアイデアは、データベースがダウンしたときに切り替えられるというものでした。データ復旧を実現するためにApollo上に設定されたデータベース情報 ソース熱変化 しかし、データベースがダウンしたとき、その友人は自分の考えに従って運用していましたが、状況が想像どおりではないことがわかりました。データ ソースを変更した後、ビジネス サービス接続が依然として古いデータベース サービスであることがわかりました。彼らには選択の余地がありませんでした。 dba処理に連絡してください。
後日、友人の説明を聞いた後、当時熱心にデータベースをどうやって作っていたのかと尋ねたところ、「とても簡単です。Apollo 上でデータソースの情報を設定するだけです。データソースを変更したい場合は、変更するだけです」との答えでした。 apollo のポータルで直接変更します。友人の話を聞いた後、私は尋ねました、それでは?友人の答えはこうだ。「それでは?」もうない。
そのやりとりを経て今日の記事ができました。今日はデータソースの動的な熱心さを実現するための apollo と druid の統合について話します。
核となるアイデアを実現する
Apollo の構成変更の動的監視 + Spring AbstractRoutingDataSource データソース切り替え用の予約メソッド決定CurrentLookupKey
コアロジックの実装を紹介する前に、構成センターについて話しましょう
構成センターとは何ですか?
構成センターは、アプリケーションのさまざまな構成を一元管理する基本的なサービス コンポーネントです。彼の核心は、構成の統合管理です。管理するカテゴリは構成であり、データ ソースなどの構成に依存するオブジェクトについては、構成センターでは管理されません。なぜ私が一人でこれを取り上げるのですか?というのも、友人はapollo上で設定を変更すると、その設定が依存しているデータソースも一緒に変更されるのではないかと勘違いしていたようです。
コアコード
1. 動的データソースを作成し、元のデータソースをプロキシします。
public class DynamicDataSource extends AbstractRoutingDataSource {
public static final String DATASOURCE_KEY = "db";
@Override
protected Object determineCurrentLookupKey() {
return DATASOURCE_KEY;
}
public DataSource getOriginalDetermineTargetDataSource(){
return this.determineTargetDataSource();
}
}
@Configuration
@EnableConfigurationProperties(BackupDataSourceProperties.class)
@ComponentScan(basePackages = "com.github.lybgeek.ds.switchover")
public class DynamicDataSourceAutoConfiguration {
@Bean
@ConditionalOnMissingBean
@Primary
@ConditionalOnClass(DruidDataSource.class)
public AbstractDataSourceManger abstractDataSourceManger(DataSourceProperties dataSourceProperties, BackupDataSourceProperties backupDataSourceProperties){
return new DruidDataSourceManger(backupDataSourceProperties,dataSourceProperties);
}
@Bean("dataSource")
@Primary
@ConditionalOnBean(AbstractDataSourceManger.class)
public DynamicDataSource dynamicDataSource(AbstractDataSourceManger abstractDataSourceManger) {
DynamicDataSource source = new DynamicDataSource();
DataSource dataSource = abstractDataSourceManger.createDataSource(false);
source.setTargetDataSources(Collections.singletonMap(DATASOURCE_KEY, dataSource));
return source;
}
}
ここで注意すべき点の 1 つは、DynamicDataSource の Bean 名は dataSource でなければならないということです。その目的は、Spring のデフォルトのデータソースによって取得される Bean を DynamicDataSource にすることです。
2. 構成の変更を監視し、データ ソースを切り替える
データソースを切り替える
@ApolloConfigChangeListener(interestedKeyPrefixes = PREFIX)
public void onChange(ConfigChangeEvent changeEvent) {
refresh(changeEvent.changedKeys());
}
/**
*
* @param changedKeys
*/
private synchronized void refresh(Set<String> changedKeys) {
/**
* rebind configuration beans, e.g. DataSourceProperties
* @see org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder#onApplicationEvent
*/
this.applicationContext.publishEvent(new EnvironmentChangeEvent(changedKeys));
/**
* BackupDataSourceProperties rebind ,you can also do it in PropertiesRebinderEventListener
* @see PropertiesRebinderEventListener
*/
backupDataSourcePropertiesHolder.rebinder();
abstractDataSourceManger.switchBackupDataSource();
}
@SneakyThrows
@Override
public void switchBackupDataSource() {
if(backupDataSourceProperties.isForceswitch()){
if(backupDataSourceProperties.isForceswitch()){
log.info("Start to switch backup datasource : 【{}】",backupDataSourceProperties.getBackup().getUrl());
DataSource dataSource = this.createDataSource(true);
DynamicDataSource source = applicationContext.getBean(DynamicDataSource.class);
DataSource originalDetermineTargetDataSource = source.getOriginalDetermineTargetDataSource();
if(originalDetermineTargetDataSource instanceof DruidDataSource){
DruidDataSource druidDataSource = (DruidDataSource)originalDetermineTargetDataSource;
ScheduledExecutorService createScheduler = druidDataSource.getCreateScheduler();
createScheduler.shutdown();
if(!createScheduler.awaitTermination(backupDataSourceProperties.getAwaittermination(), TimeUnit.SECONDS)){
log.warn("Druid dataSource 【{}】 create connection thread force to closed",druidDataSource.getUrl());
createScheduler.shutdownNow();
}
}
//当检测到数据库地址改变时,重新设置数据源
source.setTargetDataSources(Collections.singletonMap(DATASOURCE_KEY, dataSource));
//调用该方法刷新resolvedDataSources,下次获取数据源时将获取到新设置的数据源
source.afterPropertiesSet();
log.info("Switch backup datasource : 【{}】 finished",backupDataSourceProperties.getBackup().getUrl());
}
}
}
3. テスト
@Override
public void run(ApplicationArguments args) throws Exception {
while(true){
User user = userService.getById(1L);
System.err.println(user.getPassword());
TimeUnit.SECONDS.sleep(1);
}
}
切り替える前にコンソールに表示される
切り替え後、コンソールに表示される
要約する
上記は、apollo と druid の統合を実現してデータ ソースの動的な熱心さを実現するという全体的なアイデアですが、実装にはまだ問題があります。つまり、処理されていない古い接続が存在します。サンプル コードでは何も処理していませんが、コード内では getOriginalDetermineTargetDataSource が予約されており、getOriginalDetermineTargetDataSource を通じていくつかの追加操作を行うことができます。
この記事の実装は、github の apollo が提供するケースを使用して実装することもできます。リンクは次のとおりです。
https://github.com/apolloconfig/apollo-use-cases/tree/master/dynamic-datasource
彼の場合、接続の切り替え後、古いデータ ソースがクリーンアップされます。その中のデータソースはHikariDataSourceです apollo提供のケースを使用する場合、druidデータソースを使用する場合はdruidのソースコードのクローズ部分を掲載します
そして、接続ソースコードを取得します。
注意点としては、druid データソースが閉じられているときに、この時点で接続が入ってくると、この時点で DataSourceDisableException が報告され、プロジェクトが異常終了します。
最後に、少しだけ付け加えさせていただきます。以前、友人が「Apollo は nacos ほど使いにくい」と言っていたのですが、私には抵抗がありました。実際、技術の品質を測るには、現場を持ってくる必要があります。シーン、テクノロジーの利点は欠点になる可能性があります
デモリンク
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-datasource-hot-switchover