暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

倾情打造PostgreSQL高可用系列(二):客户端的故障转移

前言

上一回我们讲了怎么安装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(namevalues(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方案只能用作故障转移而不能用作负载均衡。

最后还是老规矩,祝大家搭环境诸事顺利。


文章转载自励志成为PostgreSQL大神,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论