
# 使用客户端驱动程序实现故障转移和读写分离
从PostgreSQL 10（libpq.so.5.10）开始，libpq驱动层开始支持故障转移和读写分离，JDBC驱动层则支持读写分离、故障转移和负载均衡。
PostgreSQL客户端连接程序向下兼容，对于RDS for PostgreSQL 9.5及9.6版本，使用新版本的libpq驱动程序也可以实现故障转移。
![](https://support.huaweicloud.com/bestpractice-rds-pg/public_sys-resources/note_3.0-zh-cn.png)
本章节中故障转移指的是读业务的故障转移。
- libpq是PostgreSQL的C应用程序接口，包含一组库函数，允许客户端程序将查询请求发送给PostgreSQL后端服务器并接收这些查询的结果。
- JDBC是Java语言中用来规范客户端程序如何访问数据库的应用程序接口，在PostgreSQL中JDBC支持故障转移和负载均衡。
表1libpq和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](https://www.postgresql.org/docs/14/libpq-connect.html#LIBPQ-CONNSTRING)。
您还可以在应用程序中结合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实例](https://support.huaweicloud.com/usermanual-rds-pg/rds_pg_connect_08.html)。
表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：随机连接数据库。                                                                                                                                                                                                                                                                             |
   
![](https://support.huaweicloud.com/bestpractice-rds-pg/public_sys-resources/note_3.0-zh-cn.png)
区别数据库主从的方式是通过查询数据库是否允许写入，允许写入数据的判断为主数据库，不允许写入数据的判断为从数据库。参考[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**
  
 
