μ¬μ©λ°°κ²½
νμ¬ μ§ννκ³ μλ νλ‘μ νΈμμ μΏ ν° μμ€ν μ λ§λ€κ³ μμ΅λλ€. νμ¬ μμ€ν μ λ΄λΆ μΈμλ€λ§ μ¬μ©μ νκ³ μκΈ° λλ¬Έμ λΆνμ λν λ¬Έμ λ₯Ό κ±±μ νμ§ μμλ λμ§λ§, ν΄λΉ μμ€ν μ΄ μ€μ μμ©μλΉμ€κ° λμ΄ λ§μ μ¬μ©μλ€μ΄ μ¬μ©νλ€κ³ κ°μ νλ€λ©΄ λΆλͺ μλ²μ DBμ λ§μ λΆνκ° μ€κ² λ κ²μ λλ€.
μ΄λ² κΈμμλ λΆν λΆμ° μ€ DBμμμ μ²λ¦¬ λ°©λ²μ νλ² κ³ λ―Όν΄λ³΄κ³ μ ν©λλ€. κ·Έλ¦¬κ³ κ³ λ―Όμ κ²°κ³Όμ μ μ©ν κ³Όμ μ λν΄μλ ν¨κ» μμ±ν΄λ³΄λ €κ³ ν©λλ€.
DB μ±λ₯ νμ₯ λ°©λ²
DBμ μ±λ₯μ λμ΄κΈ° μν΄μλ Scale up
λλ Scale out
νμ¬ μ±λ₯μ λμ¬μΌ ν©λλ€. νμ¬ μ§ννλ νλ‘μ νΈμμλ Scale up
μ νκΈ° μν μμμ μ 곡λ°μ μ μλ μν©μ΄κΈ° λλ¬Έμ, Scale up
μ κ³ λ €νμ§ μκ³ Scale out
μ μ μ©νκΈ°λ‘ κ²°μ νμ΅λλ€.
DBλ₯Ό Scale out
νλ λ°©λ²μ Clusterning
κ³Ό Replication
μΌλ‘ ꡬλΆν μ μμ΅λλ€. νλ² κ°κ°μ λ°©λ²μ λν΄μ μμ보λλ‘ ν©μλ€.
Clustering
Clusterning
μ DB μλ²λ₯Ό 2λ μ΄μ, DB μ€ν μ§λ¦¬λ₯Ό 1λλ‘ κ΅¬μ±νλ ννμ
λλ€. DB μλ² 2λλ₯Ό λͺ¨λ Active
μνλ‘ μ΄μνλ€λ©΄, DB μλ² 1λκ° μ£½λλΌλ λ€λ₯Έ DB μλ² 1λλ μ΄μμμ΄ μλΉμ€λ₯Ό μ μμ μΌλ‘ ν μ μμ΅λλ€. λν DB μλ²λ₯Ό μ¬λ¬λλ‘ λκ²λλ©΄, νΈλν½μ λΆμ°νμ¬ κ°λΉνκ² λμ΄ CPUμ Memory λΆνκ° μ μ΄μ§λ μ₯μ μ΄ μ‘΄μ¬ν©λλ€.
νμ§λ§, DB μλ²λ€μ΄ νλμ DB μ€ν 리μ§λ₯Ό 곡μ νκΈ° λλ¬Έμ DB μ€ν 리μ§μ λ³λͺ©μ΄ μκΈ°λ λ¨μ μ΄ μ‘΄μ¬ν©λλ€.
μ΄λ₯Ό ν΄κ²°νκΈ° μν΄μ DB μλ 2λ μ€ 1λλ₯Ό Stand-by
μ¦, μ¬μ©λκΈ° μνλ‘ λμ΄ λ¨μ μ 보μν μ μμ΅λλ€. Stand-by
μνμ μλ²λ Active
μνμ μλ²μ λ¬Έμ κ° μκ²Όμ λ, Fail Over
λ₯Ό νμ¬ μνΈ μ νμ νμ¬ μ₯μ μ λμμ ν μ μμ΅λλ€. μ΄λ₯Ό ν΅ν΄μ, DB μ€ν 리μ§μ λν λ³λͺ© νμλ ν΄κ²°ν μ μμ΅λλ€.
νμ§λ§, Fail Over
κ° λ°μνλ μκ° λμμ μμ€μ΄ μ‘΄μ¬νλ©°, DB μλ² λΉμ©μ κ·Έλλ‘μΈλ° κ°μ©λ₯ μ΄ 1/2κ° λλ€λ λ¨μ μ΄ μ‘΄μ¬ν©λλ€.
λν, Clustering
μ΄ κ°μ§λ λ³Έμ§μ μΈ λ¬Έμ λ‘ DB μ€ν 리μ§μ λ¬Έμ κ° λ°μνλ©΄, λ°μ΄ν°λ₯Ό 볡ꡬν μ μλ€λ μΉλͺ
μ μΈ λ¬Έμ κ° μ‘΄μ¬ν©λλ€.
Replication
Replication
μ κ° DB μλ²κ° κ°μ DB μ€ν 리μ§λ₯Ό κ°μ§κ³ μλ ννμ
λλ€. κ·Έλ¦¬κ³ μ΄λ₯Ό μ€μκ°μΌλ‘ λκΈ°ννλ©΄μ νΈλν½μ μ¬λ¬ MySQL μλ²λ‘ λΆμ°μμΌμ£ΌκΈ° λλ¬Έμ λ°μ΄ν°λ² μ΄μ€λ‘ μΈν λ³λͺ© νμμ κ°μ ν μ μμ΅λλ€.
Master
μ Slave
λ‘ κ΅¬μ±λ ꡬ쑰λ Master
μλ²μλ INSERT, UPDATE, DELETE μμ
μ΄ μ λ¬λκ³ Slave
μλ²μλ SELECT μμ
μ μ λ¬ν©λλ€. Slave
λ κ²°κ΅ Master
μλ²μμ 볡μ λ λ°μ΄ν°μ΄κΈ° λλ¬Έμ λ°μ΄ν°μ μ‘°μμ΄ λ°μν μ μλ INSERT, UPDATE, DELETE μμ
μ Master
λ‘ μ λ¬μ΄ λκ³ μ‘°νλ§μ νλ SELECT μμ
μ Slave
μλ²λ₯Ό ν΅νμ¬ μ§ννκ² λ©λλ€.
μμ κ·Έλ¦Όμμλ Slave
μλ²κ° νλλΏμ΄μ§λ§ μλΉμ€μ λ§κ² Slave
μλ²λ₯Ό μ¬λ¬ κ°λ‘ μ€μ ν μλ μμ΅λλ€. λ°μ΄ν°λ² μ΄μ€μμ λ°μνλ λλΆλΆμ 쿼리λ μ‘°νμΈ SELECTμΈλ° μ΄λ¬ν κ²μ Slave
μλ²λ₯Ό ν΅ν΄ λΆμ°νμ¬ μ²λ¦¬ν μ μμΌλ μ’ λ μ±λ₯ ν₯μμ κ°μ Έκ° μ μμ΅λλ€.
MySQLμμμ Replication
λμκ³Όμ μ μ‘°κΈ λ μμΈν μ΄ν΄λ³΄λ©΄ λ€μκ³Ό κ°μ΅λλ€.
- μ¬μ©μκ° INSERT, UPDATE, DELETE 쿼리λ₯Ό
Master
μλ²λ‘ μμ²νλ©΄,Master
μλ²λ μ΄λ₯Ό μ²λ¦¬νκ³Master
μλ²μμ μΌμ΄λλ λͺ¨λ λ³κ²½ μ΄λ ₯λ€μ λ°μ΄λ리 λ‘κ·Έ μ€λ λλ₯Ό μ¬μ©ν΄μ λ°μ΄λ리 λ‘κ·Έμ μ μ₯νλ€. Slave
μλ²μμ λ³κ²½ λ΄μμ μμ²νλ©΄Master
μλ²μ λ°μ΄λ리 λ‘κ·Έ λ€ν μ€λ λκ° λ°μ΄λ리 λ‘κ·Έλ€μ μ½μ΄μSlave
μλ²λ‘ λ°μ΄ν°λ₯Ό μ λ¬νλ€.Slave
μλ²μ Slave I/O μ€λ λκ° μμ²ν λ³κ²½ λ΄μλ€μSlave
μλ²μ 릴λ μ΄ λ‘κ·Έμ μ μ₯ν©λλ€.- μ΅μ’
μ μΌλ‘
Slave
μλ²μ Slave SQL μ€λ λκ° λ¦΄λ μ΄ λ‘κ·Έμ κΈ°λ‘λ λ°μ΄ν°λ€μ μ½μ΄μSlave
μλ²μ μ μ©ν©λλ€.
λ°μ΄λ리 λ‘κ·Έλ?
Master
λ
Έλμμμ DDL
or DML
κ°μ΄λ° λ°μ΄ν°μ ꡬ쑰λ λ΄μ©μ λ³κ²½νλ λͺ¨λ 쿼리 λ¬Έμ₯κ³Ό μ΄λ ₯μ κΈ°λ‘νλ λ
Όλ¦¬μ μΈ λ‘κ·Έλ₯Ό λ§ν©λλ€.
μ 체μ μΈ νλ¦μ λ€μ λμκ³Ό κ°μ΅λλ€.
κ·Έλμ.. μ΄λ€ μ νμ?
κ²°λ‘ μ μΌλ‘ DB Scale out
μ νλ λ°©λ²μΌλ‘ Replication
μ μ ννκΈ°λ‘ νμ΅λλ€. μ΄λ κ² κ΅¬μ±νκ² λ κ²½μ° DB μ€ν 리μ§λ₯Ό μ¬λ¬κ°λ‘ λκΈ° λλ¬Έμ, λ°μ΄ν° 볡ꡬλ₯Ό λͺ»νλ μν©μ μ΄λμ λ λ°©μ§ν μ μμΌλ©°, Clustering
κ³Ό λ€λ₯΄κ² DB κ°μ©λ₯ μ μ΅λλ‘ κ°μ Έκ° μ μλ€λ μ₯μ μ κ°μ Έκ° μ μλ€κ³ μκ°νκΈ° λλ¬Έμ
λλ€.
μ¬κΈ°μ κ³ λ―ΌμΈ μ μ Slave DB
λ₯Ό λͺλλ‘ μ€μ νλκ° μ
λλ€. νμ¬λ‘μ¨λ μλΉμ€ μ¬μ©μκ° λ§μ§ μκΈ° λλ¬Έμ Master 1
, Slave 1
κ΅¬μ‘°λ‘ μ νν΄λ λ¬Έμ κ° μμ΅λλ€. νμ§λ§ νμ¬ μ§ννλ μ€κ³λ λ€μμ μ¬μ©μλ₯Ό κ³ λ €νμ¬ μ§ννκ³ μκΈ° λλ¬Έμ μΆν νμ₯μ μ©μ΄νλλ‘ Nκ°μ Slave DBλ₯Ό κΈ°μ€μΌλ‘ μ€κ³λ₯Ό νκΈ°λ‘ νμμ΅λλ€.
μ€μ νλ‘μ νΈμλ Master 1
, Slave 2
λ‘ κ΅¬μ±νμ¬ μμ
μ μ§ννμμ΅λλ€.
MySQL μ€μ
Master, Slave DB Server μ¬μ μμ
MySQLμ κΈ°λ³Έμ μΌλ‘ 127.0.0.1
μ¦, λ‘컬 νΈμ€νΈμμλ§ μ μν μ μλλ° μλμ κ°μ΄ MySQL μ€μ μ λ³κ²½νμ¬ μΈλΆμμ μ μμ΄ κ°λ₯νλλ‘ λ³κ²½μ ν΄μΌν©λλ€. λ€μκ³Ό κ°μ΄ λ³κ²½μ νλ©΄ λ©λλ€.
vim /etc/mysql/mysql.conf.d/mysqld.cnf
bind-address = 0.0.0.0
mysqlx-bind-address = 0.0.0.0
Master DB Server μμ
κΈ°λ³Έμ μΈ λ°μ΄ν°λ² μ΄μ€ λΆν° νλ² μμ±ν΄λ³΄λλ‘ νκ² μ΅λλ€. λ¨Όμ Master DB
μ λν λ°μ΄ν°λ² μ΄μ€λ₯Ό μμ±ν©λλ€.
CREATE DATABASE test;
ν μ€νΈν ν μ΄λΈμ΄ μμ΄μΌ νκΈ° λλ¬Έμ ν μ΄λΈλ μμ±ν΄ μ€λλ€.
CREATE TABLE user(
id BIGINT NOT NULL AUTO_INCREMENT,
name VARCHAR(255),
PRIMARY KEY(id)
);
Replication
μ νμν μ μ© κ³μ μ μμ±ν΄μ£Όκ³ , Replication
μ μν κΆνμ λΆμ¬ν΄ μ€λλ€. root
λ₯Ό μ¬μ©ν κ²½μ° λ³΄μμ λ¬Έμ κ° λ μ μκΈ° λλ¬Έμ, λ€μκ³Ό κ°μ΄ Slave
κΆνμ λΆμ¬ν΄ μ€λλ€.
CREATE USER 'master'@'%' IDENTIFIED BY 'password';
GRANT REPLICATION SLAVE ON *.* TO 'master'@'%';
κ·Έλ¦¬κ³ MySQL μ€μ λ³κ²½μ μν΄ my.cnf
νμΌλ‘ μ΄λνμ¬ κ°μ μμ ν΄μ€λλ€.
sudo vim /etc/mysql/my.cnf
[mysqld]
max_allowed_packet=1000M
server-id = 1
log-bin = mysql-bin
binlog_format = ROW
max_binlog_size = 500M
sync_binlog = 1
expire-logs-days = 7
binlog_do_db = test
μ€μ λ€μ μΈλΆ κ°λ€μ λ€μκ³Ό κ°μ΅λλ€.
- maxallowedpacket : μλ²λ‘ μ§μνκ±°λ λ°κ²λλ ν¨ν·μ μ΅λ κΈΈμ΄λ₯Ό μ€μ
- server-id : μλ²μ ID, λ ν리μΌμ΄μ ν ν리μ§(μ°κ²°λ λ§)μμλ κ³ μ ν κ°κ°μ μλ² IDλ₯Ό κ°μ ΈμΌ ν¨
- log-bin : λ°μ΄λ리 λ‘κ·Έ νμΌ κ²½λ‘ β (/var/lib/mysql/mysql-bin.XXXXXX) νμμΌλ‘ μ μ₯
-
binlog_format : λ°μ΄λ리 λ‘κ·Έμ μ μ₯ νμμ μ§μ . STATEMENT, ROW, MIXED 3κ°μ§ μ€ νλλ₯Ό μ νμ΄ κ°λ₯
- ν΄λΉ μ€μ μ€
ROW
λ₯Ό μ ν. κ·Έ μ΄μ λ νμ¬InnoDB
λ₯Ό μ¬μ© μ€μ΄λ©°, νΈλμμ 격리 μμ€μ΄READ COMMITTED
μΌ κ²½μ°ROW
κΈ°λ° λ‘κΉ λ§ μ¬μ©μ΄ κ°λ₯.
- ν΄λΉ μ€μ μ€
- maxbinlogsize : λ°μ΄λ리 λ‘κ·Έμ μ΅λ ν¬κΈ°
- sync_binlog : Nκ°μ νΈλμμ λ§λ€ λ°μ΄λ리 λ‘κ·Έλ₯Ό λμ€ν¬μ λκΈ°ν μν¬μ§ κ²°μ . μ€μ ν 1μ μμ μ μ΄μ§λ§, κ°μ₯ λλ¦° μ€μ μ
- expire-logs-days: λ°μ΄λ리 λ‘κ·Έκ° λ§λ£λλ κΈ°κ° μ€μ
- binlogdodb : λ ν리μΌμ΄μ μ μ μ©ν λ°μ΄ν°λ² μ΄μ€ μ΄λ¦ μ€μ
μ€μ μ΄ λλ¬μΌλ©΄ MySQLμ μ¬μμ ν΄μ€λλ€.
sudo systemctl restart mysql
κ·Έλ¦¬κ³ Master
μ μνλ₯Ό νμΈν΄ μ λ°μλμλμ§ νμΈν©λλ€.
SHOW MASTER STATUS:
μ€μ!
λ ν리μΌμ΄μ
μμλ μ΄ File
κ³Ό Position
κ°μΌλ‘ Master - Slave
μλ² λκΈ°νκ° μ§νμ΄ λ©λλ€. λνFile
κ³Ό Position
μ λ°μ΄ν°λ² μ΄μ€μμ μ΄λ€ μμ
μ ν λλ§λ€ λ³κ²½μ΄ λ©λλ€. Master DB
μ ν΄λΉ μ 보λ€μ Slave DB
μμ κΈ°μ
ν΄μ μ¬μ©νκΈ° λλ¬Έμ, νμ νμΈ ν Master - Slave
μ°λμ μ§νν΄μΌ ν©λλ€.
Slave DB Server μμ
μ΄μ΄μ μμμ μ€λͺ
νλ λ΄μ©μ λ°νμΌλ‘ Slave DB
λ₯Ό μ€μ ν΄ μ€λλ€.
CREATE DATABASE test;
CREATE USER 'slave'@'%' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON *.* TO 'slave'@'%';
$ vim /etc/mysql/my.cnf
[mysqld]
max_allowed_packet=1000M
server-id = 2
log-bin = mysql-bin
binlog_format = ROW
max_binlog_size = 500M
sync_binlog = 1
expire-logs-days= 7
slow_query_log = 1
read_only = 1
μ€μ μ λ³κ²½νκΈ° λλ¬Έμ μμ MySQLμ μ¬μμ ν΄μ€λλ€.
sudo systemctl restart mysql
κ·Έλ¦¬κ³ μ΅μ’
μ μΌλ‘ MySQL
μμ μλ 쿼리λ₯Ό μ€νν΄μ£Όλ©΄ λ©λλ€. μ¬κΈ°μ MASTER_LOG_FILE
κ³Ό Β MASTER_LOG_POS
κ°μ μκΉ Master DB STATUS
μμ λμ¨ κ°μΌλ‘ μ€μ μ νλ©΄ λ©λλ€.
RESET SLAVE;
CHANGE MASTER TO MASTER_HOST='xxx.xxx.x.xxx', // private IP
MASTER_USER='master',
MASTER_PORT=8080, // λ΄λΆμ μΈ μ΄μλ‘ 8080 ν¬νΈλ‘ μ°κ²°
MASTER_PASSWORD='password',
MASTER_LOG_FILE='mysql-bin.00000x',
MASTER_LOG_POS=157;
START REPLICA;
μ§κΈκΉμ§ μ μ€μ μ΄ λμλ€λ©΄, λ€μ λͺ λ Ήμ΄λ₯Ό μ λ ₯νμ λ λ¬Έμ κ° μλ€λ κ²μ νμΈν μ μμ΅λλ€.
show slave status\G;
μΆκ°μ μΌλ‘ Master DB
μλ²μμ ν
μ΄λΈ μμ±κ³Ό λ°μ΄ν° μμ±μ ν΄λ³΄λ©΄ λ°λ‘ Slave DB
μλ²μ μ λ°μλλ κ²μ νμΈν μ μμ΅λλ€.
[Master DB]
[Slave DB]
SpringBoot μ€μ
DB μλ²λ₯Ό Master - Slave
λ‘ μ΄μ€ν νμμΌλ―λ‘, SpringBoot
μμ μ¬μ©νλ DataSource
λ Master - Slave
λ₯Ό κ°κ° μ¬μ©ν΄μΌ ν©λλ€. μ΄λ₯Ό ꡬννκΈ° μν λ°©λ²μΌλ‘ @Transactional
μ λ
Έν
μ΄μ
μ μμ±μ μ΄μ©νμ¬ readOnly = false
μΈ νΈλμμ
μ Master DataSource
λ₯Ό, readOnly = true
μΈ νΈλμμ
μ Slave DataSource
λ₯Ό μ¬μ©νλλ‘ μ€μ ν΄ μ£Όκ² μ΅λλ€.
application.yml μ€μ
μ λ Master 1
, Slave 2
λ₯Ό λ°νμΌλ‘ Replication
μ ꡬμ±νκΈ° λλ¬Έμ 3κ°μ DataSource
μ λν μ€μ μ μμ±ν΄μ£Όμ΄μΌ ν©λλ€. μ¬κΈ°μ νλ μ μν΄μΌ ν μ μ μΌλ°μ μΈ DataSource
λ±λ‘ λ°©λ²κ³Όλ κ³Όμ μ΄ μ‘°κΈ λ¬λΌμ§λ€λ μ μ
λλ€.
νλμ DataSource
λ§ μ¬μ©νλ κ²½μ°μλ SpringBoot AutoConfiguration
μ ν΅ν΄μ μλμΌλ‘ λΉμΌλ‘ λ§λ€μ΄μ Έ κ΄λ¦¬λμμ§λ§, 2κ° μ΄μμ DataSource
λ₯Ό μ¬μ©νλ κ²½μ°μλ κ°λ°μκ° μ§μ λΉμ λ§λ€μ΄μ μ¬μ©ν΄μΌ ν©λλ€.
spring:
datasource:
master:
username: master
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://XXX.XXX.XXX.XXX:3306/test
slave1:
username: slave1
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://XXX.XXX.XXX.XXX:3306/test
slave2:
username: slave2
password: password
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://XXX.XXX.XXX.XXX:3306/test
DataSource λΉ λ±λ‘
@Configuration
public class DataSourceConfig {
private static final String MASTER_SERVER = "MASTER";
private static final String SLAVE1_SERVER = "SLAVE1";
private static final String SLAVE1_SERVER = "SLAVE2";
@Bean
@Qualifier(MASTER_SERVER)
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create()
.build();
}
@Bean
@Qualifier(SLAVE1_SERVER)
@ConfigurationProperties(prefix = "spring.datasource.slave1")
public DataSource slave1DataSource() {
return DataSourceBuilder.create()
.build();
}
@Bean
@Qualifier(SLAVE2_SERVER)
@ConfigurationProperties(prefix = "spring.datasource.slave2")
public DataSource slave2DataSource() {
return DataSourceBuilder.create()
.build();
}
}
λ€μκ³Ό κ°μ΄ κ° DB μλ²μ λμλλ DataSource
νμ
μ λΉμ λ±λ‘νλ©΄ λ©λλ€.
μμ μ½λλ₯Ό 보면, @Qualifier
μ λ
Έν
μ΄μ
μ μ¬μ©ν κ²μ νμΈν μ μμ΅λλ€. μΌλ°μ μΌλ‘ κ°μ νμ
(μ¬κΈ°μλ DataSource)μ λΉμ΄ 2κ° μ΄μ λ±λ‘λ κ²½μ° μ€νλ§μ μ΄λ€ λΉμ μ£Όμ
ν΄μΌ νλμ§ λͺ¨λ¦
λλ€. μ΄λ₯Ό ν΄κ²°νκΈ° μν΄μ @Qualifier
μ λ
Έν
μ΄μ
μ μ¬μ©νμ¬ μ΄λ¦μ κΈ°λ°μΌλ‘ νμ μλ₯Ό λͺ
μνμ¬μ, μ£Όμ
λ°μ λΉμ μ§μ ν μ μλλ‘ λ§λ€λ©΄ λ©λλ€.
μΆκ°μ μΌλ‘ @ConfigurationProperties
****μ λ
Έν
μ΄μ
μ μ¬μ©ν κ²μ νμΈν μ μμ΅λλ€. ν΄λΉ μ λ
Έν
μ΄μ
μ μ¬μ©νλ©΄ application.yml
μ λͺ
μν μ¬λ¬ μ€μ μ€, νΉμ prefix
μ ν΄λΉνλ μ€μ κ°μ μλ° λΉμ 맀νν μκ° μμ΅λλ€.
AbstractRoutingDataSource
μ€νλ§μ Multi DataSource
νκ²½μμ μ¬λ¬ DataSource
λ₯Ό λ¬Άκ³ λΆκΈ°ν΄μ£ΌκΈ° μν΄μ AbstractRoutingDataSource
λΌλ μΆμ ν΄λμ€λ₯Ό μ§μν©λλ€.
AbstractRoutingDataSource
λ₯Ό μ΄ν΄λ³΄λ©΄ λ€μκ³Ό κ°μ΄ ꡬμ±λμ΄ μλ κ²μ νμΈν μ μμ΅λλ€.
public abstract class AbstractRoutingDataSource extends AbstractDataSource implements InitializingBean {
@Nullable
private Map<Object, Object> targetDataSources;
@Nullable
private Object defaultTargetDataSource;
private boolean lenientFallback = true;
private DataSourceLookup dataSourceLookup = new JndiDataSourceLookup();
@Nullable
private Map<Object, DataSource> resolvedDataSources;
@Nullable
private DataSource resolvedDefaultDataSource;
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
this.targetDataSources = targetDataSources;
}
...
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
...
}
μμ μ½λλ₯Ό 보면 setTargetDataSources()
λΌλ λ©μλλ₯Ό ν΅ν΄ Map
μ μ λ¬ν©λλ€. μ΄λ Map
μ Value
λ‘λ DataSource
λ₯Ό μ λ¬ν©λλ€. μ λ¬λ DataSource
λ Map
μ Key
λ₯Ό ν΅ν΄μ μ°Ύμ μ μμ΅λλ€.
λν determineTargetDataSource()
λ©μλλ₯Ό 보면 μ€μ λ‘ μ¬μ©λ DataSource
λ₯Ό κ²°μ ν©λλ€. λ΄λΆ μ½λλ₯Ό νμΈν΄λ³΄λ©΄ determineCurrentLookupKey()
λΌλ λ©μλλ₯Ό μ¬μ©ν΄μ κ°μ Έμ¬ DataSource
μ Key
λ₯Ό κ²°μ ν©λλ€.
μ€μ μ ν¬κ° μ΄λ₯Ό νμ©νκΈ° μν΄μ AbstractRoutingDataSource
λ₯Ό μμλ°λ ꡬ체 ν΄λμ€λ₯Ό λ§λ€μ΄μ μμμ μΈκΈν determineCurrentLookupKey()
λ©μλλ₯Ό μ€λ²λΌμ΄λνμ¬ νΈλμμ
μ readOnly
κ°μ λ°λΌ λ€λ₯Έ DataSource
μ Key
λ₯Ό λ°ννλλ‘ κ΅¬ννκ² μ΅λλ€.
public class RoutingDataSource extends AbstractRoutingDataSource {
private RoutingCircular<String> routingCircular;
@Override
public void setTargetDataSources(final Map<Object, Object> targetDataSources) {
super.setTargetDataSources(targetDataSources);
routingCircular = new RoutingCircular<>(
targetDataSources.keySet().stream()
.map(Object::toString)
.filter(key -> key.contains("slave"))
.collect(Collectors.toList())
);
}
@Override
protected Object determineCurrentLookupKey() {
boolean isReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly();
if (isReadOnly) {
return routingCircular.getOne();
}
return "master";
}
}
ν΄λΉ μ½λλ₯Ό 보면, TransactionSynchronizationManager
μ isCurrentTransactionReadOnly()
λΌλ λ©μλλ₯Ό μ¬μ©νμλλ°, μ΄λ₯Ό ν΅ν΄μ νμ¬ νΈλμμ
μ read-only
μ¬λΆλ₯Ό νμΈν μ μμ΅λλ€.
Slave DBμ λ€μ€ν μ²λ¦¬
μμ μ½λμμλ νκ°μ§ μ€λͺ
νμ§ μμ λΆλΆμ΄ μμ΅λλ€. λ°λ‘ setTargetDataSources()
κ³Ό κ΄λ ¨λ μ€λͺ
μΈλ°μ. μ λ Slave DB
λ₯Ό 2κ° μ΄μ μ¬μ©νλ €κ³ νκΈ° λλ¬Έμ readOnly
μμ±λ§μΌλ‘λ μ΄λ₯Ό ν΄κ²°ν μ μμ΅λλ€. μ΄λ₯Ό ν΄κ²°νκΈ° μν΄μ Slave DB
λ₯Ό λ²κ°μμ μ¬μ©νλλ‘ κΈ°λ₯μ μΆκ°ν΄μ£Όλλ‘ νκ² μ΅λλ€.
public class RoutingCircular<T> {
private List<T> dataSources;
private Integer counter = 0;
public RoutingCircular(final List<T> dataSources) {
this.dataSources = dataSources;
}
public T getOne() {
int circularSize = dataSources.size();
if (counter + 1 > circularSize) {
counter = 0;
}
return dataSources.get(counter++ % circularSize);
}
}
λ€μκ³Ό κ°μ΄ RoutingCircular
ν΄λμ€λ₯Ό λ§λ€μ΄ ν΅ν΄μ μ¬λ¬κ°μ Slave DB
μ DataSource
λ₯Ό μμλλ‘ λ‘λλ°Έλ°μ± ν μ μκ² λ©λλ€.
λΌμ°ν ν DataSource λ±λ‘νκΈ°
μ§κΈκΉμ§ μμμ λ§λ RoutingDataSource
μ μ°λ¦¬κ° μ¬μ©ν 3κ°μ§μ DataSource
μ 보λ₯Ό λ±λ‘νκ³ , μ΄λ₯Ό λΉμΌλ‘ λ§λ€μ΄μ μ΅μ’
μ μΌλ‘ μ€νλ§μμ κ΄λ¦¬λλλ‘ μμ
μ μ²λ¦¬ν΄μ£Όκ² μ΅λλ€. @Qualifier
λ₯Ό μ΄μ©ν΄μ μ΄λ€ λΉμ μ£Όμ
λ°μ κ²μΈμ§ λͺ
νν μ§μν΄μ μμ
μ μ§νν©λλ€.
@Bean
public DataSource routingDataSource(@Qualifier(MASTER_SERVER) DataSource masterDataSource,
@Qualifier(SLAVE1_SERVER) DataSource slave1DataSource,
@Qualifier(SLAVE2_SERVER) DataSource slave2DataSoucre) {
RoutingDataSource routingDataSource = new RoutingDataSource();
HashMap<Object, Object> dataSources = new HashMap<>();
dataSources.put("master", masterDataSource);
dataSources.put("slave1", slave1DataSource);
dataSources.put("slave2", slave2DataSource);
routingDataSource.setTargetDataSources(dataSources);
routingDataSource.setDefaultTargetDataSource(masterDataSource);
return routingDataSource;
}
ν΄λΉ κ³Όμ μ ν΅ν΄μ μ°λ¦¬κ° ꡬνν RoutingDataSource
μ μΈμ€ν΄μ€λ₯Ό μμ±ν λ€, λ±λ‘ν΄ μ€ κ°κ°μ DataSource
λ₯Ό String Type
μ Key
λ‘ λ§€ννκ³ , κΈ°λ³Έ DataSource
λ₯Ό Master
λ‘ μ§μ ν λ€ Bean
μΌλ‘ λ±λ‘μ ν΄μ€ μ μμ΅λλ€.
κ·Έλ¦¬κ³ μ€μ Spring Boot
μμ μ¬μ©νκ² λ DataSource
λ₯Ό λ§λλ μμ
μ μννλ©΄ λ©λλ€. μ¬κΈ°μ κ³ λ €ν΄μΌ ν μ μ μ€νλ§μ΄ DataSource
λ₯Ό ν΅ν΄ Connection
μ νλνλ μΌλ ¨μ μ£ΌκΈ°μ λν΄μ νμΈν νμκ° μμ΅λλ€.
Springμ Connection νλ λ°©λ²κ³Ό LazyConnection μ²λ¦¬
μ€νλ§μ νΈλμμ
μ μ§μ
νκΈ°λ§ νλ©΄ λ°λ‘ DataSource
λ₯Ό κ°μ Έμμ Connection
μ κ°μ Έμ΅λλ€. λ§λ‘λ§ νλ©΄ λ무 μΆμμ μ΄λ Springμμ Transactionalμ μ²λ¦¬νλ κ³Όμ μ μμΈνκ² νλ² μμλ΄
μλ€. λ°μ μ 리λ μμλλ‘ Spring Transactionalμ΄ λμμ νκ² λ©λλ€.
1. CglibAopProxy.DynamicAdvisedInterceptor.intercept( )
2. TransactionInterceptor.invoke( )
3. TransactionAspectSupport.invokeWithinTransaction( )
4. TransactionAspectSupport.createTransactionIfNecessary( )
5. AbstractPlatformTransactionManager.getTransaction( )
6. AbstractPlatformTransactionManager.startTransaction( )
7. AbstractPlatformTransactionManager.begin( )
8. AbstractPlatformTransactionManager.prepareSynchronization( )
9. TransactionAspectSupport.invokeWithinTransaction( )
10. InvocationCallback.proceedWithInvocation( )
11. @Transactionalμ΄ μ μ©λ μ€μ νκΉμ΄ μ€ν
μ°λ¦¬κ° μ¬κΈ°μ κΌ νμΈν΄λ΄μΌ ν λΆλΆμ 7λ²κ³Ό 8λ² κ³Όμ μ
λλ€. Spring AOPλ 7λ² κ³Όμ μμ DataSource
λ‘ λΆν° Connection
μ κ°μ Έμ€κ³ 8λ² κ³Όμ μμ νΈλμμ
μ νμ¬ μνλ₯Ό ThreadLocal
μ μ μ₯μ νκ² λ©λλ€. μ¦, TransactionSynchronizationManager
μ νΈλμμ
μ μ 보λ₯Ό λκΈ°ν νλ μμ
μ΄ DataSource
λ‘ λΆν° Connection
μ κ°μ Έμ¨ μ΄νμ μ€νμ΄ λλ€λ κ²μ
λλ€.
μ΄λκΉμ§μ λ΄μ©μ κ·Έλ¦ΌμΌλ‘ μ 리νλ©΄ λ€μκ³Ό κ°μ΅λλ€.
νμ§λ§ μ ν¬λ Replicationμ μν΄μΒ AbstractRoutingDataSource
μ ꡬννμ¬ νΈλμμ
μμ±μ λ°λΌ DataSource
λ₯Ό μ νν΄μ μ νν DataSource
μΒ Connection
κ°μ²΄λ₯Ό 리ν΄ν μ μλλ‘ κ΅¬νμ ν΄μΌ νμ΅λλ€.
μ΄λ¬ν λ¬Έμ λ₯Ό ν΄κ²°νκΈ° μν΄μ Spring
μμλ LazyConnectionDataSourceProxy
μ μ 곡ν©λλ€. Spring Docs
λ₯Ό νμΈν΄λ³΄λ©΄ λ€μκ³Ό κ°μ λ΄μ©μ νμΈν μ μμ΅λλ€.
μ¦, μ€μ 쿼리λ₯Ό νΈμΆνκΈ° μ κΉμ§ JDBC Connection
μ κ°μ Έμ€μ§ μκ³ Connection Proxy
λ₯Ό μ£Όκ³ , λμ λ©μλ μμμ μΏΌλ¦¬κ° μ€μ λ°μν λ μ°λ¦¬κ° μμ±ν AbstractRoutingDataSource
μμ Connection
μ μ»μ΄ 쿼리λ₯Ό μ€ννλλ‘ λ‘μ§μ ꡬνν μ μμ΅λλ€.
μ€λͺ
μ΄ κ΅μ₯ν κΈΈμλλ°μ. μ΄μ λ°μ μ½λμ κ°μ΄ μ€μ μ ν΄μ£Όλ©΄ μ ν¬κ° μνλλ°λ‘ DataSource
λ₯Ό κ°μ Έμ¬ μ μκ² λ©λλ€.
@Bean
@Primary
public DataSource dataSource() {
DataSource determinedDataSource = routingDataSource(masterDataSource(), slave1DataSource(), slave2DataSource());
return new LazyConnectionDataSourceProxy(determinedDataSource);
}
μ¬κΈ°μ @Primary
μ λ
Έν
μ΄μ
μ μμ±ν΄μ€ κ²μ νμΈν μ μλλ°, κΈ°λ³Έμ μΌλ‘ Spring Boot
μ AutoConfiguration
μ ν κ°μ DataSource
λ₯Ό κ°μ νκ³ μ€μ μ΄ λμ΄ μμ΅λλ€. νμ¬λ μ¬λ¬ κ°μ DataSource
λ₯Ό μ¬μ©νλ κ²½μ°μ΄κΈ° λλ¬Έμ, @Primary
μ λ
Έν
μ΄μ
μ λΆμ¬μ£Όμ΄μ DataSource
μ λν μ§μ μ ν΄μ£Όμ΄μΌ ν©λλ€.
μ§κΈκΉμ§ κ³Όμ μ΄ μ±κ³΅μ μΌλ‘ μνμ΄ λμλ€λ©΄ Replication
μ μ μμ μΌλ‘ λμμν¬ μ μμ΅λλ€.
Replication μ€κ³λ₯Ό νλ©΄μ κ³ λ €ν λ¬Έμ λ€κ³Ό μμΌλ‘μ κ°μ λ°©ν₯
λ°μ΄ν° μ ν©μ± λ¬Έμ
μ΄λ² μμ
μ νλ©΄μ κ°μ₯ κ³ λ―Όμ νλ λΆλΆμ
λλ€. λ§μ½ μ€μκ°μΌλ‘ κ³μ μΏ ν°μ΄ λ°νλκ³ , νμͺ½μμλ κ³μ μ‘°νλλ νκ²½μ΄λΌλ©΄ λκΈ°ν μ΄μ μμ μ μ‘°νλ₯Ό νκ² λ κ²½μ° NPE
κ° λ°μνκ² λ κ°λ₯μ±μ λ¨μμμμ΅λλ€. μ΄λ₯Ό ν΄κ²°νκΈ° μν΄ λ€μν λ°©λ²μ κ³ λ―Όν΄λ³΄μλλ° 2κ°μ§ μ λμ λ°©λ²μ μ°Ύμ μ μμμ΅λλ€.
첫λ²μ§Έ λ°©λ²μ λ°λκΈ°(Semi Async) λ°©μμ
λλ€. MySQL Docs
λ₯Ό μ΄ν΄λ³΄λ©΄ λΉλκΈ°(Async) λ°©μμ΄ μλ λ°λκΈ°(Semi Async) λ°©μμ μ¬μ©ν μ μλ€κ³ λ λμμμ΅λλ€. ν΄λΉ λ°©λ²μ ν΅ν΄μ DBμ μ±λ₯μ μ‘°κΈ ν¬κΈ°νκ³ , Master - Slave
μ λκΈ°νλ₯Ό μ‘°κΈ λ 보μ₯ν΄μ€λλ€. νμ§λ§ μ΄ λ°©μμ Master
κ° νΉμ μΈμ
μ νΈλμμ
μ μ²λ¦¬νκΈ° μν΄μ Slave
λ€ μ€ μ μ΄λ 1κ°κ° ACK
νκ±°λ timed out
μ λλ¬ν λκΉμ§ κΈ°λ€λ¦¬κΈ° λλ¬Έμ μ±λ₯μ μ
μν₯μ λ―Έμ³ Replication
μ μ΄μ μ μ΄λ¦¬μ§ λͺ»νλ€λ μκ°μ΄ λ€μμ΅λλ€.
λλ²μ§Έ λ°©λ²μ νμ
μ€ν¬ν λ°©μμ
λλ€. μ΅κ·Ό Nμ΄ ~ NλΆ κΉμ§μ κ°±μ λ΄μμ Master
μμ μ½κ³ , μ΄ν λ΄μμ Slave
μμ μ½λ λ°©λ²μ΄λΌκ³ ν μ μμ΅λλ€. μ΄ λ°©λ²μ Master
μ νΈλν½μ΄ λ§μ΄ λͺ°λ € Master
μ μ₯μ μνμ΄ λλ€λ μκ°μ΄ λ€μμ΅λλ€.
κ²°λ‘ μ μΌλ‘ μ λ μ§κΈμ²λΌ MySQL Replication
μμ κΈ°λ³Έμ μΌλ‘ μ 곡νλ λΉλκΈ° λ°©μμΌλ‘ μ²λ¦¬λ₯Ό νκ³ , λ°μ΄ν° λΆμΌμΉ νμμ λ€λ₯Έ λ°©μμΌλ‘ ν΄κ²°νκΈ°λ‘ κ²°λ‘ μ λ΄λ Έμ΅λλ€.
νμ¬ μκ°νλ ν΄κ²°λ°©λ²μ μ ν©μ±μ΄ μ€μν λ‘μ§μμλ μ‘°ν μμ² μμ Master DB
λ₯Ό νκ²λ μ€μ νλ κ² μ
λλ€. λ¬Όλ‘ Master DB
μ λΆνκ° μκΈ°κ² μ§λ§, Replication
μ μ§ννμ§ μμμ λ보λ€λ ν¨μ¬ λΆνλ₯Ό λΆμ°μν¬ μ μκΈ° λλ¬Έμ νμ¬ κ°μ©ν μ μλ μ΅λνμ Scale - up
μ ν ν, Master DB
λ₯Ό ν΅ν΄ μ ν©μ± λ¬Έμ λ₯Ό μ²λ¦¬νκΈ°λ‘ κ²°λ‘ μ λ΄λ Έμ΅λλ€.
Master μ₯μ μ Slaveμ μΉκ²© μ¬λΆ
λ§μ½ Master
κ° μ₯μ κ° λλ€λ©΄ Slave
νλλ₯Ό μΉκ²©μμΌμ μ΄λ₯Ό Master
μ²λΌ μ¬μ©ν΄μΌ ν κ²μ
λλ€. μ§κΈ ꡬ쑰μμ λ¬Έμ λΌκ³ μκ°νλ μ μ Master
μ₯μ μ κ΄λ¦¬μκ° μλμΌλ‘ μ΄λ₯Ό μ²λ¦¬ν΄μΌ νλ€λ μ μ
λλ€.
μ΄λ¬ν λ¬Έμ λ₯Ό Group Replicaiton
μΌλ‘ ν΄κ²°ν΄μ€ μ μλ€κ³ ν©λλ€.
Group Replication
μ μ¬μ©νλ©΄ Master DB
μ₯μ μ μλμΌλ‘ Slave DB
κ° Master DB
λ‘ μΉκ²©λλ μ₯μ μ΄ μμ΅λλ€. μ΄λ¬ν μ΄μ λλ¬Έμ νμ¬ κ΅¬μ‘°μΈ Master - Slave Replication
ꡬ쑰μμ Group Replication
μΌλ‘ λ³κ²½νλ κ²μ΄ μ‘°κΈ λ μ₯μ λμ²μ μ’μ κ²μ΄λΌ μκ°μ΄ λλλ° μ νν λ΄μ©μ μ‘°κΈ λ νμ΅μ ν΄λ΄μΌ ν κ²μΌλ‘ 보μ
λλ€.
Replication μμ κ³Όμ μμ κ²ͺμ μλ μλ λ¬Έμ λ€
Public key retrieval is not allowed
MySQL DBμ μ μνλ €λ©΄ κΈ°λ³Έμ μΌλ‘ url, username, password 3κ°μ§ μ΅μ
μ΄ νμν©λλ€. νμ§λ§ MySQL 8.0 λ²μ λΆν°λ 보μμ μΈ μ΄μλ‘ useSSL
μ΅μ
μ λν μΆκ°μ μΈ μ€μ μ΄ νμν©λλ€.
λ€μ λ κ°μ§ μ΅μ μ΄ νμνλ° μ΄λ λ€μκ³Ό κ°μ΅λλ€.
- useSSL : DBμ SSLλ‘ μ°κ²°
- allowPublicKeyRetrieval: ν΄λΌμ΄μΈνΈκ° μλ²μμ 곡κ°ν€λ₯Ό μλμΌλ‘ μμ²ν μ μλλ‘ μ€μ
μ΅μ’
μ μΌλ‘ application.yml
μ 2κ°μ§ μμ±μ μΆκ°ν΄μ£Όλ©΄ μ μμ μΌλ‘ μ°κ²°μ΄ λ©λλ€.
jdbc:mysql://xxx.xxx.xx.x:3306/test_db?useSSL=false&allowPublicKeyRetrieval=true
cachingsha2password authentication plugin
MySQL 8.0μ SHA-246 hasing
μ ꡬννλ λ κ°μ§ μΈμ¦ νλ¬κ·ΈμΈμ μ§μν©λλ€.
- sha256_password : κΈ°λ³Έμ μΈ SHA-256 μΈμ¦μ ꡬνν νλ¬κ·ΈμΈ
- cachingsha2password : sha256_passwordμ λμΌνλ° μ±λ₯ ν₯μμ μν΄ μλ² μΊμ±μ μ΄μ©
λ¬Έμ λ caching_sha2_password
λ₯Ό μ¬μ©νλ €λ©΄ λ€μ 쑰건 μ€ νλκ° λ§μ‘±ν΄μΌ νλ€λ μ μ
λλ€.
- SSL 보μμ°κ²°μ μ¬μ©
- RSA 보μμ μ μ©ν λΉμνΈ μ°κ²°μ μ¬μ©
νμ§λ§ μ λ ν μ€νΈ μ©μΌλ‘ λ‘컬μμ μμ μ νκ³ μμκΈ° λλ¬Έμ 보μ μ°κ²°μ μ¬μ©νμ§ μμ μλμ κ°μ λ¬Έμ κ° λ°μνμμ΅λλ€.
Last_IO_Error: error connecting to master 'replication@test:3306' - retry-time: 60 retries: 1 message: Authentication plugin 'caching_sha2_password' reported error: Authentication requires secure connection.
μ΄λ₯Ό ν΄κ²°νλ λ°©λ²μ μ΄μ μ mysql_native_password
λ₯Ό μ¬μ©νλ©΄ λ©λλ€. μ¬μ©μμ μνΈλ₯Ό DDL
μ ν΅ν΄μ μ΄μ λ°©μμΌλ‘ λ³κ²½ν μ μμ΅λλ€.
mysql> ALTER USER 'replication'@'%' IDENTIFIED WITH mysql_native_password BY 'password';
κ·Έλ¦¬κ³ MySQLμ μ¬μμνλ©΄ μ μμ μΌλ‘ MySQLμ μ μν μ μλ κ²μ νμΈν μ μμ΅λλ€.
λ¨, ν΄λΉ λ°©μμ MySQLμ΄ μ§μνλ μμ λ°©μμ΄κΈ° λλ¬Έμ μ€μ λ‘ μ΄μνκ²½μμλ 보μ μμ μ μ²λ¦¬ν ν, μ¬μ©νλκ² μ’μ κ²μΌλ‘ 보μ λλ€.
λ§λ¬΄λ¦¬
μ§κΈκΉμ§ MySQLμ Replication κ³Ό Spring Bootμμμ νμ©λ°©λ²μ λν΄μ νμ΅ν λ΄μ©κ³Ό κ³ λ―Όν λΆλΆμ μμ±ν΄λ³΄μμ΅λλ€. (μΆκ°μ μΌλ‘ μ±λ₯ ν μ€νΈ κ²°κ³Όλ₯Ό 보좩ν΄μ μμ±ν μμ μ λλ€) μμμ μκ°λ λ°©λ²λ€μ΄ μ λ΅μ μλκΈ° λλ¬Έμ, μμΌλ‘λ μ‘°κΈ λ DB Scale-outμ λν΄μ κ³ λ―Όμ ν΄λ΄μΌκ² μ΅λλ€.
μ°Έκ³ ν κ³³
- https://docs.spring.io/spring-framework/docs/4.2.x/spring-framework-reference/html/transaction.html
- https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jdbc/datasource/LazyConnectionDataSourceProxy.html
- https://ssup2.github.io/theoryanalysis/MySQLReplication/
- https://backtony.github.io/spring/mysql/aws/2021-09-28-spring-mysql-1/
- https://trandent.com/article/etc/detail/320833