Detail spring cloud config hot deployment of datasource
- 2020-12-22 17:38:13
- OfStack
The basic usage of spring cloud config has been covered in the previous blog, if you don't understand, please read the previous blog first
spring cloud config integrated gitlab to build a distributed configuration center
High availability of spring cloud config Distributed configuration Center
Today, we'll focus on how to implement hot deployment of data sources.
1. Configure the data source on the client side
@RefreshScope
@Configuration// Configure the data source
public class DataSourceConfigure {
@Bean
@RefreshScope// Refresh configuration file
@ConfigurationProperties(prefix="spring.datasource") // The auto-configured prefix for the data source
public DataSource dataSource(){
return DataSourceBuilder.create().build();
}
}
With the above steps, you can modify the configuration file on gitlab and refresh it so that the new data source takes effect without the server having to restart.
2. Hot deployment of custom data sources
When we use spring boot to integrate druid, we need to manually configure the data source as follows:
package com.chhliu.springcloud.config;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import com.alibaba.druid.pool.DruidDataSource;
import lombok.extern.slf4j.Slf4j;
/**
*
* Description: Manual initialization without using code DataSource If so, the monitoring interface SQL Monitoring will have no data (" is spring boot the bug???")
* @author chhliu
* Creation time: 2017 years 2 month 9 day In the afternoon 7:33:08
* @version 1.2.0
*/
@Slf4j
@Configuration
@RefreshScope
public class DruidConfiguration {
@Value("${spring.datasource.url}")
private String dbUrl;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.datasource.driverClassName}")
private String driverClassName;
@Value("${spring.datasource.initialSize}")
private int initialSize;
@Value("${spring.datasource.minIdle}")
private int minIdle;
@Value("${spring.datasource.maxActive}")
private int maxActive;
@Value("${spring.datasource.maxWait}")
private int maxWait;
@Value("${spring.datasource.timeBetweenEvictionRunsMillis}")
private int timeBetweenEvictionRunsMillis;
@Value("${spring.datasource.minEvictableIdleTimeMillis}")
private int minEvictableIdleTimeMillis;
@Value("${spring.datasource.validationQuery}")
private String validationQuery;
@Value("${spring.datasource.testWhileIdle}")
private boolean testWhileIdle;
@Value("${spring.datasource.testOnBorrow}")
private boolean testOnBorrow;
@Value("${spring.datasource.testOnReturn}")
private boolean testOnReturn;
@Value("${spring.datasource.poolPreparedStatements}")
private boolean poolPreparedStatements;
@Value("${spring.datasource.maxPoolPreparedStatementPerConnectionSize}")
private int maxPoolPreparedStatementPerConnectionSize;
@Value("${spring.datasource.filters}")
private String filters;
@Value("${spring.datasource.connectionProperties}")
private String connectionProperties;
@Value("${spring.datasource.useGlobalDataSourceStat}")
private boolean useGlobalDataSourceStat;
@Bean // Declare it as Bean The instance
@Primary // In the same DataSource In, the labeled ones are used first DataSource
@RefreshScope
public DataSource dataSource(){
DruidDataSource datasource = new DruidDataSource();
datasource.setUrl(this.dbUrl);
datasource.setUsername(username);
datasource.setPassword(password);
datasource.setDriverClassName(driverClassName);
//configuration
datasource.setInitialSize(initialSize);
datasource.setMinIdle(minIdle);
datasource.setMaxActive(maxActive);
datasource.setMaxWait(maxWait);
datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
datasource.setValidationQuery(validationQuery);
datasource.setTestWhileIdle(testWhileIdle);
datasource.setTestOnBorrow(testOnBorrow);
datasource.setTestOnReturn(testOnReturn);
datasource.setPoolPreparedStatements(poolPreparedStatements);
datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
datasource.setUseGlobalDataSourceStat(useGlobalDataSourceStat);
try {
datasource.setFilters(filters);
} catch (SQLException e) {
log.error("druid configuration initialization filter: "+ e);
}
datasource.setConnectionProperties(connectionProperties);
return datasource;
}
}
Dynamic refreshing of the data source is also possible with the above example. Next, let's look at how spring cloud config implements hot deployment of data sources.
As you can see from the previous blog, the key to dynamic refresh is the request for post refresh, which starts by refreshing the configuration file.
When we make an post refresh request, the request is intercepted by the actuator module, as can be seen from the log file that was started
Mapped "{[/refresh || /refresh.json],methods=[POST]}" onto public java.lang.Object org.springframework.cloud.endpoint.GenericPostableMvcEndpoint.invoke()
Next, let's look at EndPoint as defined by actuator. Then we find the class RefreshEndpoint. The source code of this class is as follows:
@ConfigurationProperties(prefix = "endpoints.refresh", ignoreUnknownFields = false)
@ManagedResource
public class RefreshEndpoint extends AbstractEndpoint<Collection<String>> {
private ContextRefresher contextRefresher;
public RefreshEndpoint(ContextRefresher contextRefresher) {
super("refresh");
this.contextRefresher = contextRefresher;
}
@ManagedOperation
public String[] refresh() {
Set<String> keys = contextRefresher.refresh();
return keys.toArray(new String[keys.size()]);
}
@Override
public Collection<String> invoke() {
return Arrays.asList(refresh());
}
}
From the source code above, we can see that the focus is on the ContextRefresher class. Since the class is too long, the following is the source code of the class:
private RefreshScope scope;
public ContextRefresher(ConfigurableApplicationContext context, RefreshScope scope) {
this.context = context;
this.scope = scope;
}
public synchronized Set<String> refresh() {
Map<String, Object> before = extract(
this.context.getEnvironment().getPropertySources());// 1 , before , load the extract configuration file
addConfigFilesToEnvironment();// 2 , load the configuration file into the environment
Set<String> keys = changes(before,
extract(this.context.getEnvironment().getPropertySources())).keySet();// 3 , replace the value in the original environment variable
this.context.publishEvent(new EnvironmentChangeEvent(keys));// 4 , release change events,
this.scope.refreshAll();
return keys;
}
As you can see from the above code, the focus goes through four steps, as noted in the above code.