1.緣起
源自一個匪夷所思的需求:系統(tǒng)需要頻繁采集大量(單表可能超過1億)外部數(shù)據(jù)做審核,由于單機mysql自身限制全景,數(shù)據(jù)量大了之后查詢一次都是問題,因此準備改造成處理一批,歸檔一批丽蝎,也分析過分庫分表分區(qū)方案,這里不在對比膀藐,有時間的話再開一篇討論屠阻。同時,可以隨時切換到歸檔庫查看歷史數(shù)據(jù)额各,并且還可以在歸檔庫繼續(xù)辦業(yè)務(wù)国觉,還可以對歸檔庫歸檔。
2.基礎(chǔ)框架
- springboot 2.6.2
- dynamic-datasource-spring-boot-starter 4.2.0
- mybatis 2.2.0
- spring-cloud-alibaba 2021.1
- druid 1.2.20
其他的就不再啰嗦虾啦,需要主要springcloud-alibaba和springboot的版本對應麻诀,可以去官網(wǎng)查看。
3.設(shè)計思路
- 用戶輸入歸檔使用的新庫名(推薦默認值:原庫_{年月日})
- 備份整個庫
- 生成備份記錄表(主要記錄:原庫名傲醉、新庫名蝇闭、時間等)
- 歸檔庫仍歸檔到原服務(wù)器上(至關(guān)重要),當然硬毕,也可以完整的實現(xiàn)數(shù)據(jù)源配置入庫呻引,略復雜,考慮安全性
- 提供【切換數(shù)據(jù)庫】操作
4.目標
系統(tǒng)運行過程中吐咳,本身master數(shù)據(jù)源查的是mos庫苞七,需要通過點擊【切換數(shù)據(jù)庫】按鈕藐守,將master數(shù)據(jù)源切換為查詢mos_20240421庫。即僅修改數(shù)據(jù)庫名蹂风,其他配置信息依然使用原來的卢厂。
5.實現(xiàn)原理
由于我們本來就使用了多數(shù)據(jù)源,多數(shù)據(jù)源的原理是系統(tǒng)啟動的時候已將數(shù)據(jù)源初始化完成惠啄,將多個數(shù)據(jù)源存放到DynamicRoutingDataSource中慎恒,可以跟進斷點,看到最終使用的數(shù)據(jù)源是DruidDataSource撵渡,因此融柬,只需修改DynamicRoutingDataSource中master對應的jdbcurl中的dbname就可以了。
示例
@RequestMapping("change/{db}")
public void changeDataSource(@PathVariable String db) {
String newDatabase = "1".equals(db) ? "mos" : "mos_bak";
Map<String, DynamicRoutingDataSource> dataSourceMap = beanFactory.getBeansOfType(DynamicRoutingDataSource.class);
for (Map.Entry<String, DynamicRoutingDataSource> entry : dataSourceMap.entrySet()) {
DynamicRoutingDataSource dynamicRoutingDataSource = entry.getValue();
Map<String, DataSource> stringDataSourceMap = dynamicRoutingDataSource.getDataSources();
for (Map.Entry<String, DataSource> dataSourceEntry : stringDataSourceMap.entrySet()) {
String datasourceKey = dataSourceEntry.getKey();
ItemDataSource itemDataSource = (ItemDataSource) dataSourceEntry.getValue();
DruidDataSource druidDataSource = (DruidDataSource) itemDataSource.getDataSource();
if ("master".equals(datasourceKey)) {
DruidDataSource newDatasource = druidDataSource.cloneDruidDataSource();
String jdbcUrl = newDatasource.getRawJdbcUrl();
newDatasource.setUrl(replaceDatabase(jdbcUrl, newDatabase));
itemDataSource.setDataSource(newDatasource);
stringDataSourceMap.put("master", itemDataSource);
}
}
}
}
private static String replaceDatabase(String jdbcUrlString, String newDatabase){
String oldDatabaseName = currentDatabaseName(jdbcUrlString);
return jdbcUrlString.replace(oldDatabaseName,newDatabase);
}
private static String currentDatabaseName(String jdbcUrlString){
String[] a = jdbcUrlString.split("\\?");
return a[0].substring(a[0].lastIndexOf("/")+1);
}
@RequestMapping("get")
public AjaxResult currentDataSource(){
Map<String,String> result = new HashMap<>();
Map<String, DynamicRoutingDataSource> dataSourceMap = beanFactory.getBeansOfType(DynamicRoutingDataSource.class);
for (Map.Entry<String, DynamicRoutingDataSource> entry : dataSourceMap.entrySet()) {
DynamicRoutingDataSource dynamicRoutingDataSource = entry.getValue();
Map<String, DataSource> stringDataSourceMap = dynamicRoutingDataSource.getDataSources();
for (Map.Entry<String, DataSource> dataSourceEntry : stringDataSourceMap.entrySet()) {
String datasourceKey = dataSourceEntry.getKey();
ItemDataSource itemDataSource = (ItemDataSource) dataSourceEntry.getValue();
DruidDataSource druidDataSource = (DruidDataSource) itemDataSource.getDataSource();
result.put(datasourceKey,currentDatabaseName(druidDataSource.getRawJdbcUrl()));
}
}
return AjaxResult.success(result);
}
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (DefaultListableBeanFactory) beanFactory;
}