Redis 의 FailOver 상황을 대비한 replication 구성에 대해서 알아보도록 하겠습니다. 시작에 앞서서 replication 을 '리플리케이션' 이라고 많이들 읽으시는데, '레플리케이션' [rèpləkéiʃən] 이 맞는 발음입니다. ^^;



Redis 의 Replication


다들 아시겠지만, replication 을 하기 위해서는 master 와 slave 가 있어야 합니다. 쉽게 말해서 master 는 (read/write) 용이고, slave 는 master 의 데이터를 미러링하고 있는 read 전용입니다. 사실 redis 에서는 slave 에도 write 가 가능하도록 설정해 줄 수 있긴 합니다만, 속도가 상당히 느려지고, 일반적이진 않으므로 무시하도록 하겠습니다. :)




Redis 서버 생성




실제로는 master 와 slave 를 독립적인 서버에서 돌리는게 일반적이지만, 저의 경우 단순히 테스트 용이므로 한 서버에서 포트를 여러 개 두고 설명드리도록 하겠습니다. 구성할 모양은 아래와 같습니다.




총 4대의 redis 를 돌리고, 7000 포트를 master 로, 그리고 나머지를 slave 로 운영할 예정입니다.

일일이 독립된 *.conf 파일을 만들고 로드해서 서버를 돌려도 되는데, 여간 귀찮은 일이 아닙니다. 앞서 사용했던 install_server.sh 를 사용하도록 하겠습니다.



[und3r@sungwook utils]$ sudo ./install_server.sh

Welcome to the redis service installer

This script will help you easily set up a running redis server


Please select the redis port for this instance: [6379] 7000

Please select the redis config file name [/etc/redis/7000.conf]

Selected default - /etc/redis/7000.conf

Please select the redis log file name [/var/log/redis_7000.log]

Selected default - /var/log/redis_7000.log

Please select the data directory for this instance [/var/lib/redis/7000]

Selected default - /var/lib/redis/7000

Please select the redis executable path [] /usr/local/bin/redis-server

Selected config:

Port           : 7000

Config file    : /etc/redis/7000.conf

Log file       : /var/log/redis_7000.log

Data dir       : /var/lib/redis/7000

Executable     : /usr/local/bin/redis-server

Cli Executable : /usr/local/bin/redis-cli

Is this ok? Then press ENTER to go on or Ctrl-C to abort.

Copied /tmp/7000.conf => /etc/init.d/redis_7000

Installing service...

Successfully added to chkconfig!

Successfully added to runlevels 345!

Starting Redis server...

Installation successful!

[und3r@sungwook utils]$

[und3r@sungwook utils]$ sudo ./install_server.sh

Welcome to the redis service installer

This script will help you easily set up a running redis server


Please select the redis port for this instance: [6379] 7001

Please select the redis config file name [/etc/redis/7001.conf]

Selected default - /etc/redis/7001.conf

Please select the redis log file name [/var/log/redis_7001.log]

Selected default - /var/log/redis_7001.log

Please select the data directory for this instance [/var/lib/redis/7001]

Selected default - /var/lib/redis/7001

Please select the redis executable path [] /usr/local/bin/redis-server

Selected config:

Port           : 7001

Config file    : /etc/redis/7001.conf

Log file       : /var/log/redis_7001.log

Data dir       : /var/lib/redis/7001

Executable     : /usr/local/bin/redis-server

Cli Executable : /usr/local/bin/redis-cli

Is this ok? Then press ENTER to go on or Ctrl-C to abort.

Copied /tmp/7001.conf => /etc/init.d/redis_7001

Installing service...

Successfully added to chkconfig!

Successfully added to runlevels 345!

Starting Redis server...

Installation successful!

[und3r@sungwook utils]$

[und3r@sungwook utils]$ sudo ./install_server.sh

Welcome to the redis service installer

This script will help you easily set up a running redis server


Please select the redis port for this instance: [6379] 7002

Please select the redis config file name [/etc/redis/7002.conf]

Selected default - /etc/redis/7002.conf

Please select the redis log file name [/var/log/redis_7002.log]

Selected default - /var/log/redis_7002.log

Please select the data directory for this instance [/var/lib/redis/7002]

Selected default - /var/lib/redis/7002

Please select the redis executable path [] /usr/local/bin/redis-server

Selected config:

Port           : 7002

Config file    : /etc/redis/7002.conf

Log file       : /var/log/redis_7002.log

Data dir       : /var/lib/redis/7002

Executable     : /usr/local/bin/redis-server

Cli Executable : /usr/local/bin/redis-cli

Is this ok? Then press ENTER to go on or Ctrl-C to abort.

Copied /tmp/7002.conf => /etc/init.d/redis_7002

Installing service...

Successfully added to chkconfig!

Successfully added to runlevels 345!

Starting Redis server...

Installation successful!

[und3r@sungwook utils]$

[und3r@sungwook utils]$ sudo ./install_server.sh

Welcome to the redis service installer

This script will help you easily set up a running redis server


Please select the redis port for this instance: [6379] 7003

Please select the redis config file name [/etc/redis/7003.conf]

Selected default - /etc/redis/7003.conf

Please select the redis log file name [/var/log/redis_7003.log]

Selected default - /var/log/redis_7003.log

Please select the data directory for this instance [/var/lib/redis/7003]

Selected default - /var/lib/redis/7003

Please select the redis executable path [] /usr/local/bin/redis-server

Selected config:

Port           : 7003

Config file    : /etc/redis/7003.conf

Log file       : /var/log/redis_7003.log

Data dir       : /var/lib/redis/7003

Executable     : /usr/local/bin/redis-server

Cli Executable : /usr/local/bin/redis-cli

Is this ok? Then press ENTER to go on or Ctrl-C to abort.

Copied /tmp/7003.conf => /etc/init.d/redis_7003

Installing service...

Successfully added to chkconfig!

Successfully added to runlevels 345!

Starting Redis server...

Installation successful!

[und3r@sungwook utils]$

[und3r@sungwook utils]$ ps aux | grep redis

root     15388  0.1  0.1 137436  2056 ?        Ssl  19:13   0:00 /usr/local/bin/redis-server *:7000

root     15418  0.1  0.1 137436  2056 ?        Ssl  19:14   0:00 /usr/local/bin/redis-server *:7001

root     15448  0.0  0.1 137436  2056 ?        Ssl  19:14   0:00 /usr/local/bin/redis-server *:7002

root     15478  0.0  0.1 137436  2056 ?        Ssl  19:14   0:00 /usr/local/bin/redis-server *:7003

und3r    15482  0.0  0.0 103244   864 pts/1    S+   19:14   0:00 grep redis

[und3r@sungwook utils]$



실행 후, 프로세스를 확인해보면 위처럼 4개의 redis 가 떠 있는것을 확인할 수 있습니다. 이제 아래처럼 전부 redis 프로세스를 종료시킵니다. 왜냐면 conf 파일을 수정해 주어야 하니까요. 우린 conf 파일을 만들기 귀찮아서 install_server.sh 를 사용했다는 사실을 잊으면 안됩니다. ^^;



[und3r@sungwook utils]$ sudo /etc/init.d/redis_7000 stop

Stopping ...

Redis stopped

[und3r@sungwook utils]$ sudo /etc/init.d/redis_7001 stop

Stopping ...

Redis stopped

[und3r@sungwook utils]$ sudo /etc/init.d/redis_7002 stop

Stopping ...

Redis stopped

[und3r@sungwook utils]$ sudo /etc/init.d/redis_7003 stop

Stopping ...

Redis stopped

[und3r@sungwook utils]$ ps aux | grep redis

und3r    15500  0.0  0.0 103244   868 pts/1    S+   19:18   0:00 grep redis

[und3r@sungwook utils]$



말끔히 종료되었네요. 이제 각 conf 파일을 수정해 봅시다. conf 의 위치는 /etc/redis 디렉토리에 들어있습니다.


[und3r@sungwook utils]$ ls -alF /etc/redis/

total 272

drwxr-xr-x.  2 root root  4096 May 27 19:14 ./

drwxr-xr-x. 66 root root  4096 May 27 16:23 ../

-rw-r--r--.  1 root root 41484 May 27 19:13 7000.conf

-rw-r--r--.  1 root root 41484 May 27 19:14 7001.conf

-rw-r--r--.  1 root root 41484 May 27 19:14 7002.conf

-rw-r--r--.  1 root root 41484 May 27 19:14 7003.conf

[und3r@sungwook utils]$






conf 파일 설정




먼저 Master 를 설정하도록 하겠습니다. master 의 conf 파일은 7000.conf 입니다.

아래처럼 requirepass 부분을 찾아서 주석을 풀고, slave 에서 연결할때 사용할 password 를 지정해 줍니다.



이제 나머지 3개의 slave 의 conf 파일도 수정해 봅시다.

slave 는 수정할 곳이 총 4곳 입니다.




# slaveof <masterip> <masterport>

이 부분을 찾아서 아래처럼 수정합니다.

slaveof 127.0.0.1 7000


아시다시피 저는 한 서버에서 돌리고 있기 때문에 ip 를 loopback 주소로 넣어 주었습니다.



# masterauth <master-password>

이 부분의 주석을 제거

masterauth mypassword




# repl-ping-slave-period 10

이 부분의 주석을 제거

repl-ping-slave-period 10




# repl-timeout 60

이 부분의 주석을 제거

repl-timeout 60






Replication 확인




이제 모든 설정이 끝났습니다. 제대로 replication 되고 있는지 확인해 봅시다.


[und3r@sungwook ~]$ sudo /etc/init.d/redis_7000 start

Starting Redis server...

[und3r@sungwook ~]$ sudo /etc/init.d/redis_7001 start

Starting Redis server...

[und3r@sungwook ~]$ sudo /etc/init.d/redis_7002 start

Starting Redis server...

[und3r@sungwook ~]$ sudo /etc/init.d/redis_7003 start

Starting Redis server...

[und3r@sungwook ~]$ ps aux | grep redis

root     15607  0.1  0.1 137436  2176 ?        Ssl  17:27   0:00 /usr/local/bin/redis-server *:7000

root     15613  0.1  0.1 137436  2144 ?        Ssl  17:27   0:00 /usr/local/bin/redis-server *:7001

root     15620  0.1  0.1 137436  2072 ?        Ssl  17:27   0:00 /usr/local/bin/redis-server *:7002

root     15626  0.1  0.1 137436  2072 ?        Ssl  17:27   0:00 /usr/local/bin/redis-server *:7003



모든 서버가 실행되었습니다. 각 서버의 로그를 확인해 보겠습니다. 먼저 Master 입니다.


[MASTER:7000]

[und3r@sungwook ~]$ tail -n 16 /var/log/redis_7000.log


15607:M 28 May 17:27:01.171 # Server started, Redis version 3.0.1

15607:M 28 May 17:27:01.172 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.

15607:M 28 May 17:27:01.172 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.

15607:M 28 May 17:27:01.172 * DB loaded from disk: 0.000 seconds

15607:M 28 May 17:27:01.172 * The server is now ready to accept connections on port 7000

15607:M 28 May 17:27:04.410 * Slave 127.0.0.1:7001 asks for synchronization

15607:M 28 May 17:27:04.410 * Full resync requested by slave 127.0.0.1:7001

15607:M 28 May 17:27:04.410 * Starting BGSAVE for SYNC with target: disk

15607:M 28 May 17:27:04.411 * Background saving started by pid 15616

15616:C 28 May 17:27:04.414 * DB saved on disk

15616:C 28 May 17:27:04.414 * RDB: 0 MB of memory used by copy-on-write

15607:M 28 May 17:27:04.484 * Background saving terminated with success

15607:M 28 May 17:27:04.485 * Synchronization with slave 127.0.0.1:7001 succeeded



[SLAVE:7001]

[und3r@sungwook ~]$ tail -n 16 /var/log/redis_7001.log


15613:S 28 May 17:27:04.408 # Server started, Redis version 3.0.1

15613:S 28 May 17:27:04.408 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.

15613:S 28 May 17:27:04.408 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.

15613:S 28 May 17:27:04.408 * DB loaded from disk: 0.000 seconds

15613:S 28 May 17:27:04.408 * The server is now ready to accept connections on port 7001

15613:S 28 May 17:27:04.410 * Connecting to MASTER 127.0.0.1:7000

15613:S 28 May 17:27:04.410 * MASTER <-> SLAVE sync started

15613:S 28 May 17:27:04.410 * Non blocking connect for SYNC fired the event.

15613:S 28 May 17:27:04.410 * Master replied to PING, replication can continue...

15613:S 28 May 17:27:04.410 * Partial resynchronization not possible (no cached master)

15613:S 28 May 17:27:04.411 * Full resync from master: 3c4f5b157b037b113592d7d6ac87a30021adc67c:1

15613:S 28 May 17:27:04.485 * MASTER <-> SLAVE sync: receiving 18 bytes from master

15613:S 28 May 17:27:04.486 * MASTER <-> SLAVE sync: Flushing old data

15613:S 28 May 17:27:04.486 * MASTER <-> SLAVE sync: Loading DB in memory

15613:S 28 May 17:27:04.486 * MASTER <-> SLAVE sync: Finished with success


나머지 slave 들도 확인해 보면, 위와 결과가 같습니다. 몇몇 warning 이 표시되고 있는데 나와 있는데로 수정해 주시는게 좋겠습니다. 사용할 수 있는 메모리가 더이상 없을 경우 등에 대한 것들입니다. 현재 replication 과는 무관하므로 지금 단계에서는 그냥 넘어가도록 하겠습니다.


이제 데이터를 확인해 보도록 하겠습니다. 제대로 replication 이 되고 있다면, master 에 값을 넣었을 때 slave 들에도 해당 값이 들어가면 되겠습니다. master 에 'replication_test' 라는 key 로 값을 넣어 보겠습니다.



[und3r@sungwook ~]$ redis-cli -p 7000 -a mypassword

127.0.0.1:7000> keys *

(empty list or set)

127.0.0.1:7000> set replication_test good

OK

127.0.0.1:7000> get replication_test

"good"

127.0.0.1:7000>


보시는것처럼 처음에는 아무런 키도 없었고, replication_test 라는 키와 value 로서 'good' 을 넣었습니다. 이제 slave 쪽에서 확인해 보도록 하겠습니다.



[und3r@sungwook ~]$ redis-cli -p 7001

127.0.0.1:7001> keys *

1) "replication_test"

127.0.0.1:7001> get replication_test

"good"

127.0.0.1:7001>


잘 들어가 있네요. master 에 쓴 내용이 slave 에도 잘 들어가 있습니다. 나머지 slave 들에도 동일하게 값이 들어가 있는것을 확인 하실 수 있습니다.


slave 에는 기본적으로 read 권한밖에는 없다고 했었습니다. 확인해 보도록 하겠습니다.

127.0.0.1:7001> set replication_test bad

(error) READONLY You can't write against a read only slave.

127.0.0.1:7001>






Master 가 죽으면(kill) 어떻게 될까?




slave 3개가 master 를 mirroing 하고 있는 상태에서 master 가 종료되면 어떻게 될까요? master 를 shutdown 해 보도록 하겠습니다.


[und3r@sungwook ~]$ sudo /etc/init.d/redis_7000 stop

Stopping ...

(error) NOAUTH Authentication required.

Waiting for Redis to shutdown ...

Waiting for Redis to shutdown ...

Waiting for Redis to shutdown ...

Waiting for Redis to shutdown ...

Waiting for Redis to shutdown ...


그냥 stop 시키려고 하면, 위 화면처럼 종료되지 않습니다. 이유는 나와있는것처럼 password 가 필요하기 때문이죠. 처음에 replication 구성을 위해서 master 에 password 를 지정했던 것을 기억하시죠?

해당 redis_7000 스크립트를 열어보면 아래처럼 나와있는 부분이 있습니다.



$CLIEXEC -p $REDISPORT shutdown


이 부분에 password 를 아래처럼 추가해 주면 됩니다. '-a [password]'

$CLIEXEC -p $REDISPORT -a mypassword shutdown


이후 shutdown 하면 아래처럼 잘 종료됩니다.

[und3r@sungwook ~]$ sudo /etc/init.d/redis_7000 stop

Stopping ...

Waiting for Redis to shutdown ...

Redis stopped


사실 해당 스크립트를 수정하기 귀찮다면 아래처럼 그냥 호출해도 됩니다.

[und3r@sungwook ~]$ sudo /usr/local/bin/redis-cli -p 7000 -a mypassword shutdown


자, master 가 shutdown 되었습니다. slave 에는 어떤 변화가 있을까요? slave 의 로그를 확인해 보겠습니다.

15613:S 28 May 17:53:21.744 * Connecting to MASTER 127.0.0.1:7000

15613:S 28 May 17:53:21.745 * MASTER <-> SLAVE sync started

15613:S 28 May 17:53:21.745 # Error condition on socket for SYNC: Connection refused

15613:S 28 May 17:53:22.749 * Connecting to MASTER 127.0.0.1:7000

15613:S 28 May 17:53:22.750 * MASTER <-> SLAVE sync started

15613:S 28 May 17:53:22.750 # Error condition on socket for SYNC: Connection refused

15613:S 28 May 17:53:23.754 * Connecting to MASTER 127.0.0.1:7000

15613:S 28 May 17:53:23.756 * MASTER <-> SLAVE sync started

15613:S 28 May 17:53:23.756 # Error condition on socket for SYNC: Connection refused

15613:S 28 May 17:53:24.760 * Connecting to MASTER 127.0.0.1:7000

15613:S 28 May 17:53:24.760 * MASTER <-> SLAVE sync started

15613:S 28 May 17:53:24.761 # Error condition on socket for SYNC: Connection refused



slave 는 계속해서 일정 주기마다 master 로의 연결을 시도합니다. 즉 미아가 되었습니다. orz 서비스 측면에서 어떤 상태일까요? 네, write 는 못하고 read 만 가능한 상태입니다. 이제 master 를 다시 살린 뒤에, slave 의 로그를 살펴보겠습니다.



[und3r@sungwook ~]$ sudo /etc/init.d/redis_7000 start

Starting Redis server...




[slave 로그]

[und3r@sungwook ~]$ tail -n 10 /var/log/redis_7001.log

15613:S 28 May 17:58:54.007 * MASTER <-> SLAVE sync started

15613:S 28 May 17:58:54.007 * Non blocking connect for SYNC fired the event.

15613:S 28 May 17:58:54.008 * Master replied to PING, replication can continue...

15613:S 28 May 17:58:54.009 * Trying a partial resynchronization (request 3c4f5b157b037b113592d7d6ac87a30021adc67c:1625).

15613:S 28 May 17:58:54.012 * Full resync from master: 8512bd7412fa3dc463c6a28f90e59ba6bf665334:1

15613:S 28 May 17:58:54.012 * Discarding previously cached master state.

15613:S 28 May 17:58:54.081 * MASTER <-> SLAVE sync: receiving 43 bytes from master

15613:S 28 May 17:58:54.082 * MASTER <-> SLAVE sync: Flushing old data

15613:S 28 May 17:58:54.082 * MASTER <-> SLAVE sync: Loading DB in memory

15613:S 28 May 17:58:54.082 * MASTER <-> SLAVE sync: Finished with success



Master 를 다시 찾았습니다! 더이상 고아가 아닙니다!! :)



이제 의문이 생깁니다. master 가 죽으면 slave 가 있기 때문에 read 는 가능합니다. 그런데 write 를 못하는 상태가 되었습니다. replication 은 트레픽 분산 그 이상도 이하도 아닙니다. 유저의 수가 한 서버가 감당할 수 없을 정도일때, 분산하기 위해서 slave 를 두는 것이죠. 서비스 측면에서는 문제가 발생한 상황입니다. 위와 같이 1대의 master 에 3대의 slave 가 있을 경우, master 가 죽게 되면 3 대중 1대의 slave 가 master 가 하던 일을 위임으면 어떨까요? 그렇다면 write 도 가능하게 됩니다. 애초에 master 1대에 slave 2대로 서버를 구성했던것과 동일하게 되겠죠.


master(7000)이 죽었으니, 7001 번 slave 를 master 로 바꾸고 나머지 7002, 7003 을 7001 의 slave 로 바꾸어 봅시다.


[slave:7001 -> master:7001]

[und3r@sungwook ~]$ redis-cli -p 7002

127.0.0.1:7002> slaveof no one

OK

127.0.0.1:7002> exit

[und3r@sungwook ~]$



이제 나머지 7002 와 7003 의 master 를 7001 로 변경해 줍니다.


[slave:7002, 7003]

[und3r@sungwook ~]$ redis-cli -p 7002

127.0.0.1:7002> config set masterauth ''

OK

127.0.0.1:7002> slaveof 127.0.0.1 7001

127.0.0.1:7002> exit



masterauth 를 '' 로 변경해준 이유는, 현재 7002, 7003에서 masterauth 가 기존 master(7000)의 값인 mypasswd 로 되어 있기 때문입니다. 그리고 새로 master 로 승격된 7001 은 password(requirepass) 값을 따로 지정하지 않았죠. 그래서 masterauth 를 비워주는 것입니다. 위와 같이 설정하고 나면, 모든게 끝이 납니다.


7001 에 새로운 값을 쓰고나면, 7002 와 7003 에서 slave 역할을 제대로 하는것을 확인 할 수 있습니다.





문제점




그런데 이렇게 장애가 발생했을때 failover 를 수동으로 하면 되는게 귀찮습니다. 매일 모니터 앞에 앉아서 서버가 죽었나 살았나 확인할 수도 없는 노릇이지요. 이런 상황에서 자동으로 failover 해 주는 도구로서 sentinel 이라는 것이 있습니다.


다음 포스팅에서는 이 sentinel 에 대해서 알아보도록 하겠습니다. :)



'Database > Redis' 카테고리의 다른 글

Redis 4부 - HAProxy  (6) 2015.05.29
Redis 3부 - Sentinel  (9) 2015.05.29
Redis 2부 - Replication  (4) 2015.05.27
Redis 1부 - 설치  (5) 2015.05.27