HomeBlogGuestbookLab 

JDM's Blog

온갖 테스트 결과가 기록되는 이곳은 JDM's Blog입니다. :3

레디스 레플리케이션 예제(Redis Replication)

저번 포스팅인 Redis 설치 및 사용법에 이어서 이번엔 Redis Replication에 대해 알아봅니다.

Replication

기본적으로 Replication은 Master/Slave 구조로 되어 있습니다. Master는 문자 그대로 모든것을 처리합니다. Read 또는 Write 동작 같은것이 해당 되겠네요. 반대로 Slave 는 Master가 데이터를 Write 하면 해당 내용을 자신에게도 똑같이 복제합니다. 이것을 Replication이라고 합니다.

Redis 공식 홈페이지에서는 Replication에 대해 redis - replication 항목으로 정리를 해뒀습니다.

Redis Replication Example

그러면 이번엔 Redis에서 Replication(이하 "복제")을 어떻게 하는지 예제를 통해서 알아봅니다.

Architecture

          ╋────────╋
          ┃Master          ┃
          ┃PORT : 10000    ┃
          ╋────────╋
                   ┃
       ╋─────╋─────╋
       ┃                      ┃
╋────────╋  ╋────────╋
┃Slave           ┃  ┃Slave           ┃
┃PORT : 10001    ┃  ┃PORT : 10002    ┃
╋────────╋  ╋────────╋

제가 이번에 만들고 싶은 Redis 구조입니다. 가능하다면 Master 및 Slave 인스턴스들을 개별 장비에 올려놓고 싶었지만 서버가 한대인 관계로(...) 로컬에서 Redis 인스턴스를 여러개 띄워서 테스트 할 예정입니다.

그림에서 보다시피 10000번 포트인 Master와 각각 10001,10002번 포트인 Slave Redis 인스턴스들이 있습니다. 위 구조를 실제로 만들어보고 데이터 복제가 일어나는지 확인해 봅시다.

Install Redis Server

Redis에서는 개발자들에게 편의를 제공하기 위해서 각종 스크립트를 미리 준비해놨습니다. 대표적인 것 중 하나는 install_server.sh라는 스크립트입니다. 이 스크립트는 Redis가 설치되어 있는 디렉토리의 하위 디렉토리인 "utils" 에 있습니다.

그럼 install_server.sh를 이용해서 Master와 Slave의 기본 골격을 갖춰봅시다.

Master (PORT:10000)

Master 인스턴스를 만들어 봅시다. 아래 $로 표시한 라인이 실제로 입력한 부분입니다.

$ ./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] <span class="light">10000}
Please select the redis config file name [/etc/redis/10000.conf] <span class="light">/home/redis/10000.conf}
Please select the redis log file name [/var/log/redis_10000.log] <span class="light">/home/redis/10000.log}
Please select the data directory for this instance [/var/lib/redis/10000] <span class="light">/home/redis/10000}
Please select the redis executable path [] <span class="light">/path/to/your/redis/redis-server}
Selected config:
Port           : 10000
Config file    : /home/redis/10000.conf
Log file       : /home/redis/10000.log
Data dir       : /home/redis/10000
Executable     : /path/to/your/redis/redis-server
Cli Executable : /path/to/your/redis/redis-cli
Is this ok? Then press ENTER to go on or Ctrl-C to abort.
Copied /tmp/10000.conf => /etc/init.d/redis_10000
Installing service...
Successfully added to chkconfig!
Successfully added to runlevels 345!
Starting Redis server...
Installation successful!

위처럼 성공이라는 문구가 출력 되면 자동으로 서버 실행 파일을 만들어 줍니다. 여기서는 /etc/init.d/redis_10000에 생성 되었습니다. 그리고 chkconfig를 자동 설정 하기 때문에 혹시라도 서버가 재부팅 되면 다시 해당 Redis 인스턴스를 기동합니다. 나중에 테스트가 끝나면 chkconfig에서 지워줍시다.

그리고 전 아래처럼 실행 파일을 이동했습니다.

$ mv /etc/init.d/redis_10000 /home/redis/10000.sh

Slave (PORT:10001,10002)

위와 같은 작업을 반복합니다. 각각 10001, 10002를 붙여서요.

Checking Servers

여기까지 진행했다면 아래와 같은 커맨드를 실행시킵니다.

$ ps -ef | grep redis
00:00:00 /path/to/your/redis/redis-server *:10000
00:00:00 /path/to/your/redis/redis-server *:10001
00:00:00 /path/to/your/redis/redis-server *:10002
00:00:00 grep redis

이미 10000~10002번 포트의 Redis 인스턴스가 올라가 있는 상태입니다. 다시 전부 내려줍시다.

$ /home/redis/10000.sh stop
Stopping ...
Redis stopped
$ /home/redis/10001.sh stop
Stopping ...
Redis stopped
$ /home/redis/10002.sh stop
Stopping ...
Redis stopped

Replication Config

이제 데이터 복제를 위해 설정 파일을 수정해봅시다.

Master (PORT:10000)

마스터가 될 /home/redis/10000.conf 파일을 열어서 수정합시다. 190번째쯤 라인에서 다음과 같은 주석을 볼 수 있습니다. Replication 및 각 설정값에 대한 설명이 되어 있습니다.

...
################################# REPLICATION #################################
...

그중 masterauth라는 값이 있습니다. 해당 값에 대해 주석은 다음처럼 되어 있네요.

# If the master is password protected (using the "requirepass" configuration
# directive below) it is possible to tell the slave to authenticate before
# starting the replication synchronization process, otherwise the master will
# refuse the slave request.
#
# masterauth <master-password>

마스터가 requirepass라는 값에 의해 패스워드 암호화가 되어 있으면 이 부분을 넣게 되어 있군요. 그렇군요, 안전 제일이죠. Master의 환경 구성에는 requirepass를 찾아서 고쳐봅시다.

...
# requirepass foobared
...

위처럼 주석 되어 있는 부분을 해제하고 패스워드를 입력합니다. 저는 그대로 두겠습니다. 따라서 아래처럼 됩니다.

...
requirepass foobared
...

그 외 설정값이 많아 보이지만 현재 Master 환경 구성은 /home/redis/10000.conf 에서 requirepass foobared을 주석해제 한거 말곤 없습니다. 나머지 옵션 설정은 필요할 때 살펴 봅시다.

Slave (PORT:10001,10002)

이번엔 Slave 환경 설정을 해줍니다. 각각 /home/redis/10001.conf, /home/redis/10002.conf 파일이 되겠네요.

해당 파일들에서 slaveof, masterauth값을 찾아 아래처럼 바꿔줍니다.

slaveof 127.0.0.1 10000
masterauth foobared

Start Server

이제 환경 설정까지 끝났으니 다시 한번 Redis 인스턴스들을 띄워봅시다. Master, Slave 순으로 띄워봅니다.

$ /home/redis/10000.sh start
Starting Redis server...
$ /home/redis/10001.sh start
Starting Redis server...
$ /home/redis/10002.sh start
Starting Redis server...

Check log file

음, 뭐가 달라진건지 모르겠습니다. 달랑 한 줄 나오고 끝이네요. 로그 파일 한 번 열어 봐야겠어요.

우선 Master의 로그 파일인 /home/redis/10000.log 파일을 열어서 확인해봤습니다.

* DB loaded from disk: 0.000 seconds
* The server is now ready to accept connections on port 10000
* Slave 127.0.0.1:10001 asks for synchronization
* Full resync requested by slave 127.0.0.1:10001
* Starting BGSAVE for SYNC with target: disk
* Background saving started by pid 12055
* DB saved on disk
* RDB: 0 MB of memory used by copy-on-write
* Background saving terminated with success
* Synchronization with slave 127.0.0.1:10001 succeeded
* Slave 127.0.0.1:10002 asks for synchronization
* Full resync requested by slave 127.0.0.1:10002
* Starting BGSAVE for SYNC with target: disk
* Background saving started by pid 12061
* DB saved on disk
* RDB: 0 MB of memory used by copy-on-write
* Background saving terminated with success
* Synchronization with slave 127.0.0.1:10002 succeeded

로그에는 기록이 되고 있었군요. Slave 인스턴스 두 개가 동기화 요청을 보냈고 성공한 내역이 로그에 적혀 있네요. 그럼 이번엔 Slave 로그 파일을 열어봅시다. /home/redis/10001.log 하나만 확인해 보죠.

* The server is now ready to accept connections on port 10001
* Connecting to MASTER 127.0.0.1:10000
* MASTER <-> SLAVE sync started
* Non blocking connect for SYNC fired the event.
* Master replied to PING, replication can continue...
* Partial resynchronization not possible (no cached master)
* Full resync from master: 8ba1b352b7f991710e433c89a8bd8e572c1c31af:1
* MASTER <-> SLAVE sync: receiving 18 bytes from master
* MASTER <-> SLAVE sync: Flushing old data
* MASTER <-> SLAVE sync: Loading DB in memory
* MASTER <-> SLAVE sync: Finished with success

Slave의 로그에는 Master와 접속하는 로그가 찍혀 있습니다. 로그에는 정상적으로 Replication 처리가 되어 보이는데, 실제로 값을 넣어서 테스트를 해봐야겠네요.

Data Replication Test

이번엔 Master 인스턴스에 접속해서 데이터를 넣어보고 제대로 복제가 되는지 확인해 봅시다.

$ ./redis-cli -p 10000
127.0.0.1:10000> set test test
(error) NOAUTH Authentication required.
127.0.0.1:10000>

음...? 오류가 납니다. 아까 전에 패스워드 지정한 것 때문에 그런가봐요. ./redis-cli --help를 통해 -a 옵션을 더 줘야한다는 것을 알았습니다.

$ ./redis-cli -p 10000 -a foobared
127.0.0.1:10000> set test test
OK
127.0.0.1:10000>

이번엔 정상적으로 Master에 값이 써졌습니다. Slave에 접속해서 값이 있는지 확인해 봅시다.

$ ./redis-cli -p 10001
127.0.0.1:10001> get test
"test"
127.0.0.1:10001>

잘 나오는군요. 복제는 잘 되는 것 같네요.

Exception Handling

모든 게 "언제나" 정상이면 좋겠습니다만, 언제나 "예외"가 있는법입니다.

혹시라도 Master, Slave가 죽으면 어떻게 될까요? 한번 알아봅시다.

Kill the Master

테스트를 위해서 Master부터 내려봅니다.

$ /home/redis/10000.sh stop
Stopping ...
(error) NOAUTH Authentication required.
Waiting for Redis to shutdown ...

아까부터 자꾸 패스워드가 걸리네요. /home/redis/10000.sh 파일을 열어서 stop에 관련된 부분을 찾아보니 다음과 같은 스크립트가 있습니다.

stop)
	if [ ! -f $PIDFILE ]
	then
		echo "$PIDFILE does not exist, process is not running"
	else
		PID=$(cat $PIDFILE)
		echo "Stopping ..."
		$CLIEXEC -p $REDISPORT -a foobared shutdown # (1)
		while [ -x /proc/${PID} ]
		do
			echo "Waiting for Redis to shutdown ..."
			sleep 1
		done
		echo "Redis stopped"
	fi

(1)부분에 패스워드를 추가해 넣어줍니다. 그리고 다시 정지 시켜봅니다.

$ /home/redis/10000.sh stop
Stopping ...
Redis stopped

그럼 10001번 포트 Slave 로그 파일을 열어서 한번 살펴봅시다.

$ tail -f /home/redis/10001.log
# Error condition on socket for SYNC: Connection refused
* Connecting to MASTER 127.0.0.1:10000
* MASTER <-> SLAVE sync started
# Error condition on socket for SYNC: Connection refused

그냥 커넥션이 끊어졌으니 자꾸 재접속하려고 시도하는군요. Slave 인스턴스는 살아 있으니 Read는 될거 같네요. 해봅시다.

$ ./redis-cli -p 10001
127.0.0.1:10001> get test
"test"
127.0.0.1:10001>

Master가 떨어져도 Slave에서 Read 자체는 되는걸로 보이네요. Master를 다시 올려봅시다.

$ /home/redis/10000.sh start
Starting Redis server...
$ tail -n 12 /home/redis/10001.log
# Error condition on socket for SYNC: Connection refused
* Connecting to MASTER 127.0.0.1:10000
* MASTER <-> SLAVE sync started
* Non blocking connect for SYNC fired the event.
* Master replied to PING, replication can continue...
* Trying a partial resynchronization (request 8ba1b352b7f991710e433c89a8bd8e572c1c31af:2298).
* Full resync from master: 906271f89a93e797e1388f1a9790fb60d0692793:1
* Discarding previously cached master state.
* MASTER <-> SLAVE sync: receiving 31 bytes from master
* MASTER <-> SLAVE sync: Flushing old data
* MASTER <-> SLAVE sync: Loading DB in memory
* MASTER <-> SLAVE sync: Finished with success

네, Slave는 Master 다시 커넥션을 맺고 후속 작업을 처리 하네요.

Kill the Slave

그럼, Slave를 떨군뒤에 Master에서 추가 데이터 삽입이 있었다고 한다면, 떨어졌던 Slave는 다시 살아났을 때 데이터 동기화를 할까요?

테스트를 시작합니다!

$ /home/redis/10001.sh stop
Stopping ...
Redis stopped
$ <span class="light">tail -n 3 /home/redis/10000.log}
* Synchronization with slave 127.0.0.1:10002 succeeded
* Synchronization with slave 127.0.0.1:10001 succeeded
# Connection with slave 127.0.0.1:10001 lost.

일단, 10001번 포트 Slave를 내렸습니다. 그리고 Master에서 데이터를 추가로 삽입해 봅시다.

$ ./redis-cli -p 10000 -a foobared
127.0.0.1:10000> set slave kill
OK
127.0.0.1:10000>

그리고 Slave를 다시 살려봅시다.

$ /home/redis/10001.sh start
Starting Redis server...
$ ./redis-cli -p 10001
127.0.0.1:10001> get slave
"kill"
127.0.0.1:10001>

잘 되는 것 같네요.