|
| 1 | +## PostgreSQL HAProxy ha & load balance 代理 |
| 2 | + |
| 3 | +### 作者 |
| 4 | +digoal |
| 5 | + |
| 6 | +### 日期 |
| 7 | +2019-11-01 |
| 8 | + |
| 9 | +### 标签 |
| 10 | +PostgreSQL , haproxy , master listen , slave(s) listen , ha , stream replication , session loadbalance , xinetd |
| 11 | + |
| 12 | +---- |
| 13 | + |
| 14 | +## 背景 |
| 15 | +HAProxy是一个非常流行的4层、7层(http)负载均衡与会话路由软件,效率也非常高。 |
| 16 | + |
| 17 | +虽然他没有办法直接实现pg的读写分离,但是可以实现比较简单的几类PG接入点(监听点)以及会话failover管理: |
| 18 | + |
| 19 | +1、一个或多个主实例。(多个主实例通常是指如下架构:pg-xl的coordinator, citus的coordinator。或者完全对等的multi-master节点。) |
| 20 | + |
| 21 | + |
| 22 | + |
| 23 | +当会话对应的后端数据库检测到不符合预期状态时,主动断开会话,重联。(failover 会话) |
| 24 | + |
| 25 | +rw点切换后,主动断开这个节点的会话,迁移到新的rw节点 |
| 26 | + |
| 27 | + |
| 28 | + |
| 29 | +2、一个或多个只读实例。(例如一主多从多结构) |
| 30 | + |
| 31 | +当后端有多个ro节点时,可以load balance(会话级) |
| 32 | + |
| 33 | + |
| 34 | + |
| 35 | +切换后,主动断开这个节点的会话,迁移到新的ro节点 |
| 36 | + |
| 37 | + |
| 38 | + |
| 39 | +## 实现例子 |
| 40 | +参考 https://www.percona.com/blog/2019/10/31/postgresql-application-connection-failover-using-haproxy-with-xinetd/ |
| 41 | + |
| 42 | +环境: CentOS 7, PostgreSQL , 主从 多台 (主从切换不需要haproxy管,haproxy只负责根据后端数据库的状态切换会话) |
| 43 | + |
| 44 | +准备几个东西 |
| 45 | + |
| 46 | +- A simple shell script to check the status of the PostgreSQL instance running on the local machine. |
| 47 | +- A xinetd service daemonizer. |
| 48 | +- HAProxy: Which maintains the routing mechanism. |
| 49 | + |
| 50 | +### 数据库服务器部署:check 后端数据库的脚本 |
| 51 | +这个脚本配置在数据库服务器上,用来获取数据库的三种状态: |
| 52 | + |
| 53 | +1、恢复中(从库),返回206 |
| 54 | + |
| 55 | +2、主库,返回200 |
| 56 | + |
| 57 | +3、无法确认(数据库连接不上或其他问题),返回503 |
| 58 | + |
| 59 | +脚本如下,不同状态返回不同的内容: |
| 60 | + |
| 61 | +``` |
| 62 | +#!/bin/bash |
| 63 | +# This script checks if a postgres server is healthy running on localhost. It will return: |
| 64 | +# "HTTP/1.x 200 OK\r" (if postgres is running smoothly) |
| 65 | +# - OR - |
| 66 | +# "HTTP/1.x 500 Internal Server Error\r" (else) |
| 67 | +# The purpose of this script is make haproxy capable of monitoring postgres properly |
| 68 | +# It is recommended that a low-privileged postgres user is created to be used by this script. |
| 69 | +# For eg. create user healthchkusr login password 'hc321'; |
| 70 | + |
| 71 | +PGBIN=/usr/pgsql-10/bin |
| 72 | +PGSQL_HOST="localhost" |
| 73 | +PGSQL_PORT="5432" |
| 74 | +PGSQL_DATABASE="postgres" |
| 75 | +PGSQL_USERNAME="postgres" |
| 76 | +export PGPASSWORD="passwd" |
| 77 | +TMP_FILE="/tmp/pgsqlchk.out" |
| 78 | +ERR_FILE="/tmp/pgsqlchk.err" |
| 79 | + |
| 80 | + |
| 81 | +# We perform a simple query that should return a few results |
| 82 | +# 调用如下脚本,看返回结果pg_is_in_recovery看是不是在恢复中,t表示从库。f表示主库。 |
| 83 | + |
| 84 | +VALUE=`/opt/bigsql/pg96/bin/psql -t -h localhost -U postgres -p 5432 -c "select pg_is_in_recovery()" 2> /dev/null` |
| 85 | +# Check the output. If it is not empty then everything is fine and we return something. Else, we just do not return anything. |
| 86 | + |
| 87 | + |
| 88 | +if [ $VALUE == "t" ] |
| 89 | +then |
| 90 | + /bin/echo -e "HTTP/1.1 206 OK\r\n" |
| 91 | + /bin/echo -e "Content-Type: Content-Type: text/plain\r\n" |
| 92 | + /bin/echo -e "\r\n" |
| 93 | + /bin/echo "Standby" |
| 94 | + /bin/echo -e "\r\n" |
| 95 | +elif [ $VALUE == "f" ] |
| 96 | +then |
| 97 | + /bin/echo -e "HTTP/1.1 200 OK\r\n" |
| 98 | + /bin/echo -e "Content-Type: Content-Type: text/plain\r\n" |
| 99 | + /bin/echo -e "\r\n" |
| 100 | + /bin/echo "Primary" |
| 101 | + /bin/echo -e "\r\n" |
| 102 | +else |
| 103 | + /bin/echo -e "HTTP/1.1 503 Service Unavailable\r\n" |
| 104 | + /bin/echo -e "Content-Type: Content-Type: text/plain\r\n" |
| 105 | + /bin/echo -e "\r\n" |
| 106 | + /bin/echo "DB Down" |
| 107 | + /bin/echo -e "\r\n" |
| 108 | +fi |
| 109 | +``` |
| 110 | + |
| 111 | +脚本位置/opt/pgsqlchk |
| 112 | + |
| 113 | +``` |
| 114 | +sudo chmod 755 /opt/pgsqlchk |
| 115 | +``` |
| 116 | + |
| 117 | + |
| 118 | +### 数据库服务器部署:配置 xinetd 服务(check后端) |
| 119 | +将/opt/pgsqlchk 检查脚本配置为xinetd服务,通过某个监听端口调用(本例为23267) |
| 120 | + |
| 121 | +``` |
| 122 | +yum install -y xinetd telnet |
| 123 | + |
| 124 | +vi /etc/xinetd.d/pgsqlchk |
| 125 | + |
| 126 | +service pgsqlchk |
| 127 | +{ |
| 128 | + flags = REUSE |
| 129 | + socket_type = stream |
| 130 | + port = 23267 |
| 131 | + wait = no |
| 132 | + user = nobody |
| 133 | + server = /opt/pgsqlchk |
| 134 | + log_on_failure += USERID |
| 135 | + disable = no |
| 136 | + only_from = 0.0.0.0/0 |
| 137 | + per_source = UNLIMITED |
| 138 | +} |
| 139 | +``` |
| 140 | + |
| 141 | +添加xinetd服务 |
| 142 | + |
| 143 | +``` |
| 144 | +bash -c 'echo "pgsqlchk 23267/tcp # pgsqlchk" >> /etc/services' |
| 145 | +``` |
| 146 | + |
| 147 | +启动xinetd服务 |
| 148 | + |
| 149 | +``` |
| 150 | +systemctl start xinetd |
| 151 | +``` |
| 152 | + |
| 153 | +### HAproxy节点部署 |
| 154 | +假设以上有两台数据库服务器pg0, pg1 (hostname, or dns配置). |
| 155 | + |
| 156 | +另一台服务器,用于部署haproxy,如下。 |
| 157 | + |
| 158 | +``` |
| 159 | +yum install -y haproxy |
| 160 | +``` |
| 161 | + |
| 162 | + |
| 163 | +配置haproxy.cfg |
| 164 | + |
| 165 | +监听两个端口,5000对应check返回200状态的数据库节点(pg_is_in_recovery=f),为rw节点 |
| 166 | + |
| 167 | +5001对应check返回206状态的数据库节点(pg_is_in_recovery=t),为ro节点 |
| 168 | + |
| 169 | + |
| 170 | +``` |
| 171 | +vi /etc/haproxy/haproxy.cfg |
| 172 | + |
| 173 | +global |
| 174 | + maxconn 100 |
| 175 | + |
| 176 | +defaults |
| 177 | + log global |
| 178 | + mode tcp |
| 179 | + retries 2 |
| 180 | + timeout client 30m |
| 181 | + timeout connect 4s |
| 182 | + timeout server 30m |
| 183 | + timeout check 5s |
| 184 | + |
| 185 | +listen stats |
| 186 | + mode http |
| 187 | + bind *:7000 |
| 188 | + stats enable |
| 189 | + stats uri / |
| 190 | + |
| 191 | +listen ReadWrite |
| 192 | + bind *:5000 |
| 193 | + option httpchk |
| 194 | + http-check expect status 200 |
| 195 | + default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions |
| 196 | + server pg0 pg0:5432 maxconn 100 check port 23267 |
| 197 | + server pg1 pg1:5432 maxconn 100 check port 23267 |
| 198 | + |
| 199 | +listen ReadOnly |
| 200 | + bind *:5001 |
| 201 | + option httpchk |
| 202 | + http-check expect status 206 |
| 203 | + default-server inter 3s fall 3 rise 2 on-marked-down shutdown-sessions |
| 204 | + server pg0 pg0:5432 maxconn 100 check port 23267 |
| 205 | + server pg1 pg1:5432 maxconn 100 check port 23267 |
| 206 | +``` |
| 207 | + |
| 208 | +配置解读 |
| 209 | + |
| 210 | +- HAProxy is configured to use TCP mode |
| 211 | +- HAProxy service will start listening to port 5000 and 5001 |
| 212 | +- Port 5000 is for Read-Write connections and 5001 is for Read-Only connections |
| 213 | +- Status check is done using http-check feature on port 23267 |
| 214 | +- Both server pg0 and pg1 are candidates for both Read-write and Read-only connections |
| 215 | +- Based on the http-check and the status returned, it decides the current role |
| 216 | + |
| 217 | +如果只读节点有多台,配置到server里面即可。 |
| 218 | + |
| 219 | +启动 haproxy服务 |
| 220 | + |
| 221 | +``` |
| 222 | +systemctl start haproxy |
| 223 | +``` |
| 224 | + |
| 225 | +### 测试 |
| 226 | +连接haproxy 5000端口,读写节点。 |
| 227 | + |
| 228 | +``` |
| 229 | +$ psql -h localhost -p 5000 -U postgres |
| 230 | +Password for user postgres: |
| 231 | +psql (9.6.5) |
| 232 | +Type "help" for help. |
| 233 | + |
| 234 | +postgres=# select pg_is_in_recovery(); |
| 235 | +pg_is_in_recovery |
| 236 | +------------------- |
| 237 | +f |
| 238 | +(1 row) |
| 239 | +``` |
| 240 | + |
| 241 | +连接haproxy 5001端口,只读节点。 |
| 242 | + |
| 243 | +``` |
| 244 | +$ psql -h localhost -p 5001 -U postgres |
| 245 | +Password for user postgres: |
| 246 | +psql (9.6.5) |
| 247 | +Type "help" for help. |
| 248 | + |
| 249 | +postgres=# select pg_is_in_recovery(); |
| 250 | +pg_is_in_recovery |
| 251 | +------------------- |
| 252 | +t |
| 253 | +(1 row) |
| 254 | +``` |
| 255 | + |
| 256 | +## haproxy配置介绍 |
| 257 | + |
| 258 | +https://www.cnblogs.com/MacoLee/p/5853413.html |
| 259 | + |
| 260 | +https://www.jianshu.com/p/8af373981cfe |
| 261 | + |
| 262 | +``` |
| 263 | +###########全局配置######### |
| 264 | +global |
| 265 | + log 127.0.0.1 local0 #[日志输出配置,所有日志都记录在本机,通过local0输出] |
| 266 | + log 127.0.0.1 local1 notice #定义haproxy 日志级别[error warringinfo debug] |
| 267 | + daemon #以后台形式运行harpoxy |
| 268 | + nbproc 1 #设置进程数量 |
| 269 | + maxconn 4096 #默认最大连接数,需考虑ulimit-n限制 |
| 270 | + #user haproxy #运行haproxy的用户 |
| 271 | + #group haproxy #运行haproxy的用户所在的组 |
| 272 | + #pidfile /var/run/haproxy.pid #haproxy 进程PID文件 |
| 273 | + #ulimit-n 819200 #ulimit 的数量限制 |
| 274 | + #chroot /usr/share/haproxy #chroot运行路径 |
| 275 | + #debug #haproxy 调试级别,建议只在开启单进程的时候调试 |
| 276 | + #quiet |
| 277 | + |
| 278 | +########默认配置############ |
| 279 | +defaults |
| 280 | + log global |
| 281 | + mode http #默认的模式mode { tcp|http|health },tcp是4层,http是7层,health只会返回OK |
| 282 | + option httplog #日志类别,采用httplog |
| 283 | + option dontlognull #不记录健康检查日志信息 |
| 284 | + retries 2 #两次连接失败就认为是服务器不可用,也可以通过后面设置 |
| 285 | + #option forwardfor #如果后端服务器需要获得客户端真实ip需要配置的参数,可以从Http Header中获得客户端ip |
| 286 | + option httpclose #每次请求完毕后主动关闭http通道,haproxy不支持keep-alive,只能模拟这种模式的实现 |
| 287 | + #option redispatch #当serverId对应的服务器挂掉后,强制定向到其他健康的服务器,以后将不支持 |
| 288 | + option abortonclose #当服务器负载很高的时候,自动结束掉当前队列处理比较久的链接 |
| 289 | + maxconn 4096 #默认的最大连接数 |
| 290 | + timeout connect 5000ms #连接超时 |
| 291 | + timeout client 30000ms #客户端超时 |
| 292 | + timeout server 30000ms #服务器超时 |
| 293 | + #timeout check 2000 #心跳检测超时 |
| 294 | + #timeout http-keep-alive10s #默认持久连接超时时间 |
| 295 | + #timeout http-request 10s #默认http请求超时时间 |
| 296 | + #timeout queue 1m #默认队列超时时间 |
| 297 | + balance roundrobin #设置默认负载均衡方式,轮询方式 |
| 298 | + #balance source #设置默认负载均衡方式,类似于nginx的ip_hash |
| 299 | + #balnace leastconn #设置默认负载均衡方式,最小连接数 |
| 300 | + |
| 301 | +########统计页面配置######## |
| 302 | +listen stats |
| 303 | + bind 0.0.0.0:1080 #设置Frontend和Backend的组合体,监控组的名称,按需要自定义名称 |
| 304 | + mode http #http的7层模式 |
| 305 | + option httplog #采用http日志格式 |
| 306 | + #log 127.0.0.1 local0 err #错误日志记录 |
| 307 | + maxconn 10 #默认的最大连接数 |
| 308 | + stats refresh 30s #统计页面自动刷新时间 |
| 309 | + stats uri /stats #统计页面url |
| 310 | + stats realm XingCloud\ Haproxy #统计页面密码框上提示文本 |
| 311 | + stats auth admin:admin #设置监控页面的用户和密码:admin,可以设置多个用户名 |
| 312 | + stats auth Frank:Frank #设置监控页面的用户和密码:Frank |
| 313 | + stats hide-version #隐藏统计页面上HAProxy的版本信息 |
| 314 | + stats admin if TRUE #设置手工启动/禁用,后端服务器(haproxy-1.4.9以后版本) |
| 315 | + |
| 316 | +########设置haproxy 错误页面##### |
| 317 | +#errorfile 403 /home/haproxy/haproxy/errorfiles/403.http |
| 318 | +#errorfile 500 /home/haproxy/haproxy/errorfiles/500.http |
| 319 | +#errorfile 502 /home/haproxy/haproxy/errorfiles/502.http |
| 320 | +#errorfile 503 /home/haproxy/haproxy/errorfiles/503.http |
| 321 | +#errorfile 504 /home/haproxy/haproxy/errorfiles/504.http |
| 322 | + |
| 323 | +########frontend前端配置############## |
| 324 | +frontend main |
| 325 | + bind *:80 #这里建议使用bind *:80的方式,要不然做集群高可用的时候有问题,vip切换到其他机器就不能访问了。 |
| 326 | + acl web hdr(host) -i www.abc.com #acl后面是规则名称,-i为忽略大小写,后面跟的是要访问的域名,如果访问www.abc.com这个域名,就触发web规则,。 |
| 327 | + acl img hdr(host) -i img.abc.com #如果访问img.abc.com这个域名,就触发img规则。 |
| 328 | + use_backend webserver if web #如果上面定义的web规则被触发,即访问www.abc.com,就将请求分发到webserver这个作用域。 |
| 329 | + use_backend imgserver if img #如果上面定义的img规则被触发,即访问img.abc.com,就将请求分发到imgserver这个作用域。 |
| 330 | + default_backend dynamic #不满足则响应backend的默认页面 |
| 331 | + |
| 332 | +########backend后端配置############## |
| 333 | +backend webserver #webserver作用域 |
| 334 | + mode http |
| 335 | + balance roundrobin #balance roundrobin 负载轮询,balance source 保存session值,支持static-rr,leastconn,first,uri等参数 |
| 336 | + option httpchk /index.html HTTP/1.0 #健康检查, 检测文件,如果分发到后台index.html访问不到就不再分发给它 |
| 337 | + server web1 10.16.0.9:8085 cookie 1 weight 5 check inter 2000 rise 2 fall 3 |
| 338 | + server web2 10.16.0.10:8085 cookie 2 weight 3 check inter 2000 rise 2 fall 3 |
| 339 | + #cookie 1表示serverid为1,check inter 1500 是检测心跳频率 |
| 340 | + #rise 2是2次正确认为服务器可用,fall 3是3次失败认为服务器不可用,weight代表权重 |
| 341 | + |
| 342 | +backend imgserver |
| 343 | + mode http |
| 344 | + option httpchk /index.php |
| 345 | + balance roundrobin |
| 346 | + server img01 192.168.137.101:80 check inter 2000 fall 3 |
| 347 | + server img02 192.168.137.102:80 check inter 2000 fall 3 |
| 348 | + |
| 349 | +backend dynamic |
| 350 | + balance roundrobin |
| 351 | + server test1 192.168.1.23:80 check maxconn 2000 |
| 352 | + server test2 192.168.1.24:80 check maxconn 2000 |
| 353 | + |
| 354 | + |
| 355 | +listen tcptest |
| 356 | + bind 0.0.0.0:5222 |
| 357 | + mode tcp |
| 358 | + option tcplog #采用tcp日志格式 |
| 359 | + balance source |
| 360 | + #log 127.0.0.1 local0 debug |
| 361 | + server s1 192.168.100.204:7222 weight 1 |
| 362 | + server s2 192.168.100.208:7222 weight 1 |
| 363 | +``` |
| 364 | + |
| 365 | +## 参考 |
| 366 | +https://www.percona.com/blog/2019/10/31/postgresql-application-connection-failover-using-haproxy-with-xinetd/ |
| 367 | + |
| 368 | +https://www.jianshu.com/p/8af373981cfe |
| 369 | + |
| 370 | +https://www.cnblogs.com/MacoLee/p/5853413.html |
| 371 | + |
| 372 | + |
| 373 | +#### [免费领取阿里云RDS PostgreSQL实例、ECS虚拟机](https://free.aliyun.com/ "57258f76c37864c6e6d23383d05714ea") |
| 374 | + |
| 375 | + |
| 376 | +#### [digoal's PostgreSQL文章入口](https://github.com/digoal/blog/blob/master/README.md "22709685feb7cab07d30f30387f0a9ae") |
| 377 | + |
| 378 | + |
| 379 | + |
| 380 | + |
0 commit comments