Redis, Sentinel 고가용성(HA) 설정과 운용방법, Python Client example
Redis(레디스; REmote DIctionary System)은 요즘 각광 받고 있는 In-memory Data(key-value) Store이다. 언제고 한 번 다뤄 봤으면 했는데, 마침 비슷한 기회가 주어져서 고가용성을 확보할 수 있는 중요한 설정 방법을 찾아보고 Redis 를 활용하는 아주 간단한 Python Client 예제를 정리하여 실전을 위해 기록해 두고자 한다.
"쓰다 보니 내용이 좀 많습니다. 2편 정도로 나누려 했으나, 다루려고 하는 내용을 한 편에 모아서 구성하는 편이 더 좋다고 생각했으니 스크롤 압박이 심하더라도 양해 바랍니다" - Barracuda -
"Redis 는 DBMS 인가?", "임시 데이터 저장용 캐시라고도 하던데..." 하는 잡다한 얘기는 여기서는 생략하자. 자주 쓰다 보면 아~ Redis 는 이래서 쓰는구나 하고 느끼면 되겠고, 기회가 된다면 왜 Naver Japan에서 LINE 서비스에 이를 적용했는지, Memcached와 왜 자주 비교 되는지, Twitter, Flickr, Github, Tumblr, Instagram 등의 구축 사례를 구글링 등으로 찾아 보면 어느 정도 감이 잡힐 것이라고 생각된다.
본 포스팅에서는 아래의 내용 위주로 정리해 두기로 한다.
* Redis 아키텍처 결정 및 설치/설정 방법
* Redis Sentinel 의 추가 설정과 HA 운영
* Redis Client example - python 버전
* Redis 를 서비스에 적용할 때의 주의사항
1. Redis 아키텍처 결정 및 설치/설정 방법
Redis 로 구성할 수 있는 아키텍처들은 그 용도, 규모에 따른 조합이 다양하게 있을 수 있으며, 응용/확장 형태로 Naver 등에서는 Zookeeper 와 함께 사용하기도 하고, 때로는 Haproxy/Keepalived 를 병행하여 실제 서비스에 사용되기도 한다. 여기서는 Redis 만을 사용한 구성 모델을 생각해 보자.
* Single(단일) 서버 구성 - 개발, 테스트 용도
* MS(Master_1-Slave_N) 구성 - 소규모 개발 및 테스트 용도
* Redis MS & Sentinel*2(Total 5) 구성 - 소규모 개발 및 서비스 용도
* Redis MS & Cluster & Sharding - 중규모 이상 서비스 용도
Redis 와 Sentinel 을 이해하기 위한 간단한 예
여기서는 비교적 단순하면서도 가용성(HA: High Availability)이 보장될 수 있는 3 번째 방법을 채택하고,실제로 구현해 보자. 우선 아래 첫 번째 그림을 보자. Redis 를 설명하는 웹페이지들이 예를 들어 자주 보여 주는 그림이다.
그림 1. Single Maser-Slave & Sentinel
그림1은 하나 씩의 Master-Slave 와, 이들을 감시하는 Sentinel이 Master down시 Slave를 Master 를 승격(Failover 과정을 통해)시켜 주게 되는 좋은 예라고 할 수 있다. 그러나 Redis와 Sentinel 의 작동 과정과 원리를 직관적으로 잘 보여 준다는 점 외에는 이 아키텍처로부터 얻을 수 있는 것은 거의 없다. 왜냐하면 "3대의 서버 중 Master 1대만 죽었을 때(quorom=1일때. quorom은 뒤에 설명)" 만 안정성이 보장된다는 한계를 지닌 아키텍처이기 때문이다.
부하를 분산하기 위해 Master 는 write-only, Slave 는 read-only 로 작동하게 된다고 가정하면, Redis에 접속하여 데이터를 처리하는 Client는 Sentinel로부터 Master와 Slave 의 주소를 알아내려 하기 때문에, Slave나 Sentinel이 죽거나 특정한 2대가 동시에 죽는 어떤 경우에라도 정상적인 서비스를 기대하지 못하게 된다는 사실을 잘 따져 보도록 하자(Sentinel이 왜 필요해 지는지는 아래 실제 구성에서 자연스럽게 정리 될 것이다).
서버 3대로 안정적인 Redis 서비스를 할 수 있는 방법은?
그렇다면, 위와 같은 3대의 서버로 어떻게 하면 비교적 안정적인 서비스 환경을 구성할 수 있을까? 아래 그림을 보자.
그림 2. Redis Master 1, Slave 2 & 3 Sentinels
그림 2는 2대의 서버가 동시에 죽더라도 최소한 Data의 읽기 또는 쓰기 중 한가지만은 가능한 구조이다. Master는 2대의 Slave 들에 각각 변경 데이터를 보내 주며, 3개의 Sentinel 들이 3대의 Redis 서버와 자신을 제외한 나머지 2대의 Sentinel 들을 감시하는 구조이다. Sentinel 을 3대 이상 홀수로 사용하는 이유는, Master 가 죽고 Failover 상황이 발생생했을 때, 홀수개의 Sentinel들이 각각 투표에 참여하여 과반수 이상이 선출되도록 하기 위함이다(redis.io 권고). 그러나 ...
* 이 아키텍처에는 결정적인 한계가 존재한다. 위 그림에서는 Master 서버 내의 Redis 서버(데몬, 서비스)가 Down 되었다면 3개의 Sentinel들이 투표에 참여하여 과반수인 2표(quorom >= 2) 이상을 얻은 Slave 가 Master 로 선출될 것이다. 그런데 뭐가 문제인가?
* 실제 상황에서는 서버(머신) 내의 특정 프로세스 또는 데몬이 죽어 버리는 경우도 있지만, 머신 자체의 하드웨어(전원, 네트워크) 고장/오동작 또는 네트워크 장비/포트 단절 등에 의해 서버 자체의 연결이 끊어지는 경우가 더 많을 수 있다. 그림 1에서와 같은 문제점과 유사하게, Master 서버 머신이 Down 되어 버리거나(왜 문제일까? 아래에서 Quiz 로 제시한다), 2대의 서버가 동시에 Down 되어 버린다면 마찬가지의 참담한 결과를 맞게 된다.
해결 방법은 무엇일까? 단순히 Redis-Sentinel 서버를 1대 더 추가하여 4대를 만들어서 사용하는 방법도 있고, 3대의 Sentinel을 모두 외부의 다른 서버에 설치하거나, Redis 에 접속하는 Client 머신 내에 설치를 권장하기도 한다. 또는 시스템 내의 그리 바쁘지 않은 서버 2대를 따로 선정해서 Sentinel 만 설치/설정해 두면 상대적으로 그림은 간단해지고 위의 문제도 어느 정도 해결 가능하다.
그림 3. Redis Master 1, Slave 2 & 5 Sentinels
그림 3에서는 2대의 서버 머신이 동시에 죽는 어떠한 상황이라도 Redis 서비스 자체는 훌륭하게 동작하게 될 수 있다. 특별히 2대의 Redis 서버가 동시에 다운되었더라도, Redis Client 측에서 Master 를 Write & Read 용으로 사용하도록 약간의 코딩을 해 둔다면 말이다.
* 투입되는 서버의 댓수가 많다면 충분히 안정적인 서비스가 가능하겠지만, 하드웨어나 VM이 무한정 사용될 수는 없는 것이고 보면, 외부 Sentinel 머신을 한 개 정도만 더 추가해서 Redis 3, Sentinel 5 구성이 비교적 합리적인 선택이 아닐까 생각된다(물론 운영의 부담이 되는 관리포인트가 늘어남은 감수해야 한다)
* 결국은 여건을 충분히 고려한 취사 선택이 어느 정도로 필요하다는 말이 되겠고, 본 포스팅에서는 마지막의 그림 3을 목표 아키텍처로 하여 시스템을 준비하고, 실제로 구현하고 검증까지 진행해 보도록 하자.
Redis 서버용 머신을 이용해 Master(1)-Slave(2) 를 구성한다
* 5대의 서버를 준비한다(본 테스트 환경에서 사용한 머신은 모두 ubuntu14.4 vm이며 10.0.0.0/16 네트워크에 연결)
- ubuntu14-pv1: 10.0.10.1 (Redis Master, Sentinel)
- ubuntu14-pv2: 10.0.10.2 (Redis Slave1, Sentinel)
- ubuntu14-pv3: 10.0.10.3 (Redis Slave2, Sentinel)
- ubuntu14-pvha1: 10.0.0.1 (Sentinel)
- ubuntu14-pvha2: 10.0.0.2 (Sentinel)
그림 4. Redis Master 1, Slave 2
* 1단계 작업으로 그림 4에서와 같이 3대의 서버로 Redis Master-Slave 구성부터 진행
* ubuntu14-pv1~3, ubuntu14-pvha1,2 모든 5대의 서버에서 동일하게 작업
root@ubuntu14-pv1:~# apt-get install redis-server redis-tools
root@ubuntu14-pv1:~# apt-get install python-pip
root@ubuntu14-pv1:~# pip install redis
root@ubuntu14-pv1:~# vi /etc/sysctl.conf -> 아래 내용을 추가
# Redis M-S sync
vm.overcommit_memory=1
# Increase max open file limit
fs.file-max = 1048576
root@ubuntu14-pv1:~# sysctl -p
root@ubuntu14-pv1:~# ulimit -n 65535
root@ubuntu14-pv1:~# vi ~/.bashrc -> 스크립트 최 상단에 아래 내용을 삽입
ulimit -n 65535
* ubuntu14-pv1 서버를 Redis master 로 설정하기 위한 작업이다
* 설치 자체는 어렵지 않지만, config 설정시에 주의해야할 것들이 있으니, 붉은 글씨의 라인에 쓰인 설명을 잘 참고한다
* requirepass 는 자신이 Master 가 되었을 때 요구할 접속 암호, masterauth 는 자신이 Slave 일 때 Master 로 접속하기 위한 암호이다. 본 설정에서는 뒤에 Sentinel 에 의해서 Master <-> Slave 전환이 자유로워야 하므로 동일하게 맞추어야 한다.
root@ubuntu14-pv1:~# vi /etc/redis/redis.conf
daemonize yes
pidfile /var/run/redis/redis-server.pid
port 6379
timeout 0
tcp-keepalive 0
bind 10.0.10.1 # 서버 머신 자체의 IP 주소
# slaveof 10.0.10.3 6379 # Master 는 slaveof 설정이 없어야 한다
masterauth 1234
requirepass 1234
repl-ping-slave-period 10
repl-timeout 60
loglevel notice
logfile /var/log/redis/redis-server.log
databases 16
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir /var/lib/redis
slave-read-only yes
# Yes for high bandwidth, No for high latency or high traffic
repl-disable-tcp-nodelay yes
slave-priority 100
# Mitigation for replication lag, only for Master
#min-slaves-to-write 1
#min-slaves-max-lag 10
appendonly no
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
aof-rewrite-incremental-fsync yes
* ubuntu14-pv2, 3 서버를 Redis slave 로 설정하기 위한 작업이다
* 붉은 글씨의 라인에 쓰인 설명을 잘 참고한다. 나머지 부분 내용은 Master와 동일하다
root@ubuntu14-pv2:~# vi /etc/redis/redis.conf
daemonize yes
pidfile /var/run/redis/redis-server.pid
port 6379
timeout 0
tcp-keepalive 0
bind 10.0.10.2 # 서버 머신 자체의 IP주소
slaveof 10.0.10.1 6379 # 초기에 Master 로 사용할 1번 서버의 IP주소
masterauth 1234
requirepass 1234
repl-ping-slave-period 10
repl-timeout 60
loglevel debug
logfile /var/log/redis/redis-server.log
databases 16
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb
dir /var/lib/redis
slave-read-only yes
# Yes for high bandwidth, No for high latency or high traffic
repl-disable-tcp-nodelay yes
slave-priority 100
# Mitigation for replication lag, Comment out for slaves
#min-slaves-to-write 1
#min-slaves-max-lag 10
appendonly no
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb
lua-time-limit 5000
slowlog-log-slower-than 10000
slowlog-max-len 128
notify-keyspace-events ""
hash-max-ziplist-entries 512
hash-max-ziplist-value 64
list-max-ziplist-entries 512
list-max-ziplist-value 64
set-max-intset-entries 512
zset-max-ziplist-entries 128
zset-max-ziplist-value 64
activerehashing yes
client-output-buffer-limit normal 0 0 0
client-output-buffer-limit slave 256mb 64mb 60
client-output-buffer-limit pubsub 32mb 8mb 60
hz 10
aof-rewrite-incremental-fsync yes
* Master부터 두 번째 Slave 까지, 모두 위와 같이 작업을 했다면, Master 부터 순차적으로 Redis 를 재시작한다.
* 진행 과정을 관찰하기 위해 redis-server 기동 후 log에 tail 을 걸어 둔다
root@ubuntu14-pv1:~# service redis-server restart
root@ubuntu14-pv1:~# tail -f /var/log/redis/redis-server.log
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 2.8.4 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in stand alone mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 6379
| `-._ `._ / _.-' | PID: 1140
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
[1140] 09 Jul 01:13:08.762 # Server started, Redis version 2.8.4
[1140] 09 Jul 01:13:08.763 * DB loaded from disk: 0.001 seconds
[1140] 09 Jul 01:13:08.763 * The server is now ready to accept connections on port 6379
[1140] 09 Jul 01:13:08.763 - DB 0: 633 keys (0 volatile) in 1536 slots HT.
[1140] 09 Jul 01:13:08.763 - 0 clients connected (0 slaves), 834680 bytes in use
[1140] 09 Jul 01:13:09.189 * Starting BGSAVE for SYNC
[1140] 09 Jul 01:13:09.191 * Background saving started by pid 1143
[1143] 09 Jul 01:13:09.218 * DB saved on disk
[1143] 09 Jul 01:13:09.219 * RDB: 0 MB of memory used by copy-on-write
Redis Master에 데이터를 입력하고 Slave 로 복제 되는지 확인하자
* Sentinel 전용 머신(ubuntu14-pvha1,2)에서는 Sentinel만 작동하여야 하므로, redis-server 데몬을 내리고 자동실행을 해제한다(source build 가 아닌 apt-get 같은 방법으로 redis-server 를 설치하면 부팅후 자동실행까지 설정되므로)
root@ubuntu14-pvha2:~# service redis-server stop
root@ubuntu14-pvha2:~# rm /etc/init.d/redis-server
root@ubuntu14-pvha2:~# update-rc.d redis-server remove
* Sentinel 전용 머신(또는 redis-tools 가 설치된 외부 머신)의 redis client 를 이용해서 확인을 수행한다
* Master 에 접속하여 "testkey01":"data1234" Key-Value 를 저장하고 Slave 에서 get 으로 확인
root@ubuntu14-pvha2:~# redis-cli -a 1234 -h 10.0.10.1 -p 6379
10.0.10.1:6379> info replication
# Replication
role:master
connected_slaves:2
slave0:ip=10.0.10.2,port=6379,state=online,offset=1386,lag=0
slave1:ip=10.0.10.3,port=6379,state=online,offset=1386,lag=0
master_repl_offset:1386
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:1385
10.0.10.1:6379> set testkey01 'data1234'
OK
10.0.10.1:6379>
root@ubuntu14-pvha1:~# redis-cli -a 1234 -h 10.0.10.2 -p 6379
10.0.10.2:6379> get testkey01
"data1234"
10.0.10.2:6379>
root@ubuntu14-pvha1:~# redis-cli -a 1234 -h 10.0.10.3 -p 6379
10.0.10.3:6379> get testkey01
"data1234"
전체적인 동작이 정상적인 것을 확인했다. 자 이 상황에서 Quiz 하나 !(검토하신 아이디어를 공유하거나 질문 하실 분은 댓글로 기재해 주세요)
* <Quiz 1> Master 서버 머신이 Down(redis.io 문서에서는 partition 이라고 표현한다. 즉 shutdown, power down, network 단절 등) 된다면 어떤 일이 일어 나는지 생각해 보자. Master 는 선출될 수 있을까? Read 동작은 가능한가? 원래의 Master 가 다시 연결되면 전체 시스템이 정상 동작 할까?(전제조건: Master 1, Slave 2(read-only), Sentinel 3, quorom=2 일 경우)
[답은 아래로 drag 하세요. 중요한 것은 왜 그럴까를 따져 보는 것입니다]
- Master 는 선출되지 않는다. 즉, 쓰기 작업은 불가능
- Read 는 가능하다(Sentinel을 통하여 Slave 를 알아 낸다면)
- Master 가 살아나면 원 상태로 정상 동작 한다
여기까지 3대의 머신으로 Redis Master-Slave 를 구성하는 기본 작업을 진행했다. Master 를 Write 전용으로, Slave 를 Read 전용으로 하여 부하는 분산하고 데이터를 복제 보존하는 효과까지는 기대할 수 있지만, 자동으로 Failover를 수행하지는 못하는 구성이다.
2. Redis Sentinel 의 추가 설정과 고가용(HA)적 운영
이제 목표 아키텍처인 그림 3 과 같은 구성을 만드는 것이 다음 작업이다.
5대의 머신에 Sentinel을 설정, 기동하고 log 파일을 보면서 진행 과정 관찰
* Sentinel 수행 머신인 ubuntu14-pv1~3, ubuntu14-pvha1,2 에서 각각 다음의 작업을 동일하게 수행한다.
* 모든 Sentinel의 설정 파일은 동일하여야 한다
* 현재의 Redis Master 인 10.0.10.1:6379 를 모니터 대상으로 설정하며, quorom=2, 서비스 이름은 stn-master
root@ubuntu14-pv1:~# vi /etc/redis/sentinel.conf
daemonize yes
pidfile "/var/run/redis/sentinel.pid"
logfile "/var/log/redis/sentinel.log"
# port <sentinel-port>
port 26379
sentinel monitor stn-master 10.0.10.1 6379 2 # <== Failover시 Sentinel 이 자동으로 값들을 수정
sentinel down-after-milliseconds stn-master 2000
sentinel failover-timeout stn-master 90000 <== Failover 완료되어야 하는 시간. 데이터 량에 따라 값을 증가
sentinel auth-pass stn-master 1234
sentinel config-epoch stn-master 1 # <== Failover시 Sentinel 이 자동으로 수정(1씩 증가)
# <=== 여기부터 Sentinel 이 자동으로 설정 내용을 추가, 관리
root@ubuntu14-pv1:~# /usr/bin/redis-sentinel /etc/redis/sentinel.conf
root@ubuntu14-pv1:~# tail -f /var/log/redis/sentinel.log
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 2.8.4 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 26379
| `-._ `._ / _.-' | PID: 1155
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
[1094] 09 Jul 15:58:49.321 # Sentinel runid is 0f83482ecc6a3724d40f4469ac8368a1742be8fd
[1094] 09 Jul 15:58:50.324 * +slave slave 10.0.10.2:6379 10.0.10.2 6379 @ stn-master 10.0.10.1 6379
[1094] 09 Jul 15:58:50.324 * +slave slave 10.0.10.3:6379 10.0.10.3 6379 @ stn-master 10.0.10.1 6379
[1094] 09 Jul 15:58:54.013 * +sentinel sentinel 10.0.10.2:26379 10.0.10.2 26379 @ stn-master 10.0.10.1 6379
[1094] 09 Jul 15:58:55.429 * +sentinel sentinel 10.0.10.3:26379 10.0.10.3 26379 @ stn-master 10.0.10.1 6379
[1094] 09 Jul 15:58:56.708 * +sentinel sentinel 10.0.0.1:26379 10.0.0.1 26379 @ stn-master 10.0.10.1 6379
[1094] 09 Jul 15:58:57.791 * +sentinel sentinel 10.0.0.2:26379 10.0.0.2 26379 @ stn-master 10.0.10.1 6379
<...>
root@ubuntu14-pvha2:~# vi /etc/redis/sentinel.conf
root@ubuntu14-pvha2:~# /usr/bin/redis-sentinel /etc/redis/sentinel.conf
_._
_.-``__ ''-._
_.-`` `. `_. ''-._ Redis 2.8.4 (00000000/0) 64 bit
.-`` .-```. ```\/ _.,_ ''-._
( ' , .-` | `, ) Running in sentinel mode
|`-._`-...-` __...-.``-._|'` _.-'| Port: 26379
| `-._ `._ / _.-' | PID: 26166
`-._ `-._ `-./ _.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' | http://redis.io
`-._ `-._`-.__.-'_.-' _.-'
|`-._`-._ `-.__.-' _.-'_.-'|
| `-._`-._ _.-'_.-' |
`-._ `-._`-.__.-'_.-' _.-'
`-._ `-.__.-' _.-'
`-._ _.-'
`-.__.-'
[4204] 09 Jul 15:58:55.946 # Sentinel runid is 82a6faa318223af0362f59095f8c138be9f9e76d
[4204] 09 Jul 15:58:56.949 * +slave slave 10.0.10.2:6379 10.0.10.2 6379 @ stn-master 10.0.10.1 6379
[4204] 09 Jul 15:58:56.949 * +slave slave 10.0.10.3:6379 10.0.10.3 6379 @ stn-master 10.0.10.1 6379
[4204] 09 Jul 15:58:57.617 * +sentinel sentinel 10.0.10.3:26379 10.0.10.3 26379 @ stn-master 10.0.10.1 6379
[4204] 09 Jul 15:58:57.731 * +sentinel sentinel 10.0.10.1:26379 10.0.10.1 26379 @ stn-master 10.0.10.1 6379
[4204] 09 Jul 15:58:57.935 * +sentinel sentinel 10.0.0.1:26379 10.0.0.1 26379 @ stn-master 10.0.10.1 6379
[4204] 09 Jul 15:58:58.182 * +sentinel sentinel 10.0.10.2:26379 10.0.10.2 26379 @ stn-master 10.0.10.1 6379
* 서비스에 투입된 Sentinel 의 정보를 확인해 보자(모든 Sentinel 에서의 조회 값이 동일함)
root@ubuntu14-pvha2:~# redis-cli -a 1234 -h 10.0.10.1 -p 26379
10.0.10.1:26379> info
# Server
redis_version:2.8.4
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:a44a05d76f06a5d9
redis_mode:sentinel
os:Linux 3.13.0-55-generic x86_64
arch_bits:64
multiplexing_api:epoll
gcc_version:4.8.2
process_id:1094
run_id:0f83482ecc6a3724d40f4469ac8368a1742be8fd
tcp_port:26379
uptime_in_seconds:177
uptime_in_days:0
hz:11
lru_clock:1036194
config_file:/etc/redis/sentinel.conf
# Sentinel
sentinel_masters:1
sentinel_tilt:0
sentinel_running_scripts:0
sentinel_scripts_queue_length:0
master0:name=stn-master,status=ok,address=10.0.10.1:6379,slaves=2,sentinels=5
이제 설정 과정은 끝이 났다. 테스트 시나리오를 정하고 검증을 수행해 보자. 본 포스팅의 하이라이트라고 할 수도 있는 중요한 부분이다.
Test Case 1: Master 셧다운
* Master에 ssh 로 접속하여 셧다운을 수행하고 Slave 2 의 로그를 살펴 보자
root@ubuntu14-pv1:~# shutdown -h now
* Slave 2(10.0.10.2)의 로그 모니터링, 해설
[1063] 09 Jul 16:03:13.245 # +sdown master stn-master 10.0.10.1 6379 <== Down 감지(주관적)
[1063] 09 Jul 16:03:13.323 # +odown master stn-master 10.0.10.1 6379 #quorum 2/2 <== Down 감지(객관적)
[1063] 09 Jul 16:03:13.323 # +new-epoch 10 <== 후속 조치 발동
[1063] 09 Jul 16:03:13.323 # +try-failover master stn-master 10.0.10.1 6379 <== Failover 발동
[1063] 09 Jul 16:03:13.324 # +vote-for-leader 1a80939f2d06e6c1d5073ea0dca121c2c02ac5f3 10
[1063] 09 Jul 16:03:13.327 # 10.0.0.1:26379 voted for 1a80939f2d06e6c1d5073ea0dca121c2c02ac5f3 10
[1063] 09 Jul 16:03:13.327 # 10.0.0.2:26379 voted for 1a80939f2d06e6c1d5073ea0dca121c2c02ac5f3 10
[1063] 09 Jul 16:03:13.328 # 10.0.10.2:26379 voted for 1a80939f2d06e6c1d5073ea0dca121c2c02ac5f3 10
[1063] 09 Jul 16:03:13.390 # +elected-leader master stn-master 10.0.10.1 6379 <== 투표 종료
[1063] 09 Jul 16:03:13.391 # +failover-state-select-slave master stn-master 10.0.10.1 6379
[1063] 09 Jul 16:03:13.458 # +selected-slave slave 10.0.10.2:6379 10.0.10.2 6379 @ stn-master 10.0.10.1 6379 <== Slave2(10.0.10.3:6379) 이 최종 선정
[1063] 09 Jul 16:03:13.458 * +failover-state-send-slaveof-noone slave 10.0.10.2:6379 10.0.10.2 6379 @ stn-master 10.0.10.1 6379 <== Slave2 를 10.0.10.1 의 slave 에서 제외
[1063] 09 Jul 16:03:13.513 * +failover-state-wait-promotion slave 10.0.10.2:6379 10.0.10.2 6379 @ stn-master 10.0.10.1 6379
[1063] 09 Jul 16:03:14.392 # +promoted-slave slave 10.0.10.2:6379 10.0.10.2 6379 @ stn-master 10.0.10.1 6379 <== Slave2 를 Master 로 승격 요청
[1063] 09 Jul 16:03:14.393 # +failover-state-reconf-slaves master stn-master 10.0.10.1 6379
[1063] 09 Jul 16:03:14.443 * +slave-reconf-sent slave 10.0.10.3:6379 10.0.10.3 6379 @ stn-master 10.0.10.1 6379
[1063] 09 Jul 16:03:14.635 # +sdown sentinel 10.0.10.1:26379 10.0.10.1 26379 @ stn-master 10.0.10.1 6379
[1063] 09 Jul 16:03:15.410 * +slave-reconf-inprog slave 10.0.10.3:6379 10.0.10.3 6379 @ stn-master 10.0.10.1 6379
[1063] 09 Jul 16:03:15.485 # -odown master stn-master 10.0.10.1 6379
[1063] 09 Jul 16:03:16.423 * +slave-reconf-done slave 10.0.10.3:6379 10.0.10.3 6379 @ stn-master 10.0.10.1 6379 <== 기존 Slave2의 config 를 변경(10.0.10.2 의 slave로)
[1063] 09 Jul 16:03:16.524 # +failover-end master stn-master 10.0.10.1 6379 <== Failover 종료 알림
[1063] 09 Jul 16:03:16.524 # +switch-master stn-master 10.0.10.1 6379 10.0.10.2 6379
[1063] 09 Jul 16:03:16.525 * +slave slave 10.0.10.3:6379 10.0.10.3 6379 @ stn-master 10.0.10.2 6379
[1063] 09 Jul 16:03:16.560 * +slave slave 10.0.10.1:6379 10.0.10.1 6379 @ stn-master 10.0.10.2 6379 <== Down된 10.0.10.1 은 Slave 로 내려감
[1063] 09 Jul 16:03:18.611 # +sdown slave 10.0.10.1:6379 10.0.10.1 6379 @ stn-master 10.0.10.2 6379
* 이제 1번 과정에서 redis-cli 로 Master에 데이터를 입력(Set)하고 Slave에서 Get 으로 확인하는 과정을 수행해 보자
root@ubuntu14-pvha1:~# redis-cli -a 1234 -h 10.0.10.3 -p 6379
10.0.10.2:6379> keys *
1) "testkey01"
10.0.10.2:6379> set key_into_master2 'data5678'
OK
10.0.10.2:6379>
root@ubuntu14-pvha1:~# redis-cli -a 1234 -h 10.0.10.2 -p 6379
10.0.10.3:6379> keys *
1) "key_into_master2"
2) "testkey01"
10.0.10.3:6379>
Test Case 2: 새로운 Master 셧다운
* Case 1 에 이어서 Master(10.0.10.2)에 ssh 로 접속하여 셧다운을 수행하고 Master, Sentinel 머신측 의 로그를 살펴 보자
root@ubuntu14-pv2:~# shutdown -h now
* Slave(10.0.10.2)의 로그 모니터링
[1063] 09 Jul 16:13:22.809 # +sdown master stn-master 10.0.10.2 6379
[1063] 09 Jul 16:13:22.902 # +new-epoch 11
[1063] 09 Jul 16:13:22.902 # +vote-for-leader f7ca052de1d504d263511d5fb9d032c56a0fa351 11
[1063] 09 Jul 16:13:23.910 # +odown master stn-master 10.0.10.2 6379 #quorum 3/2 <== 셧다운 감지
[1063] 09 Jul 16:13:24.666 # +sdown sentinel 10.0.10.2:26379 10.0.10.2 26379 @ stn-master 10.0.10.2 6379
<== Master 완료 후 Failover 완료시까지 시간(sentinel.conf 에 90초 설정) 경과, 결국 Failover 실패로 끝남
[1063] 09 Jul 16:16:22.920 # +new-epoch 12
[1063] 09 Jul 16:16:22.921 # +try-failover master stn-master 10.0.10.2 6379
[1063] 09 Jul 16:16:22.921 # +vote-for-leader 1a80939f2d06e6c1d5073ea0dca121c2c02ac5f3 12
[1063] 09 Jul 16:16:22.923 # 10.0.0.2:26379 voted for 1a80939f2d06e6c1d5073ea0dca121c2c02ac5f3 12
[1063] 09 Jul 16:16:22.923 # 10.0.0.1:26379 voted for 1a80939f2d06e6c1d5073ea0dca121c2c02ac5f3 12
[1063] 09 Jul 16:16:22.984 # +elected-leader master stn-master 10.0.10.2 6379 <== 투표 종료
[1063] 09 Jul 16:16:22.985 # +failover-state-select-slave master stn-master 10.0.10.2 6379
[1063] 09 Jul 16:16:23.085 # +selected-slave slave 10.0.10.3:6379 10.0.10.3 6379 @ stn-master 10.0.10.2 6379 <== 유일한 Redis(10.0.10.3:6379) 가 최종 선정
[1063] 09 Jul 16:16:23.086 * +failover-state-send-slaveof-noone slave 10.0.10.3:6379 10.0.10.3 6379 @ stn-master 10.0.10.2 6379 <== 10.0.10.2 의 slave 에서 제외
[1063] 09 Jul 16:16:23.157 * +failover-state-wait-promotion slave 10.0.10.3:6379 10.0.10.3 6379 @ stn-master 10.0.10.2 6379
[1063] 09 Jul 16:16:24.071 # +promoted-slave slave 10.0.10.3:6379 10.0.10.3 6379 @ stn-master 10.0.10.2 6379 <== Master 로 승격 요청
[1063] 09 Jul 16:16:24.072 # +failover-state-reconf-slaves master stn-master 10.0.10.2 6379
[1063] 09 Jul 16:16:24.117 * +slave-reconf-sent slave 10.0.10.1:6379 10.0.10.1 6379 @ stn-master 10.0.10.2 6379
[1063] 09 Jul 16:16:24.118 # +failover-end master stn-master 10.0.10.2 6379
[1063] 09 Jul 16:16:24.118 # +switch-master stn-master 10.0.10.2 6379 10.0.10.3 6379
[1063] 09 Jul 16:16:24.119 * +slave slave 10.0.10.1:6379 10.0.10.1 6379 @ stn-master 10.0.10.3 6379
[1063] 09 Jul 16:16:24.146 * +slave slave 10.0.10.2:6379 10.0.10.2 6379 @ stn-master 10.0.10.3 6379
<== Down 된 10.0.10.1, 10.0.10.2 는 Slave 로...
[1063] 09 Jul 16:16:26.121 # +sdown slave 10.0.10.1:6379 10.0.10.1 6379 @ stn-master 10.0.10.3 6379
[1063] 09 Jul 16:16:26.212 # +sdown slave 10.0.10.2:6379 10.0.10.2 6379 @ stn-master 10.0.10.3 6379
* Sentinel 전용머신(10.0.0.2)의 로그 모니터링
[4204] 09 Jul 16:13:23.058 # +new-epoch 11
[4204] 09 Jul 16:13:23.058 # +vote-for-leader f7ca052de1d504d263511d5fb9d032c56a0fa351 11
[4204] 09 Jul 16:13:23.700 # +sdown master stn-master 10.0.10.2 6379
[4204] 09 Jul 16:13:23.801 # +odown master stn-master 10.0.10.2 6379 #quorum 3/2
[4204] 09 Jul 16:13:24.841 # +sdown sentinel 10.0.10.2:26379 10.0.10.2 26379 @ stn-master 10.0.10.2 6379
[4204] 09 Jul 16:16:23.079 # +new-epoch 12
[4204] 09 Jul 16:16:23.079 # +vote-for-leader 1a80939f2d06e6c1d5073ea0dca121c2c02ac5f3 12
[4204] 09 Jul 16:16:24.496 # +switch-master stn-master 10.0.10.2 6379 10.0.10.3 6379
[4204] 09 Jul 16:16:24.497 * +slave slave 10.0.10.1:6379 10.0.10.1 6379 @ stn-master 10.0.10.3 6379
[4204] 09 Jul 16:16:24.533 * +slave slave 10.0.10.2:6379 10.0.10.2 6379 @ stn-master 10.0.10.3 6379
[4204] 09 Jul 16:16:26.528 # +sdown slave 10.0.10.1:6379 10.0.10.1 6379 @ stn-master 10.0.10.3 6379
[4204] 09 Jul 16:16:26.583 # +sdown slave 10.0.10.2:6379 10.0.10.2 6379 @ stn-master 10.0.10.3 6379
장애 후 복구 과정에서의 처리 절차
위의 Test Case 2까지 진행된 상황에서 redis-cli 로 접속하여 다음과 같이 데이터를 기록해 두자.
root@ubuntu14-pvha1:~# redis-cli -a 1234 -h 10.0.10.3 -p 6379
10.0.10.3:6379> set testkey02 'data7890'
OK
이제 현재의 Master 인 10.0.10.3 의 Redis 에는 최종 버전의 데이터가 저장되어 있다. 여기서 두 번째 Quiz!
셧다운되어 있는 10.0.10.2 머신이 다시 기동되었을 경우 어떤 일이 일어 나는가?10.0.10.2 에 Sentinel 이 기동되었다면 어떤 일이 일어날까? 과연 마지막에 저장한 testkey02 데이터는 보존 될 것인가?
[답은 아래로 drag 하세요] 전제 조건: 기존에 작동하던 3개의 Sentinel 이 작동 상황을 잘 유지하고 있을 경우
10.0.10.2 가 부팅되고 Redis 서비스가 작동 되면, 약간의 시간이 지난 후, 셧다운 되기 전 Master 였던 10.0.10.2는 얌전히 Slave 로 떨어지고, 마지막에 저장된 testkey02 데이터는 이상 없이 보존된다.
* 최종 Master 머신(10.0.0.3)의 로그 모니터링
[1063] 09 Jul 16:16:26.121 # +sdown slave 10.0.10.1:6379 10.0.10.1 6379 @ stn-master 10.0.10.3 6379
[1063] 09 Jul 16:16:26.212 # +sdown slave 10.0.10.2:6379 10.0.10.2 6379 @ stn-master 10.0.10.3 6379
[1063] 09 Jul 16:49:19.799 # -sdown slave 10.0.10.2:6379 10.0.10.2 6379 @ stn-master 10.0.10.3 6379
[1063] 09 Jul 16:50:50.019 * +fix-slave-config slave 10.0.10.2:6379 10.0.10.2 6379 @ stn-master 10.0.10.3 6379 <== Sentinel 에 의해, 셧다운 전에 Master였던 10.0.10.2 Redis는 Slave 로 전환된다
본 포스팅의 실험 결과에서 가장 중요하게 생각해야 할 점은, Sentinel 을 통해 가용성을 유지하기 위한 본 아키텍처에서, Sentinel 을 확인 없이 셧다운한 상태에서 Redis 를 기동하게 되면, 원래 Master 였던 Redis 의 과거 버전 데이터로 인하여 최근 버전에 데이터를 잃어버릴 가능성이 다분하다는 것이다.
그렇다면, 만약 어쩔 수 없는 사정으로 Sentinel 이 작동하지 않게 되어 있고, 수작업으로 최신 데이터를 유지하고 싶다면 어떻게 하면 될까?아래의 순서를 잘 이해하고 차분히 작업을 진행하면 된다.
첫째. 마지막 버전의 데이터를 저장하고 있다고 판단되는 Redis 머신을 Master로 결정한다
둘째, 현재까지 작업에 투입된 모든 Sentinel 을 셧다운하고(# killall redis-sentinel), Redis 서비스도 종료한다(service redis-server stop)
셋째, Redis M-S 구축 첫 단계의 설정에서, Master로 결정한 머신의 redis.conf 에서 slaveof 라인을 comment 처리하고,나머지 머신의 redis.conf 에서 slaveof 라인의 주소를 Master 주소로 변경한다
넷째, 만약을 위해, 각 Redis 머신들의 데이터 파일(/var/lib/redis/dump.rdb)를 백업해 둔다
다섯째, 모든 Sentinel 들의 config 를 아래와 같이 동일하게 수정한다
root@ubuntu14-pvha2:~# vi /etc/redis/sentinel.conf
...
port 26379
sentinel monitor stn-master 10.0.10.3 6379 2 <== Master로 결정한 머신의 주소로 변경
sentinel down-after-milliseconds stn-master 2000
sentinel failover-timeout stn-master 90000
sentinel auth-pass stn-master 1234
sentinel config-epoch stn-master 1 <== 최종 값을 1로 변경
#
# Generated by CONFIG REWRITE <== 여기부터 마지막 라인까지 삭제
dir "/root"
sentinel known-slave stn-master 10.0.10.1 6379
sentinel known-slave stn-master 10.0.10.2 6379
sentinel known-sentinel stn-master 10.0.10.2 26379 f7ca052de1d504d263511d5fb9d032c56a0fa351
sentinel known-sentinel stn-master 10.0.10.1 26379 0f83482ecc6a3724d40f4469ac8368a1742be8fd
sentinel known-sentinel stn-master 10.0.0.1 26379 9e177cbda8d73c7fc11ac17a6a6d0eef02b3b762
sentinel known-sentinel stn-master 10.0.10.3 26379 1a80939f2d06e6c1d5073ea0dca121c2c02ac5f3
여섯째, 여기 까지의 모든 작업 내용을 다시 점검
일곱째, Redis Master - Slave 순으로 시작, Sentinel 을 모두 시작 후 정상 작동 및 데이터 점검
3. Redis Client example - python 버전
다음은 Redis 와 Sentinel 이 적용된 데이터스토어에 접근하고 각종 Key-Value 데이터를 처리하는 Python Client 의 예제이다. 거의 프로그램 개발 초기의 원형이라고 보면 되겠다. 개발을 위한 라이브러리는 redis-py 를 pip 로 설치하는 정도면 충분하다.
* Redis Master 에 접속하여 쇼핑 데이터를 기록, Slave 에서 데이터를 조회, 출력하는 Python application 이다
( 참고: 1000 명의 고객 쇼핑 카트 정보 기록에 0.18 sec, 읽기에 0.1 sec 소요 - 1cpu, 1GB ram, xen vm)
root@ubuntu14-pvha2:~# apt-get install pip
root@ubuntu14-pvha2:~# pip install redis
root@ubuntu14-pvha2:~/pysrc# vi sentinel_test3.py
# -*- coding:utf-8 -*-
import sys, random, time
from redis import Redis, exceptions, RedisError
from redis.sentinel import (Sentinel, SentinelConnectionPool,ConnectionError,
MasterNotFoundError, SlaveNotFoundError)
# Redis 접속 기본 설정값
listSentinel = [('10.0.10.1', 26379), ('10.0.10.2', 26379), ('10.0.10.3', 26379), ('10.0.0.2', 26379)]
strServiceName = 'stn-master'
strRedisPass = '1234'
nDB = 0
nMaxUser = 1000
sentinel = Sentinel(listSentinel, socket_timeout=0.1)
try:
#sentinel.discover_master(strServiceName) # No need for this
#sentinel.discover_slaves(strServiceName)
master = sentinel.master_for(strServiceName, password=strRedisPass, db=nDB, socket_timeout=0.1)
slave = sentinel.slave_for(strServiceName, password=strRedisPass, db=nDB, socket_timeout=0.1)
except MasterNotFoundError:
print 'Master not found or Sentinel instances not runnung'
sys.exit()
except SlaveNotFoundError:
print 'Slave not found or Sentinel instances not runnung'
sys.exit()
except ConnectionError:
print 'Connection Error. Check if Sentinel instances are running'
sys.exit()
start_time = time.time()
for n in range(1, nMaxUser):
for m in range(1, random.randint(0, 5)):
master.hset( "cart.user:"+str(n), random.randint(1, 300), random.randint(1, 5) )
time_elapsed_1 = time.time() - start_time
start_time = time.time()
for n in range(1, nMaxUser):
slave.hgetall("cart.user:"+str(n))
time_elapsed_2 = time.time() - start_time
count = 0
for n in range(1, nMaxUser):
data = slave.hgetall("cart.user:"+str(n))
if len(data) > 0:
count = count + 1
print count, " [Cart.user:"+str(n)+"] ", data
print "---------------------------------------------------"
print "[Time for writing]: ", time_elapsed_1, " sec., [Time for reading]: ", time_elapsed_2, " sec."
주의 사항: Redis 를 서비스에 적용했을 때의 '대재앙' 을 막아라
운영상 설정 사항 관련 주의 사항
- Sentinel(즉 redis-sentinel) 을 부팅시 자동 실행하도록 두지 마라: 최종 변경 데이터를 날리거나 전체 데이터가 날아갈 수 있다. 귀차니즘은 당신의 몸값을 깎을 수도 있다.
- 데이터를 Disk에 저장하는 save 설정 주기를 짧게 하지 마라: Disk IO 때문에 속도 저하가 발생할 수 있다. 다양한 관찰과 실험을 통해 최적의 save 설정 주기를 찾고 사용하라.
- 데이터 량에 따른 최적의 Failover timeout 값을 찾고 sentinel.conf에 적용하라: 이유는 위에서 설명하였다.
- 메모리 사용 량을 초과하도록 방치하면 안된다: 자칫 메인 메모리를 다 쓰면 가상메모리까지 사용하게 되어 디스크 스와핑이 다량 발생하고, 시스템은 거의 먹통 수준에 빠질 수도 있다.
- Sentinel은 아직은 불완전하다. 결코 과신하지 마라: 덜 Busy한 시간에 주기적으로 dump.db를 백업하고 중요한 장애 복구시는 수동으로 M-S를 지정해서 작업한다.
- Redis.log, Sentinel.log 를 수시로 모니터링하라: Redis 머신의 디스크가 고장나는 등의 하드웨어 장애는 Sentinel이 감지할 수 없다. Master 가 이런 장애를 만나면 Read-only 상태로 바뀔 수도 있다(config 설정에 따라 다르긴 하다)
데이터 관리 방법/설계 관련 주의 사항
- 운영 중에 keys, flushall, flushdb 명령어를 쓰지 마라: 메모리 DB라고 과신하다가 큰 코 다친다. Single thread 방식이며, 데이터 구조와 처리 방식이 상대적으로 Memcached보다 복잡해서 데이터 건수가 많아지면 Memcached 보다는 훨씬 느려진다(널리 알려진 사실이다. 100만건 처리에 멤캐시는 1~2ms, 레디스는 1000ms). 운영 중에 이런 전체 데이터를 긁는 명령을 쓴다면 Redis 를 사용하는 시스템이 일시 먹통이 될 테고, 한 동안 뒤통수가 따가울 수도 있다. 운영지침이나 데이터 관리 등급을 따로 지정해서 체계적으로 데이터를 운영/관리하는 방식을 써야 할지도 모른다
- 개발자는 Redis Master 는 Write-only로, Slave는 Read-only 로 잘 구분해서 잘 써야한다. 코드리뷰시 주요 검토사항이 될 수도 있다.
- 콜렉션(Collection: List, Set, Hash 등) 에는 한 번에 최대 수 천 건 정도의 데이터만 Insert한다.
- 최대 데이터 량을 예측하고, 필요시 클러스터링과 샤딩을 검토하여 아키텍처 설계에 반영하고 개선하라. 때때로 Zookeeper 나 Haproxy 와 같은 다른 솔루션을 검토해야 할 경우도 있을 수 있다.
- Barracuda -