개요
MHA 기반으로 MySQL 의 서버사이드의 Failover 를 구성하였을때 클라이언트 사이드 Failover 로 HAProxy 를 이용하여 테스트 진행한다.
테스트는 2개 노드로 진행하지만 실 운영에서는 최소 3개 노드가 필요하다.
HAProxy 를 이용한 failover 예상 아키텍쳐
- MySQL
- MHA manager
- Proxy( HAProxy )
- L4,L7, GSLB
HAProxy 특징
- Event-Driven Architeture
- Non-Blocking I/O
- zero-copy
- multi process or multi thread model
- CPU Cache efficiency
- 같은 작업에 대해 동일 CPU 를 연결 → cache miss 를 줄여 성능향상
- TCP, HTTP close mode
- Kernel 85%, HAProxy 15%
- HTTP keep-alive mode
- Kernel 70%, HAProxy 30%
- 대부분의 사용자(99%)에겐 싱글코어로 충분하다
- 초당 300000 proxy 기능 수행 가능
- Context switch latency 비용이 큰 process 와 CPU 를 공유하면 안된다.
- 계산집약적 process 등
- NUMA 설정 ( cpu-mapoption )
HAProxy 주요기능
HAProxy 의 기능은 범용 Proxy 인 만큼 매우 다양하지만 DB end point 에 대한 load balancing 과 failover 관점에서 필요한 주요 기능들에 대해서 기술하면 다음과 같다.
Proxying
두개의 독립적인 연결을 통해 클라이언트와 서버간에 데이터를 전송하는 기능
- 서버포트와 서비스포트의 분리
- 양쪽에 서로 다른 프로토콜을 지원( ipv4, ipv6, unix)
- Timeout 제공
Statistics
- 웹 기반의 통계데이터를 제공
- 다른 HAProxy 노드의 가용성을 보고 받을 수 있다.
- 데이터를 csv 형태로도 제공되어 다른 그래프 도구의 데이터로 사용될 수 있다.
- 관리자 기능 제공
- Prometheus 로 내보내기도 가능 ( PMM )
- 데이터베이스 와 함게 모니터링 구성하기에 용이
Monitoring
HAProxy 는 서버 및 네트워크의 상태를 모니터링 한다.
- 주기적으로 서버의 상태를 모니터링
- 다양한 체크 방법 제공
- TCP connect, HTTP request, SMTP hello, SSL hello, LDAP, SQL, Redis, send/expect scripts, all with/without SSL
- 상태 변경에 대한 실패 이유와 당시의 수신된 데이터등을 통계인터페이스로 공유 가능
High availability
- 유효한 서버만 사용되며 이외의 것은 자동으로 사용대상에서 삭제된다.
- 백업 서버는 active 서버들이 사용되지 않을때 자동으로 사용되며 가능하다면 session 이 손실되지 않음
- Stateless design
- Caching, load balancing, scale-out 용이
Load balancing
- 10개 이상의 load balancing algorithm 을 제공
- Round robin
- Leastconn
- 서버다 가중치 적용 가능
- 동적 가중치 가능
- Slow-start 지원
- 서버당 다양한 설정 가능
- 연결 수, 연결 슬롯 수
HAProxy 를 이용한 Failover
Failover 과정
- master 노드 장애 발생
- 장애 발생시 수초~수분까지 시간이 소요될 수 있다.
- 새로운 master 선정
- mha 에 의해 master 가 lag 이 가장 적은 slave 를 master 로 선정한다.
- 새로운 master 를 기준으로 데이터 sync
- 구 master 노드는 복구 후 slave 로 편입
- dba 에 의해 read_only 가 선행되고 새로운 master 를 통해 데이터를 sync 한다.
HAProxy 에서 제공하는 옵션
- mysql-check
listen mysql-*** ... ... option mysql-check user haproxy ... ...
- 문제점
- mysql initial handshake protocol 을 이용하여 접속 가능 여부만 확인한다.
Switch 로 대체할 수 없는 이유
- 단순한 ip/port 기반의 헬스체크
- MySQL 에 연결이 되어도 서비스가 불가한 상태일 수도 있다.
- IO Thread
- SQL Thread
- read_only status
MySQL 에 특화된 health check 필요
health check 를 위해 필요한 것을 haproxy 에서 제공하지 않는다.
다만 haproxy 의 기능과 사용자 스크립트, 외부 프로그램 등을 통해 health check 를 할 수 있다.
Master 와 slave 정상노드조건
장애가 발생하고 failover 가 끝나는 과정까지 master 노드와 slave 노드들을 구분할 수 있어야 한다.
조건 | |
master | global variable read_only = off |
slave |
|
HAproxy + user script
read / write split 를 위해 listen read(3307 port) 와 listen write(3306 port) 를 구성한다. 서버체크는 사용자 정의로 진행되야 하기 때문에 external-check 를 사용한다.
주의 할 것은 external-check path 는 external-check command 뿐 아니라 해당 스크립트 내에서 실행되는 모든 명령어의 path 도 함께 기입해 줘야 한다.
haproxy.cnf
global
log 127.0.0.1 local0
chroot /var/lib/haproxy
pidfile /var/run/haproxy.pid
maxconn 4000
user haproxy
group haproxy
daemon
nbproc 1
nbthread 1
# turn on stats unix socket
#stats socket /var/lib/haproxy/stats
#---------------------------------------------------------------------
# common defaults that all the 'listen' and 'backend' sections will
# use if not designated in their block
#---------------------------------------------------------------------
defaults
mode tcp
log global
timeout queue 1m
timeout connect 10s
timeout client 1m
timeout server 1m
timeout check 10s
listen stats
bind *:8080
mode http
stats enable
stats realm Haproxy\ Statistics
stats uri /stats
stats auth admin:admin
listen mysql-masters
bind *:3306
log 127.0.0.1:514 local0
balance roundrobin
option external-check
external-check path /usr/local/bin:/bin
external-check command /usr/local/bin/master_check.sh
option allbackups
server sql1 192.168.0.100:3306 check
server sql2 192.168.0.101:3306 check
listen mysql-slaves
bind *:3307
log 127.0.0.1:514 local0
balance roundrobin
option external-check
external-check path /usr/local/bin:/bin
external-check command /usr/local/bin/slave_check.sh
option allbackups
server sql1 192.168.0.100:3306 check
server sql2 192.168.0.101:3306 check
master_check.sh
#!/bin/bash
SLAVE_LAG_LIMIT=5
MYSQL_HOST="$3"
MYSQL_PORT="$4"
MYSQL_USERNAME='test'
MYSQL_PASSWORD='test'
MYSQL_BIN='/bin/mysql'
MYSQL_OPTS="-q -A --connect-timeout=10"
TMP_FILE="/dev/shm/mysqlchk.$$.out"
ERR_FILE="/dev/shm/mysqlchk.$$.err"
FORCE_FAIL="/dev/shm/proxyoff"
preflight_check()
{
for I in "$TMP_FILE" "$ERR_FILE"; do
if [ -f "$I" ]; then
if [ ! -w $I ]; then
echo -e "Cannot write to $I\r\n"
exit 2
fi
fi
done
}
return_ok()
{
exit 0
}
return_fail()
{
exit 255
}
preflight_check
if [ -f "$FORCE_FAIL" ]; then
echo "$FORCE_FAIL found" > $ERR_FILE
return_fail
fi
CMDLINE="$MYSQL_BIN $MYSQL_OPTS --host=$MYSQL_HOST --port=$MYSQL_PORT --user=$MYSQL_USERNAME --password=$MYSQL_PASSWORD -e"
READ_ONLY=$($CMDLINE 'SHOW GLOBAL VARIABLES LIKE "read_only"' --vertical 2>/dev/null | tail -1 | awk {'print $2'})
if [ "${READ_ONLY}" == "OFF" ];
then
return_ok
fi
return_fail
slave_check.sh
#!/bin/bash
SLAVE_LAG_LIMIT=5
MYSQL_HOST="$3"
MYSQL_PORT="$4"
MYSQL_USERNAME='test'
MYSQL_PASSWORD='test'
MYSQL_BIN='/bin/mysql'
MYSQL_OPTS="-q -A --connect-timeout=10"
TMP_FILE="/dev/shm/mysqlchk.$$.out"
ERR_FILE="/dev/shm/mysqlchk.$$.err"
FORCE_FAIL="/dev/shm/proxyoff"
preflight_check()
{
for I in "$TMP_FILE" "$ERR_FILE"; do
if [ -f "$I" ]; then
if [ ! -w $I ]; then
echo -e "Cannot write to $I\r\n"
exit 1
fi
fi
done
}
return_ok()
{
exit 0
}
return_fail()
{
exit 1
}
preflight_check
if [ -f "$FORCE_FAIL" ]; then
echo "$FORCE_FAIL found" > $ERR_FILE
return_fail
fi
CMDLINE="$MYSQL_BIN $MYSQL_OPTS --host=$MYSQL_HOST --port=$MYSQL_PORT --user=$MYSQL_USERNAME --password=$MYSQL_PASSWORD -e"
SLAVE_IO=$(${CMDLINE} 'SHOW SLAVE STATUS' --vertical 2>/dev/null | grep Slave_IO_Running | tail -1 | awk {'print $2'})
SLAVE_SQL=$(${CMDLINE} 'SHOW SLAVE STATUS' --vertical 2>/dev/null | grep Slave_SQL_Running | head -1 | awk {'print $2'})
# 1. read_only = on
READ_ONLY=$($CMDLINE 'SHOW GLOBAL VARIABLES LIKE "read_only"' --vertical 2>/dev/null | tail -1 | awk {'print $2'})
[[ "${READ_ONLY}" == "OFF" ]] && return_fail
# 2. Slave_IO_Running = Yes and Slave_SQL_Running = Yes
if [[ "${SLAVE_IO}" != "Yes" ]] || [[ "${SLAVE_SQL}" != "Yes" ]]; then
return _fail
fi
# 3. lag < $SLAVE_LAG_LIMIT
SLAVE_LAG=$(${CMDLINE} 'SHOW SLAVE STATUS' --vertical 2>/dev/null | grep Seconds_Behind_Master | tail -1 | awk {'print $2'})
if [ $SLAVE_LAG -gt $SLAVE_LAG_LIMIT ] ; then
return_fail
fi
return_ok
reference
'Database > MySQL' 카테고리의 다른 글
Mysql 실행되는 로그 파일로 남기기 (0) | 2020.11.06 |
---|---|
MySQL Procedure Privileges (0) | 2020.11.04 |
replication 관련 명령어 모음 (1) | 2020.05.07 |
ERROR 1449 (HY000): The user specified as a definer ('mysql.infoschema'@'localhost') does not exist (0) | 2020.05.07 |
mysql auto increment 를 포함한 테이블에서 pk 생성시 주의점 (0) | 2020.03.25 |