⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 kailing.pub/article/index/arcid/198.html 「kailing」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

Apollo应用之动态调整线上数据源(DataSource)

前言碎语

博主之前写过使用apollo的配置动态推送能力来动态修改线上环境的日志输出级别,具体可见《spring boot动态调整线上日志级别》,今天来实现一个类似的应用场景,动态变更线上数据源。主要应用场景如:测试时不重启服务切换数据源,准生产无缝切换生产环境,应用端读写分离策略动态化等等,更多的使用场景欢迎在留言区补充。

实现思路

通过对主流数据源(c3p0,dbcp2,tomcat jdbc,Hikari)实现的代理,来动态管理应用到数据库的连接,以及实现应用端的读写分离数据链接策略。如上,spring已经有个抽象类AbstractRoutingDataSource很好的实现了。通过AbstractRoutingDataSource对DataSource的管理,使用apollo配置动态推送能力,动态修改AbstractRoutingDataSource中resolvedDataSources数据源实例,可以很好的实现动态变更线上数据源。

关于 AbstractRoutingDataSource

AbstractRoutingDataSource使用Map resolvedDataSources保存了所有可用的数据源实例,预留了一个方法determineCurrentLookupKey给子类去决定使用哪个数据源。这个方法的返回值就是resolvedDataSources中对应的key值,通过这个可以轻松实现应用层面的数据读写分离。当应用程序请求连接时,它拿着子类实现返回的结果去resolvedDataSources中寻找真实的数据源拿数据连接。

具体实现

/**
* @author : kl * @authorboke : kailing.pub
* @create : 2018-03-31 下午5:54
* @description:
**/
@Configuration
@EnableConfigurationProperties(DataSourceProperties.class)
public class DataSourceConfiguration {

Logger logger = LoggerFactory.getLogger(getClass());

private final static String DATASOURCE_TAG = "db";

@Autowired
ApplicationContext context;

@ApolloConfig
Config config;

@Bean("dataSource")
public DynamicDataSource dynamicDataSource() {
DynamicDataSource source = new DynamicDataSource();
source.setTargetDataSources(Collections.singletonMap(DATASOURCE_TAG, dataSource()));
return source;
}
@ApolloConfigChangeListener
public void onChange(ConfigChangeEvent changeEvent) {
SetchangedKeys = changeEvent.changedKeys();
if (changedKeys.contains("spring.datasource.url")) {
DynamicDataSource source = context.getBean(DynamicDataSource.class);
source.setTargetDataSources(Collections.singletonMap(DATASOURCE_TAG, dataSource()));
source.afterPropertiesSet();
logger.info("动态切换数据源为:{}", config.getProperty("spring.datasource.url", ""));
}
}
public DataSource dataSource() {
HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(config.getProperty("spring.datasource.url", ""));
dataSource.setUsername(config.getProperty("spring.datasource.username", ""));
dataSource.setPassword(config.getProperty("spring.datasource.password", ""));
return dataSource;
}
class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() { return DATASOURCE_TAG; }
}
}

实例环境说明

1.本文实现基于spring boot2.0的环境,spring boot2.0中默认数据库连接池用的Hikari,这个连接池性能不俗,按官方说法,他们在程序基础数据结构,字节码,编译器级别做了大量优化,来保证Hikari的优异性能。从spirng boot 切换tomcat jdbc默认实现这波操作来看,Hikari这款连接池的性能应该不赖。

2.spring boot默认开启了DataSource的自动装配,可以通过@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)这种方式排除自动装配

ps:

我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2rgybmkkwtusk

文末结语

通过apollo配置动态推送能力,结合AbstractRoutingDataSource,可以轻松实现线上数据源的动态切换。当然,不仅仅是数据库的数据连接可以动态切换,按照上面的设计实现思路,通过apollo的动态配置能力,可以轻松实现很多的线上动态切换。关于线上数据源切换的应用场景,以及apollo的更多应用场景,欢迎大家在下面留言区留言补充。文中提到的Apollo是携程开源的配置中心项目,顺道推广下。项目地址如下

Apollo项目地址:https://github.com/ctripcorp/apollo

Apollo配置中心技术支持QQ群:375526581,加群可以和博主畅聊技术

ps:2018/6/30补充

上文所述实现方式过于简单漏掉了一个重要的问题,老的连接没有做任何的处理,可能造成连接泄漏。其实在我们完全切换成新的数据库连接前,我们需要获取到老的连接池,并且校验是否有活动链接,直到没有任何活动链接时,我们需要关闭老的连接。具体可参考宋顺前辈提供的代码如:

private boolean terminateHikariDataSource(HikariDataSource dataSource) {
HikariPoolMXBean poolMXBean = dataSource.getHikariPoolMXBean();

//evict idle connections
poolMXBean.softEvictConnections();

if (poolMXBean.getActiveConnections() > 0 && retryTimes < MAX_RETRY_TIMES) {
logger.warn("Data source {} still has {} active connections, will retry in {} ms.", dataSource,
poolMXBean.getActiveConnections(), RETRY_DELAY_IN_MILLISECONDS);
return false;
}

if (poolMXBean.getActiveConnections() > 0) {
logger.warn("Retry times({}) >= {}, force closing data source {}, with {} active connections!", retryTimes,
MAX_RETRY_TIMES, dataSource, poolMXBean.getActiveConnections());
}

dataSource.close();

return true;
}

完整的demo实例:https://github.com/ctripcorp/apollo-use-cases/tree/master/dynamic-datasource

文章目录
  1. 1. 前言碎语
  2. 2. 实现思路
  3. 3. 关于 AbstractRoutingDataSource
  4. 4. 具体实现
  5. 5. 实例环境说明
  6. 6. 文末结语