2022. 11. 25. 01:28ㆍInfra/DevOps
Docker로 MySQL Replication 구축 내용을 간단하게 기록합니다. MySQL Replication을 구축하게 된 계기는 CQRS 패턴을 학습하면서 DB를 이중화하고 읽기와 쓰기를 구분해서 사용하기 위해 구성을 잡아보았습니다. docker로 MySQL Container를 띄워서 사용하였는데, 이번기회에 Docker Compose를 사용하여 띄워보았습니다.
환경 세팅
- Host PC에 MySQL 설치
- Host PC에 Docker, Docker-compose 설치
아래 내용으로 구성할 예정이고, 실제 배포를 한다면 Spring-Application도 docker로 Container에 띄우고 같은 Network에 연결시켜서 통신을 하겠다고 생각이 드는데... 백엔드 실무 경험은 없어서 일단 여기까지만 띄워봤습니다.
위 환경 세팅이 완료되면 docker-dompose.yml 파일을 아래와 같이 작성합니다.
파일 작성방법은 링크를 참고하면 됩니다.
version: "3.3"
services:
db_master:
image: mysql:latest
container_name: db_master
restart: always
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mariadb_root_password
volumes:
- ./master/data:/var/lib/mysql
- ./master/config/:/etc/mysql/conf.d
ports:
- "33306:3306"
networks:
dock_net:
ipv4_address: 172.16.0.10
secrets:
- mariadb_root_password
db_slave:
image: mysql:latest
container_name: db_slave
restart: always
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mariadb_root_password
volumes:
- ./slave/data:/var/lib/mysql
- ./slave/config/:/etc/mysql/conf.d
ports:
- "33307:3306"
networks:
dock_net:
ipv4_address: 172.16.0.11
secrets:
- mariadb_root_password
depends_on:
- db_master
secrets:
mariadb_root_password:
file: ./mariadb_root_password.txt
networks:
dock_net:
networks:
dock_net:
driver: bridge
ipam:
config:
- subnet: 172.16.0.0/16
위 yml 파일을 보면 컨테이너에서 사용될 볼륨을 Host PC에 mount 시켜두었는데, 해당 디렉토리에 master, slave 각각 아래와 같이 설정을 저장합니다.
아래는 mater입니다.
[mysqld]
log-bin=mysql-bin
server-id=1
아래는 slave입니다.
[mysqld]
log-bin=mysql-bin
server-id=2
relay-log=relaylog
log-slave_updates=1
비밀번호는 파일로 관리하는 사례를 보고 고대로 따라해봤습니다. 보통 실무에서는 설정파일을 읽어서 처리하는 부분이다보니 괜찮은 방법이라고 생각했습니다.
설정 잡아주기
기본적으로 아래 구성으로 세팅을 잡았습니다. Master와 Slave가 바이너리 로그를 통해 동기화를 이룹니다. 해당 동작원리는 링크가 상세하게 설명을 하고있습니다. 나중에 저도 기회가 된다면 한번 잘 정리해 보겠습니다.
- master 서버에서 User를 생성하고 공유할 DB를 하나 생성합니다.
- master 서버에서 dump 데이터를 생성합니다.
- dump한 데이터를 host pc로 가져옵니다.
- host pc로 가져온 dump 데이터를 slave로 옮깁니다.
- slave 서버에서 dump한 데이터를 불러옵니다.
- slave에서 master 서버를 연동합니다.
- 테스트 ( mater에서 table에 데이터 추가 후 slave에서 확인 )
위 순서대로 진행하는 과정은 링크의 블로그를 참고하였습니다.
1. Master 서버에서 User 생성 및 DB 생성
mysql> CREATE USER 'repluser'@'%' IDENTIFIED BY 'replpw';
mysql> GRANT REPLICATION SLAVE ON *.* TO 'repluser'@'%';
테스트 DB까지 생성해 보겠습니다.
mysql> CREATE DATABASE repldb;
mysql> USE repldb;
mysql> CREATE TABLE repltable ( no INT(8), PRIMARY KEY (no) );
mysql> DESC repltable;
2. dump 생성
mysqldump -u root -p repldb > dump.sql
3~4. master dump를 slave로 이동
저는 해당 과정을 위 예시 링크와 달리 local에 mount 된 디렉토리를 사용하여 옮겼습니다. 리눅스 명령어를 적절히 사용하면 됩니다.
5. slave에서 dump 데이터 불러오기
mysql> CREATE DATABASE repldb;
mysql> exit
$ mysql -u root -p repldb < dump.sql
6. slave 서버에서 master 연동하기
일단 master에 접속하여 아래 정보를 확인 합니다.
'show master status\G;' 커맨드를 입력하여 결과를 확인합니다. 아래 정보를 slave 서버에 연동하기 위해 입력을 넣어야 합니다.
mysql> show master status\G;
*************************** 1. row ***************************
File: mysql-bin.000015
Position: 157
Binlog_Do_DB:
Binlog_Ignore_DB:
Executed_Gtid_Set:
1 row in set (0.00 sec)
ERROR:
No query specified
아래 명령어를 입력합니다. 위 정보중에 File과 Position 정보를 MASTER_LOG_FILE과 MASTER_LOG_POS에 입력합니다.
mysql> CHANGE MASTER TO MASTER_HOST='mysql-master', MASTER_USER='repluser', MASTER_PASSWORD='replpw', MASTER_LOG_FILE='mysql-bin.000015', MASTER_LOG_POS=157;
mysql> START SLAVE;
정상적으로 connection이 되지 않으면 연결이 안되었다고 표시가 됩니다.
mysql> SHOW SLAVE STATUS\G;
*************************** 1. row ***************************
Slave_IO_State: Waiting for source to send event
Master_Host: db_master
Master_User: repluser
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000015
Read_Master_Log_Pos: 157
Relay_Log_File: relaylog.000014
Relay_Log_Pos: 373
Relay_Master_Log_File: mysql-bin.000015
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 157
Relay_Log_Space: 745
Until_Condition: None
Until_Log_File:
Until_Log_Pos: 0
Master_SSL_Allowed: No
Master_SSL_CA_File:
Master_SSL_CA_Path:
Master_SSL_Cert:
Master_SSL_Cipher:
Master_SSL_Key:
Seconds_Behind_Master: 0
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 1
Master_UUID: 39b0d6fb-682c-11ed-99f7-0242ac150002
Master_Info_File: mysql.slave_master_info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Replica has read all relay log; waiting for more updates
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set:
Executed_Gtid_Set:
Auto_Position: 0
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
Master_public_key_path:
Get_master_public_key: 1
Network_Namespace:
1 row in set, 1 warning (0.00 sec)
SpringBoot에서 아래와 같이 연결정보를 설정합니다.
application.yml 파일 설정 내용입니다.
datasource:
url: jdbc:mysql://127.0.0.1:33307/repldb?useSSL=false&allowPublicKeyRetrieval=true
slave-list:
- name: slave_1
url: jdbc:mysql://127.0.0.1:33307/repldb?useSSL=false&allowPublicKeyRetrieval=true
username: <설정정보>
password: <설정정보>
hibernate:
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
spring:
jpa:
hibernate:
show-sql: true
properties:
hibernate:
enable_lazy_load_no_trans: true
format_sql: true
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
open-in-view: false
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
main:
allow-bean-definition-overriding: true
datasource:
url: jdbc:mysql://127.0.0.1:33306/repldb?useSSL=false&allowPublicKeyRetrieval=true
username: <설정정보>
password: <설정정보>
driver-class-name: com.mysql.cj.jdbc.Driver
maximum-pool-size: 20
logging:
level:
org.springframework.jdbc.datasource.SimpleDriverDataSource: DEBUG
org.hibernate.SQL: DEBUG
요렇게 사용을 하면됩니다. 그리고 Read/Write를 구분하기 위해서는 아래와 같이 Transactional Annotation을 사용하면 됩니다. readOnly를 사용하면 slave DB에서 읽기를 수행하게 됩니다. 어노테이션들에 대해서는 다른 글로 정리해보겠습니다.
@Transactional(readOnly = true)
public AccumulatedPointResponse getAccumulatedPoint(Long memberId ){
AccumulatedPoint accumulatedPoint = accumulatedPointPointRepository.getByMemberId(memberId)
.orElseThrow(AccumulatedPointNotFoundException::new);
return AccumulatedPointResponse.builder().accumulatedPoint(accumulatedPoint).build();
}
@Transactional
public AccumulatedPointResponse updateAccumulatedPoint(AccumulatedPoint accumulatedPoint){
AccumulatedPoint saveResult = accumulatedPointPointRepository.saveAndFlush(accumulatedPoint);
return new AccumulatedPointResponse(saveResult);
}
한번 해보면 별거아닌데, 참 ~ 한번이 오래 걸립니다.
'Infra > DevOps' 카테고리의 다른 글
[kafka] 카프카(kafka) 오픈소스 주요 개념정리 (0) | 2022.12.07 |
---|---|
[Kafka] Kafka Cluster Docker-compose로 구축하기 (0) | 2022.12.07 |
[ERROR] kafka cluster 구성 후 --zookeeper 옵션 사용 시 에러 발생 (0) | 2022.12.07 |