前言
本文的素材起源与某次和敌人技术交换,过后敌人就跟我吐槽说apollo不如nacos好用,而且他们还因为apollo产生过一次线上事变。
故事的背景大略是如下
前阵子敌人部门的数据库产生宕机,导致业务无奈失常操作,过后敌人他们数据库信息是配置在apollo上,敌人的想法是当数据库宕机时,能够通过切换配置在apollo上的数据库信息,实现数据源热变更。但当他们数据库产生宕机时,敌人按他的想法操作,发现事件并不像他设想的那样,他们更换数据源后,发现业务服务连贯依然是旧的数据库服务,前面没方法他们只能分割dba解决。
后边我听了敌人的形容后,我就问他说,你们过后数据库热切是怎么做的,他的答复是:很简略啊,就把数据源信息配置在apollo上,如果要变更数据源,就间接在apollo的portal上变更一下啊。听了敌人话,我就问而后呢?敌人的答复是:什么而后?就没而后了啊。
通过那次交换,就有了明天的文章,明天咱们就来聊聊apollo与druid整合实现数据源动静热切
实现外围思路
apollo的配置变更动静监听 + spring AbstractRoutingDataSource预留办法determineCurrentLookupKey来做数据源切换
在介绍实现外围逻辑之前,咱们来聊一下配置核心
何为配置核心?
配置核心是一种对立治理各种利用配置的根底服务组件。他的外围是对配置的对立治理。他治理的领域是配置,至于对配置有依赖的对象,比方数据源,他是不归配置核心来治理。为什么我会独自提这个?是因为敌人仿佛陷入了一个误区,认为在apollo上变更了配置,这个配置依赖的数据源也会一起跟着变更
外围代码
1、创立动静数据源,代理原来的datasource
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; }}
这边有个须要留神的点就是DynamicDataSource的bean名称肯定是须要为dataSource,目标是为了让spring默认的datasource取到的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来做额定一些操作。
本文的实现形式还能够应用apollo在github提供的case来实现,链接如下
https://github.com/apolloconfig/apollo-use-cases/tree/master/dynamic-datasource
他这个case在进行连贯切换后,会对老的数据源进行连贯清理。他外面的用数据源是HikariDataSource,如果你用apollo提供的case,当你是应用druid数据源时,我贴下druid的敞开局部源码
以及获取connection源码
这边有个留神点就是,当druid数据源进行敞开时,如果此时恰好有连贯进来,此时就会报DataSourceDisableException,而后导致我的项目异样退出
最初说点额定的,之前敌人说apollo比nacos不好用啥的,我是持保留意见的,其实掂量一个技术的好坏,是要带上场景的,在某些场景,技术的具备的劣势可能反而成了劣势
demo链接
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-datasource-hot-switchover