文档首页/ 云数据库 RDS/ 最佳实践/ RDS for PostgreSQL/ 使用客户端驱动程序实现故障转移和读写分离
更新时间:2024-09-30 GMT+08:00

使用客户端驱动程序实现故障转移和读写分离

从PostgreSQL 10(libpq.so.5.10)开始,libpq驱动层开始支持故障转移和读写分离,JDBC驱动层则支持读写分离、故障转移和负载均衡。

PostgreSQL客户端连接程序向下兼容,对于RDS for PostgreSQL 9.5及9.6版本,使用新版本的libpq驱动程序也可以实现故障转移。

本章节中故障转移指的是读业务的故障转移。

  • libpq是PostgreSQL的C应用程序接口,包含一组库函数,允许客户端程序将查询请求发送给PostgreSQL后端服务器并接收这些查询的结果。
  • JDBC是Java语言中用来规范客户端程序如何访问数据库的应用程序接口,在PostgreSQL中JDBC支持故障转移和负载均衡。
表1 libpq和JDBC驱动支持的功能

驱动

读写分离

负载均衡

故障转移

libpq驱动

×

JDBC驱动

libpq实现故障转移和读写分离

通过libpq函数连接多个数据库,当出现故障时会自动切换到可用的数据库。

postgresql://[user[:password]@][netloc][:port][,...][/dbname][?param1=value1&...]

示例:连接1个RDS for PostgreSQL主实例数据库和对应的2个只读实例数据库,只要确保至少有一个数据库可用,读请求就不会失败。

postgres://<instance_ip>:<instance_port>,<instance_ip>:<instance_port>,<instance_ip>:<instance_port>/<database_name>?target_session_attrs=any

表2 参数说明

参数

说明

取值样例

<instance_ip>

数据库的主机IP。

如果通过内网连接,“instance_ip”是主机IP,即“概览”页面该实例的“内网地址”。

如果通过连接了公网的设备访问,“instance_ip”为该实例已绑定的“弹性公网IP”。

<instance_port>

数据库端口。

默认5432,当前端口,参考“概览”页面该实例的“数据库端口”。

<database_name>

数据库名,即需要连接的数据库名。

默认的管理数据库是postgres,可根据业务实际情况填写数据库名。

target_session_attrs

允许连接到指定状态的数据库。

  • any:默认值,表示允许连接到任意数据库,会连接到第一个允许连接的数据库,如果连接的数据库出现故障导致连接断开,会尝试连接其他数据库,从而实现故障转移。
  • read-write:只会连接到支持读写的数据库,即从第一个数据库开始尝试连接,如果连接后发现不支持读写,则会断开连接,然后尝试连接第二个数据库,以此类推,直至连接到支持读写的数据库。
  • read-only:只会连接只读数据库,即从第一个数据库开始尝试连接,如果连接后发现支持读写,则会断开连接,然后尝试连接第二个数据库,以此类推,直至连接到只读的数据库。RDS for PostgreSQL 13(libpq.so.5.13)及以下版本,不支持该取值。

更多libpq的使用方法和参数说明请参见Connection Strings

您还可以在应用程序中结合pg_is_in_recovery()函数,判断连接的数据库是主实例数据库(结果为“f”表示主数据库)还是只读实例数据库,进而实现读写分离。

使用Python代码的示例如下(psycopg2使用的为libpq):

// 认证用的用户名和密码直接写到代码中有很大的安全风险,建议在配置文件或者环境变量中存放(密码应密文存放,使用时解密),确保安全。
// 本示例以用户名和密码保存在环境变量中为例,运行本示例前请先在本地环境中设置环境变量(环境变量名称请根据自身情况进行设置)EXAMPLE_USERNAME_ENV和EXAMPLE_PASSWORD_ENV。

import psycopg2
import os

username = os.getenv("EXAMPLE_USERNAME_ENV")
password = os.getenv("EXAMPLE_PASSWORD_ENV")
conn = psycopg2.connect(database=<database_name>,host=<instance_ip>, user=username, password=password, port=<instance_port>, target_session_attrs="read-write")
cur = conn.cursor()
cur.execute("select pg_is_in_recovery()")
row = cur.fetchone()
print("recovery =", row[0])

JDBC实现故障转移和读写分离

您可以在连接URL中定义多个数据库(主机和端口),并用逗号分隔,驱动程序将尝试按顺序连接到它们中的每一个,直到连接成功。如果没有成功,会返回连接异常报错。

jdbc:postgresql://node1,node2,node3/${database}?targetServerType=preferSecondary&loadBalanceHosts=true

示例:

jdbc:postgresql://<instance_ip>:<instance_port>,<instance_ip>:<instance_port>,<instance_ip>:<instance_port>/<database_name>?targetServerType=preferSecondary&loadBalanceHosts=true

JDBC连接实例java实现代码请参考:通过JDBC连接RDS for PostgreSQL实例

表3 参数说明

参数

说明

取值样例

targetServerType

允许连接到指定状态的数据库。

  • any:任何数据库。
  • primary:主数据库(可写可读)。JDBC 42.2.0以下版本请使用参数值“master”。
  • secondary:从数据库(可读)。JDBC 42.2.0以下版本请使用参数值“slave”。
  • preferSecondary:优先从数据库,如果没有从数据库才连接到主数据库。JDBC 42.2.0以下版本请使用参数值“preferSlave”。

loadBalanceHosts

尝试连接数据库的顺序。

  • False:默认值,按URL中的定义顺序连接数据库。
  • True:随机连接数据库。

区别数据库主从的方式是通过查询数据库是否允许写入,允许写入数据的判断为主数据库,不允许写入数据的判断为从数据库。参考libpq实现故障转移和读写分离中通过pg_is_in_recovery()函数来判断,结果为“f”表示为主数据库。

为实现读写分离,需要在配置JDBC时设置2个数据源,首先设置targetServerType=primary,用于写操作。另一个可以根据以下情况进行设置:

  • 有一个只读实例,为实现高可用设置targetServerType=preferSecondary,用于读操作。假设主实例IP为10.1.1.1,只读实例IP为10.1.1.2。

    jdbc:postgresql://10.1.1.2:5432,10.1.1.1:5432/${database}?targetServerType=preferSecondary

  • 有两个以上只读实例,可设置targetServerType=any,用于读操作。假设只读实例IP分别为10.1.1.2、10.1.1.3。

    jdbc:postgresql://10.1.1.2:5432,10.1.1.3:5432/${database}?targetServerType=any&loadBalanceHosts=true