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.


Related articles: