前言

上一回我们讲了怎么安装pg_auto_failover,今天就来讲一下客户端怎么配置故障转移。
从PostgreSQL 10版本之后,使用PSQL客户端就可以利用libpq
驱动进行故障转移(不能负载均衡)。也就是我们配置客户端连接,数据库的主节点或者备节点出现故障后,也可以保障业务的连续性。libpq
是一组库函数,允许客户端程序将查询传递到PostgreSQL后端服务器并接收这些查询返回的结果。libpq
还是其他几个PostgreSQL应用程序接口的基础引擎,包括为C ++,PHP,Perl,Python,Tcl,Swift和ECPG编写的接口。这里没有Java,因为Java的故障转移和负载均衡要使用PostgreSQL JDBC驱动程序。
在PSQL客户端,我们只需要配置好多个IP地址端口,调整参数target_session_attrs
就行了。
target_session_attrs
read-write
在连接的时候,只接受可以读写的数据库。顾名思义,当它建立连接后,会发送SHOW transaction_read_only
,如果是on,就代表是只读库,它就会把连接关闭,然后测试第二个数据库,以此类推,直至连接到支持读写的数据库为止。any
,它表示可以允许连接到任意数据库,它会从所有配置的连接中随机选择一个,如果连接的数据库出现故障导致连接断开,会尝试连接其他数据库,从而实现故障转移。
接下来我们来测试一下。
使用read-write连接到主节点
使用target_session_attrs=read-write,就可以连接到主节点进行读-写操作。还是先看一下我们的集群环境。
[postgres@paf1 data]$ pg_autoctl show state
Name | Node | Host:Port | LSN | Reachable | Current State | Assigned State
-------+-------+------------+-----------+-----------+---------------------+--------------------
node_1 | 1 | paf2:5432 | 0/3091DF8 | yes | primary | primary
node_2 | 3 | paf3:5432 | 0/3091DF8 | yes | secondary | secondary
我的客户端,IP地址是192.168.56.99,通过客户端连接集群这两个地址。
[postgres@centos8 ~]$ [postgres@centos8 ~]$ ifconfig -a
eth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.168.56.99 netmask 255.255.255.0 broadcast 192.168.56.255
inet6 fe80::a00:27ff:fe70:b8bc prefixlen 64 scopeid 0x20<link>
ether 08:00:27:70:b8:bc txqueuelen 1000 (Ethernet)
RX packets 6917 bytes 1270060 (1.2 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 8339 bytes 1306666 (1.2 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
[postgres@centos8 ~]$ psql 'postgres://192.168.56.94:5432,192.168.56.95:5432/postgres?target_session_attrs=read-write' \
> -c "select inet_server_addr()"
inet_server_addr
------------------
192.168.56.94
(1 row)
如上面所示,我配置了两个ip地址,一个是主节点,一个是从节点。当前它会选择连主节点进行读写,一旦主节点发生了故障,集群出现切换,客户端就会连接到从节点进行读写。
我们来编写一个循环插入的脚本测试,首先创建一个表,然后在客户端执行循环插入脚本
postgres=# CREATE TABLE t1 (id int GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name text);
CREATE TABLE
##循环插入脚本
while true
do
psql 'postgres://192.168.56.94:5432,192.168.56.95:5432/postgres?target_session_attrs=read-write' <<EOF
select inet_server_addr();
select now();
insert into t1(name) values(md5(random()::text));
EOF
sleep 1
done
循环插入脚本执行的输出如下。
now | inet_server_addr
-------------------------------+------------------
2020-12-16 09:26:50.205583+00 | 192.168.56.94
(1 row)
INSERT 0 1
now | inet_server_addr
-------------------------------+------------------
2020-12-16 09:26:51.235959+00 | 192.168.56.94
(1 row)
INSERT 0 1
now | inet_server_addr
-------------------------------+------------------
2020-12-16 09:26:52.268203+00 | 192.168.56.94
(1 row)
INSERT 0 1
此时,我们在192.168.56.94上执行pg_autoctl stop
。就会停止主库,并发生主从切换操作。

可以看到,客户端插入短暂出现1-2秒的异常。这是因为主从发生了切换。切换成功之后客户端仍然可以写入到从库,实现了客户端读写的连续性。
使用any连接到任意服务器进行读写
使用target_session_attrs=any
选项,将自动连接列表中的任意服务器,使用下面的脚本运行。
while true
do
psql 'postgres://192.168.56.94:5432,192.168.56.95:5432/postgres?target_session_attrs=any' <<EOF
select now(),inet_server_addr();
insert into t1(name) values(md5(random()::text));
EOF
sleep 1
done
我们观察到连接上了主库。此时在主节点执行pg_autoctl stop
。
[postgres@paf2 ~]$ pg_autoctl show state
Name | Node | Host:Port | LSN | Reachable | Current State | Assigned State
-------+-------+------------+-----------+-----------+---------------------+--------------------
node_1 | 1 | paf2:5432 | 0/3165DE8 | yes | primary | primary
node_2 | 3 | paf3:5432 | 0/3165DE8 | yes | secondary | secondary
[postgres@paf2 ~]$ pg_autoctl stop
[postgres@paf2 ~]$ pg_autoctl show state
Name | Node | Host:Port | LSN | Reachable | Current State | Assigned State
-------+-------+------------+-----------+-----------+---------------------+--------------------
node_1 | 1 | paf2:5432 | 0/31846A0 | yes | secondary | secondary
node_2 | 3 | paf3:5432 | 0/31846A0 | yes | primary | primary
执行完stop之后,主从发生了切换。

此时可以观察到主从切换后,程序连上了从节点。但是无法插入。这是因为它并没有像read-write
发起的SHOW transaction_read_only
判断是不是从库。而一旦主从切换完成后,它又会重新连回了主节点,而此时的主节点已经变成了从库,当然也是无法写入的。这就很尴尬了,如果使用read-write就不会存在这样的问题。
如果此时在从节点上执行pg_autoctl stop
,主从再一次发生切换,切换回去就又能正常写入了。

因此,target_session_attrs=any
这个选项在使用过程中还是需要谨慎一点的。建议设置成读模式。这样才可以随便使用啊喂。
使用Python来操作
既然有Python这个选项,那我们还是来写个小程序测试一下吧。
vi pg_conn.py
import psycopg2
conn = psycopg2.connect(database="postgres",host="192.168.56.94,192.168.56.95", user="postgres", password="sqlite123", port="5432", target_session_attrs="read-write")
cur = conn.cursor()
cur.execute("select pg_is_in_recovery(),now(),inet_server_addr()")
row = cur.fetchone()
print "recovery =",row[0]
print "now =",row[1]
print "server =",row[2]
$ python pg_conn.py
recovery = f
now = 2020-12-16 14:33:33.280008+00
server = 192.168.56.94
我们在python的connect的方法中,可以直接设置target_session_attrs="read-write"。
客户端使用HA总结
建议使用read-write来使用读写的场景,因为它会执行 SHOW transaction_read_only
来判断是否是只读库,如果是只读库就会断开连接。建议使用 any
来使用读场景,因为它没有判断数据库是否是只读库,如果使用写的场景连上了只读库就会一直报cannot execute read-only transaction。客户端使用libqc方案只能用作故障转移而不能用作负载均衡。
最后还是老规矩,祝大家搭环境诸事顺利。




