-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DB Replication 설정하기 #399
Comments
PrimaryDB
DB 들어가서 확인
Replica DB
참고 |
Spring Boot에서 Master-Slave 설정하기기본적으로 Spring Boot는 하나의 DataSource만 등록되어 있는데, application.properties spring.datasource.master.jdbc-url=jdbc:mariadb://{master ip:port}/gpu_is_mine?characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.master.driverClassName=org.mariadb.jdbc.Driver
spring.datasource.master.username=root
spring.datasource.master.password=password
spring.datasource.slave.jdbc-url=jdbc:mariadb://{slave ip:port}/gpu_is_mine?characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.slave.driverClassName=org.mariadb.jdbc.Driver
spring.datasource.slave.username=root
spring.datasource.slave.password=password ReplicationRoutingDataSource.java public class ReplicationRoutingDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return TransactionSynchronizationManager.isCurrentTransactionReadOnly() ? "slave" : "master";
}
} DataSourceConfig.java @Configuration(proxyBeanMethods=flase_
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean
public DataSource routingDataSource() {
var routingDataSource = new ReplicationRoutingDataSource();
var dataSourceMap = new HashMap<>();
DataSource slaveDataSource = slaveDataSource();
DataSource masterDataSource = masterDataSource();
dataSourceMap.put("master", masterDataSource);
dataSourceMap.put("slave", slaveDataSource);
routingDataSource.setTargetDataSources(dataSourceMap);
routingDataSource.setDefaultTargetDataSource(masterDataSource);
return routingDataSource;
}
@Bean
public DataSource dataSource() {
return new LazyConnectionDataSourceProxy(routingDataSource());
}
} 참고 블로그 에서는 @Configuration
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
@EnableTransactionManagement
@EnableJpaRepositories(basePackages = {"com.mudchobo.example.masterslave"}) 의 많은 설정을 추가해주었지만, Auto-Configuration의 설정 부분을 고려하여 불필요하다 판단하였습니다. ( 기본적으로 Qualifier를 활용한 다른 방식이 경우에는 직접 springContext의 bean을 가져오는 방식. CGLIB proxy생성이 필요 없기 때문에, 성능상의 이점을 위해 proxyBeanMethods=false 처리해준다. (실제 많은 많은 spring의 auto-configuration에서는 proxyBeanMethods=false로 설정되어있다.) @Configuration(proxyBeanMethods = false)
public class DataSourceConfig {
@Bean
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean
public DataSource routingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slaveDataSource") DataSource slaveDataSource) {
var routingDataSource = new ReplicationRoutingDataSource();
var dataSourceMap = new HashMap<>();
dataSourceMap.put("master", masterDataSource);
dataSourceMap.put("slave", slaveDataSource);
routingDataSource.setTargetDataSources(dataSourceMap);
routingDataSource.setDefaultTargetDataSource(masterDataSource);
return routingDataSource;
}
@Bean
@Primary
public DataSource dataSource(@Qualifier("routingDataSource") DataSource routingDataSource) {
return new LazyConnectionDataSourceProxy(routingDataSource);
}
} 참고 |
WARN 21-10-06 20:59:48 [AnnotationConfigServletWebServerApplicationContext:591] - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'flywayInitializer' defined in class path resource [org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration$FlywayConfiguration.class]: Unsatisfied dependency expressed through method 'flywayInitializer' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'flyway' defined in class path resource [org/springframework/boot/autoconfigure/flyway/FlywayAutoConfiguration$FlywayConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.flywaydb.core.Flyway]: Factory method 'flyway' threw exception; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'dataSource' defined in class path resource [mine/is/gpu/config/DataSourceConfig.class]: Unsatisfied dependency expressed through method 'dataSource' parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'routingDataSource' defined in class path resource [mine/is/gpu/config/DataSourceConfig.class]: Unsatisfied dependency expressed through method 'routingDataSource' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'masterDataSource' defined in class path resource [mine/is/gpu/config/DataSourceConfig.class]: Initialization of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.boot.autoconfigure.jdbc.DataSourceInitializerInvoker': Invocation of init method failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'dataSource': Requested bean is currently in creation: Is there an unresolvable circular reference? |
@Profile("prod|was1|was2")
@Configuration(proxyBeanMethods = false)
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
public class DataSourceConfig {
보다 이를 구조적으로 이해했으면, 최선의 방법을 사용했겠지만, 지금의 수준에서는 Circular 의존을 명확하게 이해하지는 못하겠음. 참고 자료Remember a Spring Boot multi-data source circular reference problem |
실제 dev 서버로 올리고 작업했을 때, insert, select query 모두 slave db를 찌르는 문제 발생.
|
순환 의존을 막기 위해서 @EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class}) 이 설정이 필요했군요. |
public class ReplicationRoutingDataSource extends AbstractRoutingDataSource {
private static final Logger logger = LoggerFactory.getLogger(ReplicationRoutingDataSource.class);
public static final String DATASOURCE_KEY_MASTER = "master";
public static final String DATASOURCE_KEY_SLAVE = "slave";
@Override
protected Object determineCurrentLookupKey() {
boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
logger.info("This Transaction is " + isReadOnly);
if (isReadOnly) {
return DATASOURCE_KEY_SLAVE;
}
return DATASOURCE_KEY_MASTER;
}
} |
혹시 |
이 부분도 혹시 몰라서 지금은 넣은 상태입니다! 근데, 안돼..
|
DB 쿼리 이력 갯수 확인
|
@Configuration
@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
@EnableTransactionManagement
public class DataSourceConfig {
private final JpaProperties jpaProperties;
public DataSourceConfig(JpaProperties jpaProperties) {
this.jpaProperties = jpaProperties;
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
@Bean
public DataSource routingDataSource() {
ReplicationRoutingDataSource routingDataSource = new ReplicationRoutingDataSource();
HashMap<Object, Object> sources = new HashMap<>();
DataSource masterDataSource = masterDataSource();
sources.put(DATASOURCE_KEY_MASTER, masterDataSource);
sources.put(DATASOURCE_KEY_SLAVE, slaveDataSource());
routingDataSource.setTargetDataSources(sources);
routingDataSource.setDefaultTargetDataSource(masterDataSource);
return routingDataSource;
}
@Bean
public DataSource dataSource() {
return new LazyConnectionDataSourceProxy(routingDataSource());
}
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
EntityManagerFactoryBuilder entityManagerFactoryBuilder = createEntityManagerFactoryBuilder(jpaProperties);
return entityManagerFactoryBuilder.dataSource(dataSource()).packages("com.example.dbreplication").build();
}
private EntityManagerFactoryBuilder createEntityManagerFactoryBuilder(JpaProperties jpaProperties) {
AbstractJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
return new EntityManagerFactoryBuilder(vendorAdapter, jpaProperties.getProperties(), null);
}
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager tm = new JpaTransactionManager();
tm.setEntityManagerFactory(entityManagerFactory);
return tm;
}
} 왜 이런 방식으로 했을 때에는, 그건 바로 아래의 @Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(HibernateProperties.class)
@ConditionalOnSingleCandidate(DataSource.class)
class HibernateJpaConfiguration extends JpaBaseConfiguration {
} DataSource의 SinglaeCandidate으로 정의될 때에만, HibernateJpaConfiguration의 autoConfiguration 설정이 실행된다. 즉, DataSource의 Candidate가 설정되지 않은 위의 설정은 HibernateJpaConfiguration가 아예 실행되지 않는다. -> 이 부모클래스인 JpaBaseConfiguration의 설정들도 모두 실행되지 않는다. OpenEntityManagerInViewInterceptor 또한 설정이되지 않는 것이다! |
Provide support for auto-configuring multiple datasources Flyway circle에 관한 생각DataSourceInitializerInvoker는 dataSource를 필요로함.
-> Master에 적용하면 Slave에도 반영되는 부분 확인. 하지만, 안정적인 시스템을 위해서는 Application이 뜨는 시점에서 모든 DB에 migration validate를 진행해도 좋을 듯 하다. |
이슈 설명
DB 가용성 증대와 단일 장애점 해소를 위한 Replication 도입
The text was updated successfully, but these errors were encountered: