├── README.md ├── diagram.png ├── haproxy.cfg ├── notification.sh └── sentinel.conf /README.md: -------------------------------------------------------------------------------- 1 | # Redis HA - sentinel + haproxy 2 | 3 | - sentinel: autoswitch master/slave 4 | - haproxy: active check for only master node 5 | - haproxy api: disable failed nodes to prevent multimaster 6 | 7 | !["diagram"](diagram.png) 8 | 9 | without haproxy maintenance mode 10 | ``` 11 | A is master, B is slave 12 | A crashes, sentinel convert B to master 13 | A is recovered (still master) 14 | haproxy balancing between A and B, until sentinel convert A to slave 15 | data written to A are lost 16 | ``` 17 | 18 | witch haproxy maintenance mode via notification script 19 | ``` 20 | A is master, B is slave 21 | A crashes, sentinel convert B to master 22 | haproxy disable A 23 | A is recovered (still master) but disabled in haproxy 24 | haproxy points only to B 25 | ``` 26 | 27 | ### Run redis cluster 28 | ``` 29 | redis-server --port 6666 30 | redis-server --port 6667 31 | redis-server --port 6668 32 | ``` 33 | 34 | ### Set slaves 35 | ``` 36 | redis-cli -p 6667 SLAVEOF 127.0.0.1 6666 37 | redis-cli -p 6668 SLAVEOF 127.0.0.1 6666 38 | ``` 39 | 40 | ### Run sentinel 41 | ``` 42 | redis-server sentinel.conf --sentinel 43 | ``` 44 | 45 | ### Run haproxy 46 | ``` 47 | haproxy -f haproxy.cfg -db 48 | ``` 49 | 50 | Open http://localhost:8080/ and try kill some redis 51 | 52 | 53 | ### Notice 54 | tested on 55 | - redis 2.8.6 56 | - haproxy 1.5-dev21 57 | 58 | [!] in production on single host you must specify different data dir before SLAVEOF command otherwise you loose data on master 59 | -------------------------------------------------------------------------------- /diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/falsecz/haredis/334d82d74e3807298c669d582b4e9d7aac97c19a/diagram.png -------------------------------------------------------------------------------- /haproxy.cfg: -------------------------------------------------------------------------------- 1 | global 2 | daemon 3 | maxconn 256 4 | 5 | defaults 6 | mode tcp 7 | timeout connect 5000ms 8 | timeout client 50000ms 9 | timeout server 50000ms 10 | 11 | 12 | frontend http 13 | bind :8080 14 | 15 | default_backend stats 16 | 17 | backend stats 18 | mode http 19 | stats enable 20 | 21 | stats enable 22 | stats uri / 23 | stats refresh 1s 24 | stats show-legends 25 | stats admin if TRUE 26 | 27 | frontend redis-alpha 28 | bind *:6379 29 | default_backend redis-alpha 30 | 31 | 32 | 33 | backend redis-alpha 34 | mode tcp 35 | balance first 36 | option tcp-check 37 | 38 | 39 | tcp-check send info\ replication\r\n 40 | tcp-check expect string role:master 41 | 42 | 43 | 44 | server redis-alpha:127.0.0.1:6666 127.0.0.1:6666 maxconn 1024 check inter 1s 45 | server redis-alpha:127.0.0.1:6667 127.0.0.1:6667 maxconn 1024 check inter 1s 46 | server redis-alpha:127.0.0.1:6668 127.0.0.1:6668 maxconn 1024 check inter 1s 47 | 48 | 49 | backend redis-online 50 | mode tcp 51 | balance first 52 | option tcp-check 53 | 54 | tcp-check send PING\r\n 55 | tcp-check expect string +PONG 56 | 57 | server redis-alpha:127.0.0.1:6666 127.0.0.1:6666 maxconn 1024 check inter 1s 58 | server redis-alpha:127.0.0.1:6667 127.0.0.1:6667 maxconn 1024 check inter 1s 59 | server redis-alpha:127.0.0.1:6668 127.0.0.1:6668 maxconn 1024 check inter 1s 60 | 61 | -------------------------------------------------------------------------------- /notification.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | HAPROXY="http://localhost:8080/" 3 | CMD="$1" 4 | ARGS="$2" 5 | ARG1=`echo $ARGS | awk '{print $1}'` 6 | 7 | 8 | call_curl () { 9 | DATA=`echo "s=$1&action=$2&b=%234" | sed -e s/:/%3A/` 10 | curl --silent -o /dev/null $HAPROXY --data "$DATA" 11 | echo curl $HAPROXY --data "$DATA" 12 | return 0 13 | } 14 | 15 | 16 | [ "$CMD" = "+odown" ] && [ "$ARG1" = "master" ] && \ 17 | call_curl `echo $ARGS | awk '{print $2 ":" $3 ":" $4}'` 'disable' 18 | 19 | [ "$CMD" = "+sdown" ] && [ "$ARG1" = "slave" ] && \ 20 | call_curl `echo $ARGS | awk '{print $6 ":" $3 ":" $4}'` 'disable' 21 | 22 | [ "$CMD" = "+switch-master" ] && \ 23 | call_curl `echo $ARGS | awk '{print $1 ":" $4 ":" $5}'` 'enable' && 24 | call_curl `echo $ARGS | awk '{print $1 ":" $2 ":" $3}'` 'disable' 25 | 26 | [ "$CMD" = "-odown" ] && [ "$ARG1" = "master" ] && \ 27 | call_curl `echo $ARGS | awk '{print $2 ":" $3 ":" $4}'` 'enable' 28 | 29 | # without exit code, sentinel thinks the script is still running and locks any further execution 30 | exit 0 31 | -------------------------------------------------------------------------------- /sentinel.conf: -------------------------------------------------------------------------------- 1 | sentinel monitor redis-alpha 127.0.0.1 6666 1 2 | sentinel down-after-milliseconds redis-alpha 2000 3 | sentinel notification-script redis-alpha ./notification.sh 4 | --------------------------------------------------------------------------------