'HikariCP'에 해당하는 글 2건

Spring Boot 프로젝트의 jar 배포 테스트를 위해 구동 테스트를 했다.

  1. bootRun (O)
  2. cmd - bootJar 실행 (X)
  3. docker - bootJar 실행 (O)

 

bootRun 을 통과하고 bootJar 를 만들어 cmd 에서 실행을 했는데 db connection 타이밍에 에러가 발생했다.

 

> java -jar example-0.0.1-SNAPSHOT.jar
...
com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Exception during pool initialization.

java.sql.SQLNonTransientConnectionException: Could not create connection to database server. Attempted reconnect 3 times. Giving up.
Caused by: com.mysql.cj.exceptions.CJCommunicationsException: Communications link failure.
...
The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server.
Caused by: javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)

 

mysql-ssl

 

ㅋㅋ 아니 뭐 요점만 간단히 알려주든가, '왜' 에러 났는지를 알려줘야지 거참...

 

bootRun 은 되는데, jar 실행에서 db 연결이 안된다? ... ㅡㅡ;; 우선 구글을 겁나 뒤졌다. 하... 그 많은 게시물들 중 DB 연결해 놓고 jar 실행한 사람이 하나 없냐... 다들 톰캣 띄운 Hello World... 나도 헬로월드 좋아하긴 하지만... 개발군 헬창들... 저 에러메시지는 너무 광범위하고, 내 상황을 검색어로 표현하기는 더 어려웠다. 구글 검색 페이지를 넘기다넘기다 중국말까지 나오기 시작했다. 아니... 이 아이러니한 상황이 납득이가 안갔다. 이렇게 시간을 낭비할 수는 없어 docker 를 일단 돌려봤다. 엥... 도커는 또 잘됨; 최종 목적이 도커라 로컬은 그냥 넘겨도 되지만 또또... 오늘한 삽질이 아까워 그 끝을 보기로 했다.

 

spring-boot-starter-web
spring-boot-starter-data-jdbc
mysql-connector-java:8.0.25

 

우선 톰캣만 띄웠더니 문제없다. 바로 dataSource 연결했더니 똑같은 오류...! 아니 원래 로컬에서 DB 연결이 안됐었나; 더 해볼게 없었지만 마지막으로, 혹시나 하는 마음에 connector 버전을 바꿔보았다.

 

mysql-connector-java:5.1.49

 

당연히 같은 자리에서 오류가 발생하기는 했는데, 해결책이 나왔다!

 

Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification.

 

Mysql 5.5.45+, 5.6.26+, 5.7.6+ 부터는 ssl 접속이 default 라서, useSSL=false 를 명시하지 않으면 자동으로 ssl 접속을 시도한다는 것이다.

 

jdbc:mysql://example.ap-northeast-2.rds.amazonaws.com:3306/example?...&useSSL=false

 

설마 이거 때문일 줄은 몰랐다. 이렇게 해결되니, 그래도 편히 눈을 감을 수 있게 됐다. 친절한 mysql connector 구버전에게 감사를 표한다.하지만 여전히 황당하기는 하다. ssl 이유라면 bootRun 으로도 되지 말았어야지. 도커에서도 되지 말았어야지...ㅜ

 

지금 보니 처음 발생했던 에러의 저 마지막 줄이 힌트긴 했네...

 

Caused by: javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate)

 

 


WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

,

데이터베이스에서 replication 은 데이터베이스간의 데이터 복제를 의미한다. 하나의 데이터베이스에서 읽기와 쓰기 작업을 동시에 진행할 경우 병목 현상이 발생할 확률이 높다. 이를 방지하기 위해 흔히 Master(Write) 와 Slave(Read) 로 나누어, Master 에서 변경된 데이터를 Slave 로 복제하는 것을 replication 이라고 한다.

 

Java 어플리케이션에서 분기 처리하는 방법은 커넥션 풀(DataSource) 을 Master 와 Slave 로 나누어 주고, JDBC 의 Connection.setReadOnly 메소드나 Spring 의 @Transactional 를 사용하는 것이다.

 

 

Spring Boot 에서의 read / write 분기 처리

 

1. DB 설정 (application.yml)

 

spring:
  datasource:
    master:
      hikari:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://dev.cluster-abc.rds.amazonaws.com:3306/kps
        read-only: false
        username: username
        password: password

    slave:
      hikari:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://dev.cluster-ro-abc.rds.amazonaws.com:3306/kps
        read-only: true
        username: username
        password: password

 

 

2. DataSource Bean 등록

 

@Configuration
public class DataSourceConfiguration {

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.master.hikari")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create()
                .type(HikariDataSource.class)
                .build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.slave.hikari")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create()
                .type(HikariDataSource.class)
                .build();
    }
}

 

 

3. AbstractRoutingDataSource 구현

: Read-Write 가 동시에 필요하거나 복제 지연으로의 이슈를 막기 위해 AbstractRoutingDataSource 구현.

 

public class routingDataSourceImpl extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) ? "slave" : "master";
    }
}

 

 

4. Lazy 설정

: LazyConnectionDataSourceProxy 설정으로 실제 쿼리 직전에 connection 을 생성하여 불필요한 커넥션 점유 방지.

 

@Configuration(proxyBeanMethods = false)
public class DataSourceConfiguration {

    @Primary
    @Bean
    public DataSource dataSource(@Qualifier("routingDataSource") DataSource routingDataSource) {
        return new LazyConnectionDataSourceProxy(routingDataSource);
    }

    @Bean
    public DataSource routingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                        @Qualifier("slaveDataSource") DataSource slaveDataSource) {
        RoutingDataSourceImpl routingDataSourceImpl = new RoutingDataSourceImpl();
        Map<Object, Object> targetDataSource = new HashMap<>();
        targetDataSource.put("master", masterDataSource);
        targetDataSource.put("slave", slaveDataSource);
        routingDataSourceImpl.setTargetDataSources(targetDataSource);
        routingDataSourceImpl.setDefaultTargetDataSource(masterDataSource);
        return routingDataSourceImpl;
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.master.hikari")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create()
                .type(HikariDataSource.class)
                .build();
    }

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.slave.hikari")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create()
                .type(HikariDataSource.class)
                .build();
    }
}

 

 

5. @Transactional 명시

 

@Service
public class ExampleService {

    ...

    @Transactional(readOnly = true)
    public List<ExampleEntity> getList() {
        return exampleRepository.getList();
    }

    @Transactional  // default : readOnly = false
    public Integer insertExample() {
        return exampleRepository.insertExample();
    }
}

 

 

6. 테스트

 

위의 코드를 예로 들면 updateExample() 를 실행하여 데이터 삽입을 확인하고, 

getList() 를 실행했을 때 slave dataSource 가 구성되며 삽입한 데이터가 출력되면 성공.

 

...
HikariPool-1 - configuration:
HikariPool-1 - Starting...
...
HikariPool-2 - configuration:
HikariPool-2 - Starting...
...

 

 


WRITTEN BY
손가락귀신
정신 못차리면, 벌 받는다.

,